---
title: Map Overlay
source: https://vjmap.com/en/guide/mapOverlay.html
---

# Map Overlay

## Two Ways to Overlay Maps

### Merge CAD Graphics for Overlay

This method uses `composeMap` to merge graphics. The server composes different CAD maps into a new combined graphic based on parameters. When viewing the merged map, you see the combined result.

```js
// --Graphic processing to compose new graphics--Process graphic list, then compose into a new graphic
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// Compose into new map: process sys_world, then merge with sys_hello to generate new map file
let rsp = await svc.composeNewMap([
    {
        mapid: "sys_world", // Map id
        // Parameters below can be set as needed for layer, bounds, coordinate transformation
        //layers: ["latlon labels","COUNTRY"], // Layer name list to display
        //clipbounds: [10201.981489534268, 9040.030491346213, 26501.267379,  4445.465999], // Bounds to display
        //fourParameter: [0,0,2,0] // Four-parameter transformation for the map
    },
    {
        mapid: "sys_hello"
    }
])
if (!rsp.status) {
    message.error(rsp.error)
}
```

### WMS Service for Combining Graphics

Web Map Service (WMS) is the most widely used and simplest way to display GIS data on the web. It overlays different graphic tiles by coordinates. This method uses WMS to overlay maps and returns the combined tile data to the frontend.

```js
// Overlay other graphics on base map via WMS service
// Get map metadata first for layer style
let cadMapId = "sys_world"; // Overlay map id
let style = await svc.createStyle({
    backcolor: 0xFFFFFF // Light theme
}, cadMapId)
let wmsurl = svc.wmsTileUrl({
    mapid: cadMapId,
    version:"v1",
    layers: style.stylename,
    mapbounds: res.bounds,
    fourParameter: "0,0,1,0" // (x offset, y offset, scale k, rotation r) Same coords as base map, so no offset/scale/rotation
})
map.addSource('wms-test-source', {
    'type': 'raster',
    'tiles': [
        wmsurl
    ],
    'tileSize': 256
});
map.addLayer({
        'id': 'wms-test-layer',
        'type': 'raster',
        'source': 'wms-test-source',
        'paint': { "raster-opacity": 1 }
    }
);
```

### Comparison

**WMS service:** Fast during overlay; less efficient with many overlays; less convenient for later data queries (multiple maps involved).

**Merge CAD graphics:** Slower during overlay (server creates new graphic); after creation, performance is independent of number of maps; convenient for later queries.


The example below uses `WMS overlay`. For CAD graphic merge, see [Graphic Processing](/en/guide/processMap).

## CAD Map Overlay by Coordinates

Related examples:

[WMS overlay raster graphics—overlay two maps via WMS raster tiles](https://vjmap.com/demo/#/demo/map/service/27wmsRaster)

[WMS overlay multiple maps (auto coordinate system)—overlay multiple maps via WMS raster tiles](https://vjmap.com/demo/#/demo/map/service/28wmsZMultimap)

[WMS overlay multiple maps (fixed coordinate system)—overlay multiple maps via WMS raster tiles](https://vjmap.com/demo/#/demo/map/service/28wmsZMultimap2)

[WMTS overlay multiple maps (auto coordinate system)—overlay multiple maps via WMTS raster tiles](https://vjmap.com/demo/#/demo/map/service/28wmtsMultimap)


[WMTS overlay multiple maps (base map first, auto coordinate system)—overlay multiple maps via WMTS raster tiles](https://vjmap.com/demo/#/demo/map/service/28wmtszMultimap)

[WMTS overlay multiple maps (base map first, fixed coordinate system)—overlay multiple maps via WMTS raster tiles](https://vjmap.com/demo/#/demo/map/service/28wmtsztMultimap)

<mapFrameEx>
<pre>
// --WMS overlay raster graphics--Overlay two maps via WMS raster tiles
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
let res = await svc.openMap({
    mapid: "sys_hello",
    mapopenway: vjmap.MapOpenWay.GeomRender // Open with geometry render mode
})
if (res.error) {
    message.error(res.error)
}
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
let prj = new vjmap.GeoProjection(mapExtent);
let center = mapExtent.center();
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(),
    center: prj.toLngLat(center),
    zoom: 2,
    renderWorldCopies: false
});
map.attach(svc, prj);
map.fitMapBounds();
await map.onLoad();
map.addControl(new vjmap.NavigationControl());
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
// Overlay other graphics on base map via WMS service
// Get map metadata first for layer style
let cadMapId = "sys_world"; // Overlay map id
let style = await svc.createStyle({
    backcolor: 0xFFFFFF // Light theme
}, cadMapId)
let wmsurl = svc.wmsTileUrl({
    mapid: cadMapId,
    version:"v1",
    layers: style.stylename,
    mapbounds: res.bounds,
    fourParameter: "0,0,1,0" // (x offset, y offset, scale k, rotation r) Same coords as base map, so no offset/scale/rotation
})
map.addSource('wms-test-source', {
    'type': 'raster',
    'tiles': [
        wmsurl
    ],
    'tileSize': 256
});
map.addLayer({
        'id': 'wms-test-layer',
        'type': 'raster',
        'source': 'wms-test-source',
        'paint': { "raster-opacity": 1 }
    }
);
</pre>
</mapFrameEx>
<br/>
<br/>



## CAD Map Overlay with Four-Parameter Calibration


Example

[WMS graphic arrangement—arrange multiple maps via WMS](https://vjmap.com/demo/#/demo/map/service/28wmsZZArrange)

<mapFrameEx>
<pre>
// --WMS graphic arrangement--Arrange multiple maps via WMS
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
svc.setCurrentMapParam({
    darkMode: true // No map opened yet, so set dark mode manually
})
// Arrange the four maps below in 2x2 grid
let mapIds = ['sys_zp', 'sys_world', 'sys_hello', "sys_topo"];
// Assume new map bounds 0,0,20000,20000
const newMapLength = 20000;
// Get metadata for all four maps to get coordinate bounds
const col = 2;
const cellMapColLength = newMapLength / col;
const cellMapRowLength = newMapLength / Math.ceil( mapIds.length / col);
let allMapParams = [];// All map parameters
let mapFourParam = {};// Four-parameter for each map for coordinate transform when adding to new map
let addMarkers = []; // Center point of each map to add
for(let i = 0; i < mapIds.length; i++) {
    let metadata = await svc.metadata(mapIds[i]);
    let bounds = vjmap.GeoBounds.fromString(metadata.bounds);
    let mapBounds = bounds; // Original map bounds
    let style = await svc.createStyle({
        backcolor: svc.currentMapParam().darkMode ? 0 : 0xFFFFFF
    }, mapIds[i]); // Original map style
    bounds = bounds.scale(+metadata.initViewScale * 1.1); // Slightly larger than initial view scale
    // Four-parameter: map top-left and bottom-right of source to new top-left and bottom-right to solve four-parameter (min 2 points)
    let srcPoints = [
        bounds.min,
        bounds.max
    ];
    const curRow = Math.floor(i / col);
    const curCol = i % col;
    let destMin = [cellMapColLength * curCol, newMapLength - cellMapRowLength * (curRow + 1)];
    let destMax = [cellMapColLength * (curCol + 1), newMapLength - cellMapRowLength * curRow];
    // New bounds may not be square; get minimum square centered on rectangle
    let newBounds = new vjmap.GeoBounds(vjmap.geoPoint(destMin), vjmap.geoPoint(destMax));
    const c = newBounds.center();
    let w = newBounds.width() < newBounds.height() ? newBounds.width() : newBounds.height();
    // Corresponding points in target coordinate system
    let destPoints = [
        vjmap.geoPoint([c.x - w / 2.0, c.y - w / 2.0]),
        vjmap.geoPoint([c.x + w / 2.0, c.y + w / 2.0])
    ]
    // Solve four-parameter from coordinates; no rotation
    // Four-parameter: destPoints -> srcPoints (WMS uses WMS bounds to find map bounds)
    let fourparam = vjmap.coordTransfromGetFourParamter(destPoints, srcPoints, true);
    allMapParams.push({
        mapid: mapIds[i],
        fourParameter: [fourparam.dx, fourparam.dy, fourparam.scale, fourparam.rotate],  // (x offset, y offset, scale k, rotation r)
        layer: style.stylename,  // Original map style
        mapBounds: mapBounds // Original map bounds
    })
    // Record center of each map for drawing test markers on new map
    mapFourParam[mapIds[i]] = fourparam;
    addMarkers.push({
        mapid: [mapIds[i]],
        point: bounds.center()
    })
}
let mapBounds = [0, 0, newMapLength, newMapLength];
let mapExtent = vjmap.GeoBounds.fromArray(mapBounds)
// Build coordinate system
let prj = new vjmap.GeoProjection(mapExtent);
// Create map object
let map = new vjmap.Map({
    container: 'map', // container ID
    style: {
        version: svc.styleVersion(),
        glyphs: svc.glyphsUrl(),
        sources: {},
        layers: []
    },// Vector tile style
    center: [0,0], // Center
    zoom: 2,
    renderWorldCopies: false
});
// Attach service and projection
map.attach(svc, prj);
await map.onLoad();
const addWmsLayer = async (maps) => {
    let mapIds = [];
    let layers = [];
    let fourParameters = [];
    for(let a = 0; a < maps.length; a++) {
        let m = maps[a];
        mapIds.push(m.mapid);
        fourParameters.push(m.fourParameter);
        layers.push(m.layer);
    }
    let wmsurl = svc.wmsTileUrl({
        mapid: mapIds,
        layers: layers,
        fourParameter: fourParameters,
        mapbounds: mapExtent.toString() // Full map bounds
    })
    map.addSource('wms-maps-source', {
        'type': 'raster',
        'tiles': [
            wmsurl
        ],
        'tileSize': 256
    });
    map.addLayer({
            'id': 'wms-maps-layer',
            'type': 'raster',
            'source': 'wms-maps-source',
            'paint': { "raster-opacity": 1 }
        }
    );
}
addWmsLayer(allMapParams);
// Draw center of each original map on new coordinate system
for(let m of addMarkers) {
    let fourParam = mapFourParam[m.mapid];
    // Inverse four-parameter for coordinate transform (original map -> WMS bounds)
    let newPoint = vjmap.coordTransfromByInvFourParamter(m.point, fourParam);
    new vjmap.Marker({color: vjmap.randomColor()})
        .setLngLat(map.toLngLat(newPoint))
        .addTo(map);
}
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
map.setZoom(0);// Set to minimum zoom
</pre>
</mapFrameEx>
<br/>
<br/>



## Internet Map + CAD Map Overlay (Internet Map as Base)

This scenario uses an internet map as the base (internet map as coordinate system). The map uses the CAD map’s `WMS` service to adapt to the internet map for overlay.

If the CAD map has a geographic coordinate system (e.g. CGCS2000), it can be overlaid automatically.

If the CAD map has no coordinate system, you must specify three or more common points for calibration and compute the four-parameter (x/y offset, rotation, scale) for overlay.

See [Coordinate transformation](/en/guide/coordtransform.html) for details.


This scenario fits when most data is from the internet map and the CAD map is only for reference.


<mapFrameEx>
<pre>
// --Tianditu + CAD overlay [Tianditu as base]--Tianditu + CAD overlay, Tianditu as coordinate system
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// Build lnglat projection from map bounds
let prj = new vjmap.LnglatProjection();
// Map object
let map = new vjmap.Map({
    container: 'map', // DIV container ID
    style: {
        version: svc.styleVersion(),
        sources: {
            tdt1: {
                type: 'raster',
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
                // Use this URL for imagery
                // tiles: ["https://t3.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"]
            },
            tdt2: {
                type: 'raster',
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
            }
        },
        layers: [{
            id: 'tdt1',
            type: 'raster',
            source: 'tdt1',
        },{
            id: 'tdt2',
            type: 'raster',
            source: 'tdt2',
        }]
    },
    center: prj.toLngLat([117.21242940465396, 39.196783504641644]),
    zoom: 15,
    pitch: 0,
    renderWorldCopies: false // No multi-world copy
});
// Attach service and projection
map.attach(svc, prj);
// Fit map bounds to full extent
//map.fitMapBounds();
await map.onLoad();
// Get map metadata for layer style
let cadMapId = "sys_cad2000"
let style = await svc.createStyle({
    backcolor: 0xFFFFFF // Light theme
}, cadMapId)
let wmsurl = svc.wmsTileUrl({
    mapid: cadMapId,
    version:"v1",
    layers: style.stylename,
    srs: "EPSG:3857", // Base map is Tianditu coordinate system
    crs: "EPSG:4548", // CAD map is CGCS2000 coordinate // Get via first two digits vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg
    // fourParameter: "-39000000,0,1,0"// Add zone offset if needed; use four-parameter to adjust position
})
map.addSource('wms-test-source', {
    'type': 'raster',
    'tiles': [
        wmsurl
    ],
    'tileSize': 256
});
map.addLayer({
        'id': 'wms-test-layer',
        'type': 'raster',
        'source': 'wms-test-source',
        'paint': { "raster-opacity": 1 }
    }
);
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
</pre>
</mapFrameEx>


## CAD Map + Internet Map Overlay (CAD Map as Base)

This scenario uses the CAD map as the base (CAD map as coordinate system). The map uses the internet map’s `WMS` service to adapt to the CAD map for overlay.

If the CAD map has a geographic coordinate system (e.g. CGCS2000), it can be overlaid automatically.

If the CAD map has no coordinate system, you must specify three or more common points for calibration and compute the four-parameter (x/y offset, rotation, scale) for overlay.

See [Coordinate transformation](/en/guide/coordtransform.html) for details.


This scenario fits when most data is from the CAD map and the internet map is only for reference.


<mapFrameEx>
<pre>
// --CAD + internet map overlay--CAD + internet map overlay, CAD as coordinate system
// Assume CAD full extent corresponds to zoom level 8; internet map still visible when zoomed out
let initZoom = 8;
let style = {
    backcolor: 0, // Use dark style when div has dark background
    clipbounds: Math.pow(2, initZoom) // Number only = zoom factor; no bounds
}
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
let res = await svc.openMap({
    mapid: "sys_cad2000",
    mapopenway: vjmap.MapOpenWay.GeomRender, // Open with geometry render mode
    style:  style
})
if (res.error) {
    consoloe.error(res.error)
}
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
let prj = new vjmap.GeoProjection(mapExtent);
let center = mapExtent.center();
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(),
    center: prj.toLngLat(center),
    zoom: 10,
    renderWorldCopies: false
});
map.attach(svc, prj);
await map.onLoad();
map.addControl(new vjmap.NavigationControl());
map.addControl(new vjmap.MousePositionControl({showZoom: true}));
// If x is 8 digits, get via first two digits e.g. vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg
// If x is 6 digits, use https://vjmap.com/demo/#/demo/map/web/03webzgetepsg: move map to CAD region, click to get center lnglat, compute EPSG, pick EPSG close to CAD map
let epsg = "EPSG:4509" // CAD map EPSG
// Add Amap base map
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
    })
    map.addSource('web-gaode-source', {
        'type': 'raster',
        'tiles': [
            tileUrl
        ],
        'tileSize': 256
    });
    map.addLayer({
            'id': 'web-gaode-layer',
            'type': 'raster',
            'source': 'web-gaode-source',
            'paint': { "raster-opacity": 1 }
        }
    );
    let layers = map.getStyle().layers
    // Move layer to bottom
    map.moveLayer('web-gaode-layer', layers[0].id)
    // CAD <-> Amap coordinate conversion example
    let webCo = await cad2webCoordinate(center, false); // CAD to Amap
    let cadCo = await web2cadCoordinate(webCo, false); // Amap to CAD
    console.log(center, webCo, cadCo)
}
// Add Tianditu base map
const addTiandituMap = async (isRoadway) => {
    const tileUrl = svc.webMapUrl({
        tileCrs: "wgs84",
        tileUrl: [
            isRoadway ? "https://t{s}.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk={t}" :
                /* For imagery */"https://t{s}.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk={t}",
            "https://t{s}.tianditu.gov.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk={t}"
        ],
        tileSize: 256,
        tileRetina: 1,
        tileMaxZoom: 18,
        tileShards: "1,2,3,4,5,6",
        tileToken: [
            "6d53378dc5f7dbef8d84ffdd2b54139b", // Multiple tokens in case one fails; next tries on failure
            "69eb2fa0de3b2a668f1ef603a3f8bc73"
        ],
        tileFlipY: false,
        mapbounds: res.bounds,
        srs: epsg
    })
    map.addSource('web-tdt-source', {
        'type': 'raster',
        'tiles': [
            tileUrl
        ],
        'tileSize': 256
    });
    map.addLayer({
            'id': 'web-tdt-layer',
            'type': 'raster',
            'source': 'web-tdt-source',
            'paint': { "raster-opacity": 1 }
        }
    );
    let layers = map.getStyle().layers
    // Move layer to bottom
    map.moveLayer('web-tdt-layer', layers[0].id)
    // CAD <-> Tianditu coordinate conversion example
    let webCo = await cad2webCoordinate(center, true); // CAD to Tianditu
    let cadCo = await web2cadCoordinate(webCo, true); // Tianditu to CAD
    console.log(center, webCo, cadCo)
}
const removeMapLayer = ()=> {
    // Remove source and all layers under it
    map.removeSourceEx(['web-gaode-source', 'web-tdt-source'])
}
// CAD to web coordinate; isWgs84: true for WGS84 4326 (e.g. Tianditu); false for GCJ02 (e.g. Amap)
const cad2webCoordinate = async (pt, isWgs84) => {
    // Transform to WGS84 4326
    let res = await svc.cmdTransform(epsg,"EPSG:4326",pt); // Or use proj4 on frontend
    // If GCJ02, convert from 4326 to GCJ02
    let webPt = res[0];// First result
    if (isWgs84 === false) {
        webPt = vjmap.transform.convert(webPt, vjmap.transform.CRSTypes.EPSG4326, vjmap.transform.CRSTypes.AMap)
    }
    return webPt;
}
// Web to CAD coordinate; isWgs84: true for WGS84 4326 (e.g. Tianditu); false for GCJ02 (e.g. Amap)
const web2cadCoordinate = async (pt, isWgs84) => {
    // Inverse of above; if GCJ02, convert to 4326 first, then to CAD
    if (isWgs84 === false) {
        pt = vjmap.transform.convert(pt, vjmap.transform.CRSTypes.AMap, vjmap.transform.CRSTypes.EPSG4326);
    }
    // Transform CAD EPSG to WGS84 4326
    let res = await svc.cmdTransform("EPSG:4326",epsg,vjmap.geoPoint(pt)); // Or use proj4 on frontend
    let cadPt = res[0];// First result
    return cadPt
}
map.on("zoomend", async ()=> {
    let center = map.fromLngLat(map.getCenter());// CAD map center
    let gcj02Web = await cad2webCoordinate(center, false);// To Amap
    let wgs84Web = await cad2webCoordinate(center, true);// To Tianditu
    let content = `Current map center: CAD:${center.x.toFixed(4)},${center.y.toFixed(4)}; Amap:${gcj02Web[0].toFixed(4)},${gcj02Web[1].toFixed(4)}; Tianditu:${wgs84Web[0].toFixed(4)},${wgs84Web[1].toFixed(4)}`;
    console.log({ content, key: "info", duration: 3});
})
addGaodeMap(true);
</pre>
</mapFrameEx>


::: tip
For more on internet map overlay, see [Internet map](/en/guide/webmap.html).
:::

