# openlayers

# OpenLayers Introduction

OpenLayers is a free, open-source frontend map development library based on JavaScript. With it, you can easily develop WebGIS systems. OpenLayers currently supports map tiles, vector data, and many other map data formats, with comprehensive map interaction support. OpenLayers has become a mature, popular framework with many developers and a helpful community, and is widely used in GIS-related industries both domestically and internationally.

OpenLayers official site: https://openlayers.org/ (opens new window)

OpenLayers source code: https://github.com/openlayers/openlayers (opens new window)

# Loading CAD Raster Tiles in OpenLayers

image-20221016204713951

// Map service object, call Vijmap service to open map and get map metadata
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// Open map
let mapId = "sys_zp";
let res = await svc.openMap({
    mapid: mapId, // Map ID
    mapopenway: vjmap.MapOpenWay.GeomRender, // Open with geometry data rendering
    style: vjmap.openMapDarkStyle() // Use dark background style when div has dark background
})
if (res.error) {
    // If open fails
    message.error(res.error)
}
// Get map bounds
let mapBounds = vjmap.GeoBounds.fromString(res.bounds);

// Custom projection parameters
let cadProjection = new ol.proj.Projection({
    // extent used to determine zoom levels
    extent: mapBounds.toArray(),
    units: 'm'
});
// Set resolution for each level
let resolutions= [];
for(let i = 0; i < 25; i++) {
    resolutions.push(mapBounds.width() / (512 * Math.pow(2, i - 1)))
}
// Add custom CAD coordinate system
ol.proj.addProjection(cadProjection);

// Create OpenLayers map object
let map = new ol.Map({
    target: 'map', // div id
    view: new ol.View({
        center: mapBounds.center().toArray(),  // Map center
        projection: cadProjection, // Custom CAD coordinate system
        resolutions:resolutions, // Resolution
        zoom: 2// Initial zoom level
    })
});

// Add a tile layer
let layer = new ol.layer.Tile({
    // Add tile data source
    source: new ol.source.TileImage({
        url: svc.rasterTileUrl() // Vijmap service CAD raster tile URL
    })
});
// Add the tile layer to the map
map.addLayer(layer);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# Loading CAD Vector Tiles in OpenLayers


// Add a vector tile layer
let layer = new ol.layer.VectorTile({
    // Add tile data source
    source: new ol.source.VectorTile({
        projection: cadProjection,
        format: new ol.format.MVT(),
        url: svc.vectorTileUrl() // Vijmap service CAD vector tile URL
    }),
    style: createVjMapVectorStyle(ol.style.Style, ol.style.Fill, ol.style.Stroke, ol.style.Circle)
});
// Add the tile layer to the map
map.addLayer(layer);
1
2
3
4
5
6
7
8
9
10
11
12
13

# Selecting and Highlighting CAD Entities in OpenLayers

olselectHighlight.gif


const highlight_ent = async co => {
    vectorSource.clear();
    let res = await svc.pointQueryFeature({
        x: co[0],
        y: co[1],
        zoom: map.getView().getZoom(),
        fields: ""
    }, pt => {
        // Coordinate conversion callback for each queried point
        return mapPrj.fromMercator(pt);// Convert to CAD coordinates
    })
    if (res && res.result && res.result.length > 0) {
        let features = [];
        for (let ent of res.result) {
            if (ent.geom && ent.geom.geometries) {
                let clr = vjmap.entColorToHtmlColor(ent.color);
                for (let g = 0; g < ent.geom.geometries.length; g++) {
                    features.push({
                        type: "Feature",
                        properties: {
                            objectid: ent.objectid + "_" + g,
                            color: clr,
                            alpha: ent.alpha / 255,
                            lineWidth: 1,
                            name: ent.name,
                            isline: ent.isline,
                            layerindex: ent.layerindex
                        },
                        geometry: ent.geom.geometries[g]
                    })
                }
                // Selection hint
                let content = `feature: ${ent.objectid}; layer: ${cadLayers[ent.layerindex].name}; type: ${ent.name}`
                message.info({ content, key: "info", duration: 3});
            }
        }
        geojsonObject.features = features;
        if (geojsonObject.features.length > 0) {
            vectorSource.addFeatures( new ol.format.GeoJSON().readFeatures(geojsonObject, {dataProjection: cadProjection}))
        }
    }
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# Uploading and Opening CAD DWG in OpenLayers

oluploadmap.gif


// Map service object, call Vijmap service to open map and get map metadata
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)

// Upload dwg file
const uploadDwgFile = async file => {
    message.info("Uploading drawing, please wait", 2);
    let res = await svc.uploadMap(file); // Upload map
    // Enter map id
    let mapid = prompt("Please enter map name ID", res.mapid);
    res.mapid = mapid;
    res.mapopenway = vjmap.MapOpenWay.GeomRender; // Geometry render, use vjmap.MapOpenWay.Memory for memory render
    res.isVector = false; // Use raster tiles
    res.style = vjmap.openMapDarkStyle(); // Dark style, use openMapDarkStyle for light
    message.info("Opening drawing, please wait. First open may take tens of seconds to several minutes depending on size", 5);
    let data = await svc.openMap(res); // Open map
    if (data.error) {
        message.error(data.error)
        return;
    }
    openMap(data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# Switching CAD Layers in OpenLayers

olswitchlayer.gif

// Switch layer
const switchLayer = async layers => {
    let res = await svc.cmdSwitchLayers(layers); // Call Vijmap service to switch layers, returns layer id {layerid: "xxxx"}
    let source = layer.getSource();
    // Reset new Vijmap CAD raster tile URL
    source.setUrl(svc.rasterTileUrl());
    // Refresh
    source.refresh();
}
1
2
3
4
5
6
7
8
9

# Switching CAD Maps in OpenLayers

olswitchmap.gif


const switchToMapId = async (mapId)=> {
    let res = await svc.openMap({
        mapid: mapId, // Map ID
        mapopenway: vjmap.MapOpenWay.GeomRender, // Open with geometry data rendering
        style: vjmap.openMapDarkStyle() // Use dark background style when div has dark background
    })
    if (res.error) {
        // If open fails
        message.error(res.error)
        return;
    }
// Get map bounds
    let mapBounds = vjmap.GeoBounds.fromString(res.bounds);

// Custom projection parameters
    let cadProjection = new ol.proj.Projection({
        // extent used to determine zoom levels
        extent: mapBounds.toArray(),
        units: 'm'
    });
// Set resolution for each level
    let resolutions= [];
    for(let i = 0; i < 25; i++) {
        resolutions.push(mapBounds.width() / (512 * Math.pow(2, i - 1)))
    }
// Add custom CAD coordinate system
    ol.proj.addProjection(cadProjection);

// Recreate OpenLayers map object
    map = new ol.Map({
        target: createNewMapDivId(), // div id
        view: new ol.View({
            center: mapBounds.center().toArray(),  // Map center
            projection: cadProjection, // Custom CAD coordinate system
            resolutions:resolutions, // Resolution
            zoom: 2 // Initial zoom level
        })
    });

// Add a tile layer
    let layer = new ol.layer.Tile({
        // Add tile data source
        source: new ol.source.TileImage({
            url: svc.rasterTileUrl() // Vijmap CAD raster tile URL
        })
    });
// Add the tile layer to the map
map.addLayer(layer);

    map.on('click', (e) => message.info({content: `Clicked coordinate: ${JSON.stringify(e.coordinate)}`, key: "info", duration: 3}));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# Dark/Light Theme Switching in OpenLayers

image-20221016210550215

let curIsDarkTheme = true;
const switchToDarkTheme = async () => {
    if (curIsDarkTheme) return;
    curIsDarkTheme = true;
    document.body.style.background = "#022B4F"; // Change background to dark
    await updateStyle(curIsDarkTheme)
}

const switchToLightTheme = async () => {
    if (!curIsDarkTheme) return;
    curIsDarkTheme = false;
    document.body.style.backgroundImage = "linear-gradient(rgba(255, 255, 255, 1), rgba(233,255,255, 1), rgba(233,255,255, 1))"
    await updateStyle(curIsDarkTheme)
}

const updateStyle = async (isDarkTheme) => {
    style.backcolor = isDarkTheme ? 0 : 0xFFFFFF;// Black for dark, white for light
    let res = await svc.cmdUpdateStyle(style);
    let source = layer.getSource();
    // Reset new Vijmap CAD raster tile URL
    source.setUrl(svc.rasterTileUrl());
    // Refresh
    source.refresh();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Custom CAD Map Style in OpenLayers

Customize the map by modifying CAD map backend style data

olcustommapstyle.gif


// Change style
const expressionList = [] ;// Expression array
const updateStyle = async (style) => {
    let res = await svc.cmdUpdateStyle({
        name: "customStyle2",
        backcolor: 0,
        expression: expressionList.join("\n"),
        ...style
    });
    let source = layer.getSource();
    // Reset new Vijmap CAD raster tile URL
    source.setUrl(svc.rasterTileUrl());
    // Refresh
    source.refresh();
}

// For expression syntax and variables, refer to
// Server-side condition query and expression query https://vjmap.com/guide/svrStyleVar.html
// Server-side render expression syntax https://vjmap.com/guide/expr.html

// Modify color: red color.r, green color.g, blue color.b, alpha color.a. If zoom is provided, applies to that level and above
const modifyColor = (color, zoom) => {
    let result = "";
    let z = Number.isInteger(zoom) ? `[${zoom + 1}]` : '';
    if ("r" in color) result += `gOutColorRed${z}:=${color.r};`;
    if ("g" in color) result += `gOutColorGreen${z}:=${color.g};`;
    if ("b" in color) result += `gOutColorBlue${z}:=${color.b};`;
    if ("a" in color) result += `gOutColorAlpha${z}:=${color.a};`;
    return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# CAD Map Composition in OpenLayers

Combine multiple CAD maps with layer visibility, clipping, rotation, and scaling into a new CAD map

image-20221016210950391


// Compose new map: process sys_world, then combine with sys_hello to generate new map file name
let rsp = await svc.composeNewMap([
    {
        mapid: "sys_world", // Map id
        // Set parameters below as needed for layer, extent, coordinate transform
        layers: ["latlon labels","COUNTRY"], // Layer names to display
        //clipbounds: [10201.981489534268, 9040.030491346213, 26501.267379,  4445.465999], // Extent to display
        //fourParameter: [0,0,1,0] // Four-parameter transform for map
    },
    {
        mapid: "sys_hello"
    }
])
if (!rsp.status) {
    message.error(rsp.error)
}
// Result:
/*
{
    "fileid": "pec9c5f73f1d",
    "mapdependencies": "sys_world||sys_hello",
    "mapfrom": "sys_world&&v1&&&&0&&&&&&&&&&00A0&&10||sys_hello&&v1&&&&0&&&&&&&&&&&&2",
    "status": true
}
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# Querying All Text in Map and Drawing Bounds in OpenLayers

olfindtextdrawbounds.gif


// Entity type ID and name mapping
const { entTypeIdMap } = await svc.getConstData();
const getTypeNameById = name => {
    for(let id in entTypeIdMap) {
        if (entTypeIdMap[id] == name) {
            return id
        }
    }
}
const queryTextAndDrawBounds = async () => {
    let queryTextEntTypeId = getTypeNameById("AcDbText"); // Single-line text
    let queryMTextEntTypeId = getTypeNameById("AcDbMText"); // Multi-line text
    let queryAttDefEntTypeId = getTypeNameById("AcDbAttributeDefinition"); // Attribute definition text
    let queryAttEntTypeId = getTypeNameById("AcDbAttribute"); // Attribute text
    let query = await svc.conditionQueryFeature({
        condition: `name='${queryTextEntTypeId}' or name='${queryMTextEntTypeId}' or name='${queryAttDefEntTypeId}' or name='${queryAttEntTypeId}'`, // Only write SQL WHERE clause, see "Server-side condition query and expression query" for fields
        fields: "",
        limit: 100000 // Large value to fetch all. Default 100 if not provided
    }, pt => {
        // Coordinate conversion callback for each queried point
        return mapPrj.fromMercator(pt);// Convert to CAD coordinates
    })
    if (query.error) {
        message.error(query.error)
    } else {
        message.info(`Query matched count: ${query.recordCount}`)

        if (query.recordCount > 0) {
            let features = [];
            for(var i = 0; i < query.recordCount; i++) {
                let bounds = vjmap.getEnvelopBounds(query.result[i].envelop, mapPrj);
                let clr = vjmap.entColorToHtmlColor(query.result[i].color); // Entity color to HTML color

                features.push({
                    type: "Feature",
                    properties: {
                        name: "objectid:" + query.result[i].objectid,
                        color: clr
                    },
                    geometry: {
                        'type': 'Polygon',
                        'coordinates': [
                            bounds.toPointArray(),
                        ],
                    }
                })
            }

            if (!vectorSource) {
                // If no highlight vector layer yet
                addHighLightLayer();
            }
            vectorSource.clear();
            let geojsonObject = {
                'type': 'FeatureCollection',
                'features': features
            }
            // Update vector source data
            vectorSource.addFeatures( new ol.format.GeoJSON().readFeatures(geojsonObject, {dataProjection: cadProjection}))
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

# Drawing in OpenLayers

image-20221016211331840


const source = new ol.source.Vector({wrapX: false});

const vector = new ol.layer.Vector({
    source: source,
});

map.addLayer(vector);

let draw; // global so we can remove it later
function addInteraction(value) {
    map.removeInteraction(draw);
    if (value !== 'None') {
        draw = new ol.interaction.Draw({
            source: source,
            type: value,
        });
        map.addInteraction(draw);
    }
}

addInteraction('Point');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# CAD Map Overlaid on Web Map in OpenLayers [CAD as Base]

olwebCad.gif


// Add Amap base
let gdlayer;
const addGaodeMap = async (isRoadway) => {
    const tileUrl = svc.webMapUrl({
        tileCrs: "gcj02",
        tileUrl:  isRoadway ? [
                "https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}"
            ] :
            /* For imagery */
            [
                "https://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=6&x={x}&y={y}&z={z}",
                "https://webst0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}"
            ],
        tileSize: 256,
        tileRetina: 1,
        tileMaxZoom: 18,
        tileShards: "1,2,3,4",
        tileToken: "",
        tileFlipY: false,
        mapbounds: res.bounds,
        srs: "EPSG:4527",// Get via vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg
        // sys_cad2000 has 6 digits without zone. Need zone offset before transform https://blog.csdn.net/thinkpang/article/details/124172626
        fourParameterBefore: "39000000,0,1,0"
    })


    // Add tile layer
    gdlayer = new ol.layer.Tile({
        // Add tile data source
        source: new ol.source.TileImage({
            url: tileUrl
        })
    });
    gdlayer.setZIndex(-1);
// Add tile layer to map
    map.addLayer(gdlayer);


    // CAD and web coordinate conversion example
    let webCo = await cad2webCoordinate(center, false); // CAD to web
    let cadCo = await web2cadCoordinate(webCo, false); // Web to CAD
    console.log(center, webCo, cadCo)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# Auto Overlay CAD on Web Map in OpenLayers [Web Map as Base]

image-20221016211820059


let cadEpsg = "EPSG:4544";// CAD map EPSG code
// Add CAD WMS layer
let wmsUrl = svc.wmsTileUrl({
    mapid: mapId, // Map id
    layers: layer, // Layer name
    bbox: '', // bbox not needed here
    srs: "EPSG:3857", //
    crs: cadEpsg
})
function getQueryStringArgs(url) {
    let theRequest = {};
    let idx = url.indexOf("?");
    if (idx != -1) {
        let str = url.substr(idx + 1);
        let strs = str.split("&");
        for (let i = 0; i < strs.length; i++) {
            let items = strs[i].split("=");
            theRequest[items[0]] = items[1];
        }
    }
    return theRequest;
}

let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
// CAD to web WGS84 coordinate
const cadToWebCoordinate = async point => {
    let co = await svc.cmdTransform(cadEpsg, "EPSG:4326", point);
    return co[0]
}
// CAD to WGS84 lnglat
let boundsMin = await cadToWebCoordinate(mapBounds.min);
let boundsMax = await cadToWebCoordinate(mapBounds.max);
// WGS84 lnglat to Mercator
boundsMin = vjmap.Projection.lngLat2Mercator(boundsMin);
boundsMax = vjmap.Projection.lngLat2Mercator(boundsMax);

// Add WMS layer in OpenLayers
map.addLayer(new ol.layer.Tile({
    // Extent
    extent: [boundsMin[0], boundsMin[1], boundsMax[0], boundsMax[1]],
    source: new ol.source.TileWMS({
        url: wmsUrl.substr(0, wmsUrl.indexOf("?")),
        params: {...getQueryStringArgs(wmsUrl),'TILED': true}
    }),
}))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# Overlaying CAD on Web Map by Common Points in OpenLayers [Web Map as Base]

olCadFourparam.gif


// Points on CAD
let cadPoints = [
    vjmap.geoPoint([587464448.8435847, 3104003685.208651,]),
    vjmap.geoPoint([587761927.7224838, 3104005967.655292]),
    vjmap.geoPoint([587463688.0280377, 3103796743.3798513]),
    vjmap.geoPoint([587760406.0913897, 3103793700.1176634])
];

// Corresponding points picked on web map (WGS84)
let webPoints = [
    vjmap.geoPoint([116.48476281710168, 39.96200739703454]),
    vjmap.geoPoint([116.48746772021137, 39.96022062215167]),
    vjmap.geoPoint([116.48585059441585, 39.9588451134361]),
    vjmap.geoPoint([116.48317418949145, 39.960515760972356])
]
// Compute four-parameter from point pairs
let epsg3857Points = webPoints.map(w => vjmap.geoPoint(vjmap.Projection.lngLat2Mercator(w)));
let param = vjmap.coordTransfromGetFourParamter(epsg3857Points, cadPoints , false); // Consider rotation
let fourparam = [param.dx, param.dy, param.scale, param.rotate]

// WMS layer URL
const getCadWmsUrl = (transparent) => {
    let wmsUrl = svc.wmsTileUrl({
        mapid: mapId, // Map id
        layers: layer, // Layer name
        bbox: '', // bbox not needed here
        fourParameter: fourparam,
        transparent: transparent,
        backgroundColor: 'rgba(240, 255, 255)' // For non-transparent
    })
    return wmsUrl
}

let mapBounds = vjmap.GeoBounds.fromString(res.bounds);
let cadPrj = new vjmap.GeoProjection(mapBounds);


// CAD to 3857 coordinate
const cadToWebCoordinate = point => {
    // Inverse four-parameter to get web coordinate
    return vjmap.coordTransfromByInvFourParamter(vjmap.geoPoint(point), param)
}
// 3857 to CAD coordinate
const webToCadCoordinate = point => {
    return vjmap.coordTransfromByFourParamter(vjmap.geoPoint(point), param)
}

let wmsLayer;
const addWmsLayer = async (transparent)=> {
    removeWmsLayer();
    let wmsUrl = getCadWmsUrl(transparent);
    wmsLayer = new ol.layer.Tile({
        // Extent
        extent: bounds.toArray(),
        source: new ol.source.TileWMS({
            url: wmsUrl.substr(0, wmsUrl.indexOf("?")),
            params: {...getQueryStringArgs(wmsUrl),'TILED': true}
        }),
    });
    // Add WMS layer to map
    map.addLayer(wmsLayer);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# Finally

Click https://vjmap.com/demo/#/demo/map/openlayers/01olraster (opens new window) to try the above features online

For OpenLayers CAD loading development, see https://vjmap.com/demo/#/demo/map/openlayers/01olraster (opens new window)

For Leaflet CAD loading development, see https://vjmap.com/demo/#/demo/map/leaflet/01leafletraster (opens new window)

For Maptalks CAD loading development, see https://vjmap.com/demo/#/demo/map/maptalks/01maptalksraster (opens new window)

For Vue3 OpenLayers development, see https://github.com/MelihAltintas/vue3-openlayers

For Vue2 OpenLayers development, see https://github.com/ghettovoice/vuelayers