---
title: 地图叠加
source: https://vjmap.com/guide/mapOverlay.html
---

# 地图叠加

## 地图叠加的两种方式

### 合并CAD图形进行叠加

此方式是通过`composeMap`合成图形，在后台根据参数把不同的CAD图合成一个新的组合图形。看查合并图时，查看的是组合后的图形。

```js
// --图形处理合成新的图形--通过将图形列表处理后，再合成一个新的图形
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 组合成新的图,将sys_world图进行一定的处理后，再与sys_hello进行合成，生成新的地图文件名
let rsp = await svc.composeNewMap([
    {
        mapid: "sys_world", // 地图id
        // 下面的参数可以根据实际需要来设置，可以对图层，范围，坐标转换来进行处理
        //layers: ["经纬度标注","COUNTRY"], // 要显示的图层名称列表
        //clipbounds: [10201.981489534268, 9040.030491346213, 26501.267379,  4445.465999], // 要显示的范围
        //fourParameter: [0,0,2,0] // 对地图进行四参数转换计算
    },
    {
        mapid: "sys_hello"
    }
])
if (!rsp.status) {
    message.error(rsp.error)
}
```

### WMS服务组合图形

Web 地图服务 (WMS) 是用于在 Web 上显示 GIS 数据的 最广泛使用和最简单的形式，他能将不同的图形瓦片根据坐标叠加对应到一起。此方式是通过WMS方式，把要叠加显示的图通过WMS叠加到一起，把叠加后的瓦片数据返回给前端。

```js
// 基于底图，通过wms服务叠加其他图形
// 先获取地图元数据来获取图层样式
let cadMapId = "sys_world"; // 要叠加的图形id
let style = await svc.createStyle({
    backcolor: 0xFFFFFF // 浅色主题
}, cadMapId)
let wmsurl = svc.wmsTileUrl({
    mapid: cadMapId,
    version:"v1",
    layers: style.stylename,
    mapbounds: res.bounds,
    fourParameter: "0,0,1,0" // 参数为(平移x,平移y,缩放k,旋转弧度r) 因为坐标与底图一样，所以不用偏移缩放和旋转
})
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 }
    }
);
```

### 两者的优缺点比较如下：

WMS服务组合图形： 叠加显示过程中速度快；叠加图形太多时效率低；后期查询数据时不方便(涉及到多个图查询)

合并CAD图形创建叠加图形： 叠加显示过程速度较慢（在后台要重新创建新图形); 创建完成后和组合图形多少没关系了；后期查询方便


以下面的示例以 `WMS服务叠加` 以例。如需要合并CAD图形处理的方法请参考文档 (图形处理)[guide/processMap]

## CAD图根据坐标直接叠加

相关示例

[WMS叠加栅格图形--通过wms服务，把两个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/27wmsRaster)

[WMS叠加多个图形(坐标系自动变化)--通过wms服务，把多个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/28wmsZMultimap)

[WMS叠加多个图形(坐标系不变)--通过wms服务，把多个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/28wmsZMultimap2)

[WMTS叠加多个图形(坐标系自动变化)--通过wmts服务，把多个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/28wmtsMultimap)


[WMTS叠加多个图形(先有底图坐标系自动变化)--通过wmts服务，把多个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/28wmtszMultimap)

[WMTS叠加多个图形(先有底图坐标系不变)--通过wmts服务，把多个图形通过栅格瓦片叠加到一起](https://vjmap.com/demo/#/demo/map/service/28wmtsztMultimap)

<mapFrameEx>
<pre>
// --WMS叠加栅格图形--通过wms服务，把两个图形通过栅格瓦片叠加到一起
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
let res = await svc.openMap({
    mapid: "sys_hello",
    mapopenway: vjmap.MapOpenWay.GeomRender // 以几何数据渲染方式打开
})
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}));
// 基于上面的底图，通过wms服务叠加其他图形
// 先获取地图元数据来获取图层样式
let cadMapId = "sys_world"; // 要叠加的图形id
let style = await svc.createStyle({
    backcolor: 0xFFFFFF // 浅色主题
}, cadMapId)
let wmsurl = svc.wmsTileUrl({
    mapid: cadMapId,
    version:"v1",
    layers: style.stylename,
    mapbounds: res.bounds,
    fourParameter: "0,0,1,0" // 参数为(平移x,平移y,缩放k,旋转弧度r) 因为坐标与底图一样，所以不用偏移缩放和旋转
})
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图根据四参数校准叠加


示例

[WMS图形排列组合--将多个图通过WMS排列组合](https://vjmap.com/demo/#/demo/map/service/28wmsZZArrange)

<mapFrameEx>
<pre>
// --WMS图形排列组合--将多个图通过WMS排列组合
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
svc.setCurrentMapParam({
    darkMode: true // 由于没有打开过图，所以主动设置黑色模式
})
// 将下面四个图按2行2列的排列方式，组合成新图
let mapIds = ['sys_zp', 'sys_world', 'sys_hello', "sys_topo"];
// 新的地图范围假设设置为0,0,20000,20000
const newMapLength = 20000;
// 先获取四个图的元数据，获取坐标范围
const col = 2;
const cellMapColLength = newMapLength / col;
const cellMapRowLength = newMapLength / Math.ceil( mapIds.length / col);
let allMapParams = [];// 所有的地图参数
let mapFourParam = {};//记录下每个地图的四参数，用于把地图的数据加进新地图中时的坐标转换
let addMarkers = []; // 要增加的每个地图的中心点位置
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; // 原来每个地图的地图范围
    let style = await svc.createStyle({
        backcolor: svc.currentMapParam().darkMode ? 0 : 0xFFFFFF
    }, mapIds[i]); // 原来每个地图的地图样式
    bounds = bounds.scale(+metadata.initViewScale * 1.1); // 比初始视图比例稍大点就可以
    // 四参数，找一个图的左上角各右下角坐标与新的坐标下面的左上角和右下解坐标对应起来，来求解四参数(四参数最少要两个点)
    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];
    // 求的新范围可能不是正方形，得求出这个矩形的中心点的最小正方形
    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();
    // 另外坐标系上拾取的与上面的点一一对应的坐标
    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])
    ]
    // 通过坐标参数求出四参数，不用考虑旋转
    // 因为wms叠加，是根据wms的范围去找图的范围，所以参数应设置成destPoints转srcPoints
    let fourparam = vjmap.coordTransfromGetFourParamter(destPoints, srcPoints, true);
    allMapParams.push({
        mapid: mapIds[i],
        fourParameter: [fourparam.dx, fourparam.dy, fourparam.scale, fourparam.rotate],  // 参数为(平移x,平移y,缩放k,旋转弧度r)
        layer: style.stylename,  // 原来每个地图的地图样式
        mapBounds: mapBounds // 原来每个地图的地图范围
    })
    // 下面记录下每个地图的中心点，然后到时在新的地图中，把之前每个点的中心点绘制出来，用于测试坐标转换
    mapFourParam[mapIds[i]] = fourparam;
    addMarkers.push({
        mapid: [mapIds[i]],
        point: bounds.center()
    })
}
let mapBounds = [0, 0, newMapLength, newMapLength];
let mapExtent = vjmap.GeoBounds.fromArray(mapBounds)
// 建立坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 新建地图对象
let map = new vjmap.Map({
    container: 'map', // container ID
    style: {
        version: svc.styleVersion(),
        glyphs: svc.glyphsUrl(),
        sources: {},
        layers: []
    },// 矢量瓦片样式
    center: [0,0], // 中心点
    zoom: 2,
    renderWorldCopies: false
});
// 地图关联服务对象和坐标系
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() // 全图的范围
    })
    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);
// 在新的坐标系下，把之前每个地图的中心点绘制出来
for(let m of addMarkers) {
    let fourParam = mapFourParam[m.mapid];
    // 需要注意的是，这个地方因为是从原来的地图到wms坐标范围，所以需要反算 通过四参数反算进行坐标转换
    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);// 设置到级别最小
</pre>
</mapFrameEx>
<br/>
<br/>



## 互联网地图与CAD图叠加(为互联网地图为底图)

此场景是以互联网地图为底图（以互联网地图为坐标系），通用调用唯杰地图CAD图的 `WMS` 服务适配互联网地图来进行叠加显示。

如果CAD图有地理坐标系，如本身是2000坐标系的图，则可以自动叠加。

如果CAD图本身没有坐标系，只能通过人为指定三个以上的公共点进行校准，计算出四参数（平移xy、旋转、缩放)来进行叠加。

具体原理可查看 [坐标转换](/guide/coordtransform.html) 文档。


此场景适合于业务数据大部分是互联网地图数据，CAD图只是叠加查看效果。

<mapFrameEx>
<pre>
// --天地图与CAD图叠加[天地图为底图]--天地图与CAD图叠加，以天地图为坐标系
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 根据地图范围建立经纬度投影坐标系
let prj = new vjmap.LnglatProjection();
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器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"],
                // 要使用影像请用这个地址
                // 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 // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
//map.fitMapBounds();
await map.onLoad();
// 先获取地图元数据来获取图层样式
let cadMapId = "sys_cad2000"
let style = await svc.createStyle({
    backcolor: 0xFFFFFF // 浅色主题
}, cadMapId)
let wmsurl = svc.wmsTileUrl({
    mapid: cadMapId,
    version:"v1",
    layers: style.stylename,
    srs: "EPSG:3857", // 底图是天地图坐标系
    crs: "EPSG:4548", // cad图是2000坐标  可通过前两位获取 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg
    // fourParameter: "-39000000,0,1,0"// 此图坐标前面没有加带系，需要加上带系的偏移量，用四参数调节位置
})
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图与互联网地图叠加(为CAD图为底图)

此场景是以CAD为底图（以CAD图为坐标系），通用调用唯杰地图提供的互联网地图的`WMS`服务来适配CAD地图来进行叠加显示。

如果CAD图有地理坐标系，如本身是2000坐标系的图，则可以自动叠加。

如果CAD图本身没有坐标系，只能通过人为指定三个以上的公共点进行校准，计算出四参数（平移xy、旋转、缩放)来进行叠加。

具体原理可查看 [坐标转换](/guide/coordtransform.html) 文档。


此场景适合于业务数据大部分是CAD图数据，互联网地图只是叠加查看效果。


<mapFrameEx>
<pre>
// --CAD图与互联网图叠加--CAD图与互联网图叠加显示，以CAD图为坐标系
// 假设我们希望CAD全图显示是对应级别为8级,这样地图缩小后，互联网地图还会显示
let initZoom = 8;
let style = {
    backcolor: 0, // div为深色背景颜色时，这里也传深色背景样式
    clipbounds: Math.pow(2, initZoom) // 只传值，不传范围，表示之前的范围放大多少倍
}
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
let res = await svc.openMap({
    mapid: "sys_cad2000",
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    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}));
// 如果x是8位，可通过前两位获取 如 vjmap.transform.getEpsgParam(vjmap.transform.EpsgCrsTypes.CGCS2000, 39).epsg
// 如果x是6位，是通过下面此工具 https://vjmap.com/demo/#/demo/map/web/03webzgetepsg，把地图移动至cad图位置的省市，点击获取当前地图中心经纬度， 计算EPSG，找一个与 CAD图距离相近的EPSG
let epsg = "EPSG:4509" // cad图的epsg
// 增加高德地图底图
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}"
            ] :
            /* 如果用影像 */
            [
                "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
    // 把这个图层放至所有图层的最下面
    map.moveLayer('web-gaode-layer', layers[0].id)
    // cad坐标与高德坐标相互转换示例
    let webCo = await cad2webCoordinate(center, false); // cad转高德
    let cadCo = await web2cadCoordinate(webCo, false); // 高德转cad
    console.log(center, webCo, cadCo)
}
// 增加天地图图底图
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}" :
                /* 如果用影像 */"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", // 有可能有一个token会导致请求过多而返回失败，可以多写几个，失败时会尝试后面的token
            "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
    // 把这个图层放至所有图层的最下面
    map.moveLayer('web-tdt-layer', layers[0].id)
    // cad坐标与天地图坐标相互转换示例
    let webCo = await cad2webCoordinate(center, true); // cad转天地图
    let cadCo = await web2cadCoordinate(webCo, true); // 天地图转cad
    console.log(center, webCo, cadCo)
}
const removeMapLayer = ()=> {
    // 删除source及source下面的所有图层
    map.removeSourceEx(['web-gaode-source', 'web-tdt-source'])
}
// cad转web坐标，isWgs84是否为wgs84 4326坐标，如天地图；否的话为gcj02火星坐标,如高德地图
const cad2webCoordinate = async (pt, isWgs84) => {
    // 通过坐标转换把转成wgs84 4326坐标
    let res = await svc.cmdTransform(epsg,"EPSG:4326",pt); // 如果不想调服务转，也可以在前端用proj4库
    // 如果为火星坐标，还需要从4326转火星
    let webPt = res[0];// 返回的第1条结果
    if (isWgs84 === false) {
        webPt = vjmap.transform.convert(webPt, vjmap.transform.CRSTypes.EPSG4326, vjmap.transform.CRSTypes.AMap)
    }
    return webPt;
}
// web转cad坐标，isWgs84是否为wgs84 4326坐标，如天地图；否的话为gcj02火星坐标,如高德地图
const web2cadCoordinate = async (pt, isWgs84) => {
    // (上面计算过程的逆过程), 如果为火星坐标，还需要从火星转4326，再通过坐标转换把wgs84 4326转成cad图坐标
    if (isWgs84 === false) {
        pt = vjmap.transform.convert(pt, vjmap.transform.CRSTypes.AMap, vjmap.transform.CRSTypes.EPSG4326);
    }
    // 通过坐标转换把cad epsg转成wgs84 4326坐标
    let res = await svc.cmdTransform("EPSG:4326",epsg,vjmap.geoPoint(pt)); // 如果不想调服务转，也可以在前端用proj4库
    let cadPt = res[0];// 返回的第1条结果
    return cadPt
}
map.on("zoomend", async ()=> {
    let center = map.fromLngLat(map.getCenter());// 得到cad地图地理中心坐标点
    let gcj02Web = await cad2webCoordinate(center, false);// 转高德坐标
    let wgs84Web = await cad2webCoordinate(center, true);// 转天地图坐标
    let content = `当前地图中心点坐标： CAD坐标:${center.x.toFixed(4)},${center.y.toFixed(4)}; 高德坐标:${gcj02Web[0].toFixed(4)},${gcj02Web[1].toFixed(4)}; 天地图坐标:${wgs84Web[0].toFixed(4)},${wgs84Web[1].toFixed(4)}`;
    console.log({ content, key: "info", duration: 3});
})
addGaodeMap(true);
</pre>
</mapFrameEx>


::: tip 提示
更多互联网地图叠加显示的介绍请查看 [互联网地图](/guide/webmap.html) 文档。
:::



