---
title: 坐标转换
source: https://vjmap.com/guide/coordtransform.html
---

# 坐标转换

## 坐标转换原因

CAD二维图形世界坐标系是`笛卡尔直角坐标系`，图形的范围是指绘制出的图形所占的范围。

如用户在坐标 [1000,1000]和[2000,2000]绘制了一条直线，那么图形的范围 [左上角x, 左下角y,右上角x,右下角y] 就是[1000,1000,2000,2000]。

但有时候用户制图时坐标用的是大地坐标，坐标数值会比较大，如在坐标[600000000,30000000],[620000000,50000000]绘制了一条直线，那图形的范围就是[600000000,30000000,700000000,70000000]。

同时在一些基于互联网地图开发时，经纬度数值都很小。

为了解决大坐标渲染的问题和统一互联网底图和CAD底图坐标的需要，我们渲染底层采用了经纬度进行渲染，绘制时需要把实际坐标转换成经纬度进行绘制。经纬度范围为 经度范围为(-180,180), 纬度范围为(-85,85) 




## 坐标转换关系


|名称|说明|
|:---|:---|
|几何坐标|CAD图中实际的坐标|
|经纬度坐标|经纬度坐标(在渲染时必须要转成经纬度坐标)|
|墨卡托坐标|投影坐标||


<div align="left">
  <img :src="$withBase('/assets/images/coordtransform.png')" style="transform:scale(0.8)"/>
</div>

### 如果底图为CAD图为坐标系时

转换关系如下图

<div align="left">
  <img :src="$withBase('https://vjmap.com/blogimages/202409262050910.png')" style="transform:scale(0.8)"/>
</div>


通过调用 `toLngLat` 把CAD图转成渲染用的`经纬度坐标`, 如会把 [CAD范围左下角x1,左下角y1]转成 经纬度[-180,-85]。

<br/>

所以**这时候所转成的经纬度只是底层渲染用的经纬度坐标数值，不是实际互联网地图中所用到的经纬度坐标，这是两个不同的概念**。

### 如果底图是互联网地图如天地图时

转换关系如下图

<div align="left">
  <img :src="$withBase('https://vjmap.com/blogimages/202409262058353.png')" style="transform:scale(0.8)"/>
</div>



这时候因为互联网地图的范围和渲染用到的经纬度范围完全一样，
所以**这时候所转成的经纬度就是实际互联网地图中所用到的经纬度坐标，这是同一个概念**。



### 有地理坐标系的CAD图与互联网地图经纬度转换

如果CAD图有地理坐标系系，如是2000坐标系时，需要转换为互联网地图经纬度时。这时候需要用第三方坐标转换库如`proj4`进行转换。转换原理如下图：

<div align="left">
  <img :src="$withBase('https://vjmap.com/blogimages/202409262114548.png')" style="transform:scale(0.8)"/>
</div>


### 在CAD中使用UCS坐标绘制的图坐标与WCS坐标转换
在云端管理平台打开图后，右侧属性栏中 `ucsorg` 的值不为零的话表示有ucs，或者用`map`的`hasUcs`方法来判断是否有ucs坐标。

在唯杰地图获取的是CAD的`WCS`坐标。

`WCS坐标`转`UCS坐标`用 `map.wcsToUcs`

`UCS坐标`转`WCS坐标`用 `map.ucsToWcs`

## 总结

::: tip 总结1
唯杰地图渲染用的经纬度坐标只有在底图为互联网底图(如天地图、高德地图)时，才是真正的经纬度坐标。<br/>
否则为CAD底图时，经纬度坐标值只是一个用于底层渲染的坐标值，没有实际的地理意义，只是为了解决大坐标渲染的问题和统一互联网底图和CAD底图坐标的需要。<br/>
如果CAD底图是有真正地理坐标系，如北京54坐标、西安80坐标、2000坐标系时，需要转换成wgs84经纬度时，可以用SDK提供的vjmap.transform.convert方法或者自己用第三方库如proj4进行转换，具体可以参考演示示例中的`互联网地图`
或者使用四参数对不同的坐标系进行坐标转换[如何把另外一个坐标系的数据叠加到当前cad图上](https://vjmap.com/guide/faq.html#%E5%A6%82%E4%BD%95%E6%8A%8A%E5%8F%A6%E5%A4%96%E4%B8%80%E4%B8%AA%E5%9D%90%E6%A0%87%E7%B3%BB%E7%9A%84%E6%95%B0%E6%8D%AE%E5%8F%A0%E5%8A%A0%E5%88%B0%E5%BD%93%E5%89%8Dcad%E5%9B%BE%E4%B8%8A)
:::



::: tip 总结2
如果map关联了[投影对象](/guide/mapProjection.html) `map.attach(svc, prj)` 则<br/>
map.toLngLat 等价于  prj.toLngLat<br/>
map.fromLngLat 等价于 prj.fromLngLat<br/>

CAD实际几何坐标 转 底层渲染的经纬度坐标 一律请调用  toLngLat (与底层绘制有关的，都得调用toLngLat转经纬度)<br/>
由底层渲染的经纬度坐标 转  CAD实际几何坐标  一律请调用 fromLngLat (得到CAD图上面的实际几何坐标，都得调用fromLngLat转CAD实际几何坐标)<br/>
:::


::: tip 总结3
因为CAD图坐标转渲染经纬度时，转换过程是非线性的，在进行`几何计算`如相交、插值等请使用 `CAD几何坐标`， `渲染时`再转成 `经纬度坐标`
:::

::: tip 总结4
因为CAD图坐标转渲染经纬度时，和CAD当前绘制范围有关。绘图范围变化所转换的渲染经纬度的值也会变化。所以如果要保存坐标至后台下次打开再加载显示的时候，一定要保存CAD图坐标， 不要保存经纬度。因为如果CAD图版本更新了，如果绘图范围有变化的话，加载经纬度坐标就会和原图对应不上了。而加载CAD图坐标能完全对应上。

例如 [自定义高亮区域](https://vjmap.com/demo/#/demo/map/service/29customzhoverregion) 这个示例

当点击保存数据时会获取绘图对象的渲染经纬度，通过调用 `map.fromLngLat` 转为CAD几何坐标再保存至后台
```js
// 保存数据至后台过程
// 获取获取绘图对象的渲染经纬度
let lngLatJson = draw.getAll();
// 渲染经纬度转成CAD几何坐标
let cadJson = map.fromLngLat(lngLatJson);
```

而在加载后台数据显示至地图中，则需要获取 `map.toLngLat` 把CAD几何坐标转为绘图对象的渲染经纬度进行绘制。
```js
// 前端加载后台数据进行显示
let cadJson = {......}; // 后台获取至的CAD几何坐标
// CAD几何坐标转成渲染经纬度
let lngLatJson = map.toLngLat(cadJson);
// 设置绘图对象坐标
draw.set(lngLatJson)
```

:::

## 如何对CAD图进行坐标转换处理
1、获取要转换的四参数
在CAD图上拾取`三组及以上`的对应点坐标(公共点尽量分布在图的四周)
```js
let pts = [
    {
        cadX: 1, // CAD图上对应点一的坐标X
        cadY: 1, // CAD图上对应点一的坐标Y
        destX: 101, // 对应点一目标图上的坐标x
        destY: 101, // 对应点一目标图上的坐标y
    },
    {
        cadX: 1, // CAD图上对应点二的坐标X
        cadY: 5, // CAD图上对应点二的坐标Y
        destX: 101, // 对应点二目标图上的坐标x
        destY: 105, // 对应点二目标图上的坐标y
    },
    {
        cadX: 5, // CAD图上对应点三的坐标X
        cadY: 5, // CAD图上对应点二的坐标Y
        destX: 105, // 对应点三目标图上的坐标x
        destY: 105, // 对应点三目标图上的坐标y
    }
]
let cadPoints = [];
let destPoints = [];
for(let n = 0; n < pts.length; n++) {
    cadPoints.push(vjmap.geoPoint([pts[n].cadX, pts[n].cadY]))
    destPoints.push(vjmap.geoPoint([pts[n].destX, pts[n].destY]))
}

// 通过坐标参数求出四参数
let fourparam = vjmap.coordTransfromGetFourParamter(cadPoints, destPoints, false);
console.log([fourparam.dx, fourparam.dy, fourparam.scale, fourparam.rotate])

// 计算误差
for(let i = 0; i< cadPoints.length; i++) {
    const destPt = vjmap.coordTransfromByFourParamter(cadPoints[i], fourparam);
    console.log("原始点：" + cadPoints[i], "计算结果：" + destPt, "误差：" + destPoints[i].distanceTo(destPt))
}
```

例如`把ucs的图转成wcs坐标的图`
用如下代码可自动获取原图wcs到ucs转换的四参数
```js
let cadPoints = [];
let destPoints = []
// 获取地图范围的四个点算出对应的ucs坐标，做为公共点
let mapExtent = map.getMapExtent()
let pts = mapExtent.toPointArray()
for (let n = 0; n < pts.length; n++) {
    let wcs = vjmap.geoPoint(pts[n]);
    let ucs = map.wcsToUcs(wcs);
    cadPoints.push(wcs)
    destPoints.push(ucs);
}
// 通过坐标参数求出四参数
let fourparam = vjmap.coordTransfromGetFourParamter(cadPoints, destPoints, false);
console.log([fourparam.dx, fourparam.dy, fourparam.scale, fourparam.rotate])
```
2、在云端管理平台里面打开此图，点击`更多功能`里面的`在线处理图形`

3、在`在线处理图形`中`直接输入四参数格式`输入上面获取到`fourparam.dx, fourparam.dy, fourparam.scale, fourparam.rotate`值

4、点击确定，选择新建图形。即可。

## 坐标控件

CAD为底图坐标系
<mapFrameEx>
<pre>
// --打开地图[栅格瓦片]--以栅格瓦片方式打开已上传的CAD的DWG格式的图
// js代码
// 新建地图服务对象，传入服务地址和token
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 打开地图
let res = await svc.openMap({
    mapid: env.exampleMapId, // 地图ID,(请确保此ID已存在，可上传新图形新建ID)
    mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开
    style: vjmap.openMapDarkStyle() // div为深色背景颜色时，这里也传深色背景样式
})
if (res.error) {
    message.error(res.error)
}
// 获取地图的范围
let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
// 建立坐标系
let prj = new vjmap.GeoProjection(mapExtent);
// 新建地图对象
let map = new vjmap.Map({
    container: 'map', // container ID
    style: svc.rasterStyle(), // 栅格瓦片样式
    center: prj.toLngLat(mapExtent.center()), // 中心点
    zoom: 2,
    renderWorldCopies: false
});
// 地图关联服务对象和坐标系
map.attach(svc, prj);
// 使地图全部可见
map.fitMapBounds();
await map.onLoad();
let mousePositionControl = new vjmap.MousePositionControl({
    showLatLng: true
});
map.addControl(mousePositionControl, "top-left");
</pre>
</mapFrameEx>
<br/>
<br/>



互联网地图为底图坐标系
<mapFrameEx>
<pre>
// --天地图地图--
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(),
        glyphs: svc.glyphsUrl(),
        sources: {
            tdt1: {
                type: 'raster',
                // 影像
                //tiles: ["https://t3.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=7baeffb96bf61861b302d0f963cfda66"],
                // 道路
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=vec_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([116.3912, 39.9073]),
    zoom: 10,
    pitch: 0,
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
//map.fitMapBounds();
await map.onLoad();
let mousePositionControl = new vjmap.MousePositionControl({
    showLatLng: true
});
map.addControl(mousePositionControl, "top-left");
</pre>
</mapFrameEx>
<br/>
<br/>


