concepts
本文回答:几何坐标 / 渲染经纬度 / 真实经纬度 / 墨卡托 / UCS / WCS 的区别与边界。
一、三种坐标层次
| 名称 | 数值范围 | 何时出现 | 备注 |
|---|---|---|---|
| CAD 几何坐标 | 任意(可达 10^8) | DWG 中实际点、业务保存值 | 单位米,笛卡尔直角坐标 |
| 渲染经纬度 | [-180, 180] × [-85.0511, 85.0511] |
map.toLngLat 输出、所有 addSource/Marker/Popup 的坐标 |
WebGL 渲染引擎使用的绘制坐标 |
| 真实经纬度 | [-180, 180] × [-85.0511, 85.0511] |
互联网底图实际地理坐标、LnglatProjection |
仅当底图是互联网地图时,渲染经纬度 == 真实经纬度 |
二、为什么要有渲染经纬度?
CAD 图的坐标常常很大(上亿),直接交给 WebGL 渲染会浮点精度塌陷;而底层渲染器的世界观是经纬度 (-180..180, -85..85)。vjmap 在 GeoProjection 里建立一张"线性仿射"映射:
GeoBounds 左下角↔(-180, -85.0511)(Web 墨卡托纬度极限)GeoBounds 右上角↔(180, 85.0511)
这样渲染器只处理一个合理量级的坐标,CAD 几何可以任意大。注意这是线性映射但整体是非线性(经纬度投影自身非线性),所以几何计算请在 CAD 侧做。
示例(sys_cad2000,CGCS2000 图):
map.toLngLat(bounds.center)恰好得[0,0],整张图只落在几度的小范围内——CAD 底图的"渲染经纬度"就是归一化渲染坐标,看上去像真经纬度实则没有任何地理意义。
三、互联网底图的差别
当底图是互联网地图(OSM、天地图、高德……),prj 建议用:
const prj = new vjmap.LnglatProjection();
它的 toLngLat([lng, lat]) 直接返回 [lng, lat]。此时"渲染经纬度"就是真实经纬度——所有 GIS 数据(Marker、GeoJSON)直接用经纬度填就行。
四、UCS 与 WCS
CAD 里的 UCS(User Coordinate System)是用户自定义局部坐标;WCS(World Coordinate System)是图的全局坐标。
- vjmap 获取的几何坐标都是 WCS。
map.hasUcs()判断当前图是否存在 UCS 定义。map.wcsToUcs(wcsPt)/map.ucsToWcs(ucsPt)互转。
当需求是"图纸上标注的坐标"时(比如工程师手算的点号与坐标),通常要转 UCS 再显示。
五、墨卡托坐标
vjmap 内部用球面墨卡托投影(EPSG:3857,范围 ±20037508 米)绘制。⚠️ Map 上没有 toMercator/fromMercator——它们在 Projection 上,且输入是投影源空间坐标(CAD 底图 = CAD 坐标),不是经纬度:
const prj = map.getMapProjection();
prj.toMercator(cadPt); // CAD -> 拉伸墨卡托米(线性拉伸到 ±20037508,非真实地理墨卡托)
prj.fromMercator(mktXY); // 反向
// 真实 WGS84 <-> EPSG:3857 用静态方法(与地图投影无关):
vjmap.Projection.lngLat2Mercator([lng, lat]);
vjmap.Projection.mercator2LngLat([mx, my]);
mapext 里 CoordMapper.cadToMercator / mercatorToCad 走同一通路(委托 prj.toMercator/fromMercator)。用于需要做屏幕像素计算 / 单位米长度估算的场景。
长度换算见 projection-api 长度类 API 表——注意 prj.toMeter(1) 的"米"是渲染尺度不是真实地面米。
六、拿到一个坐标不知道它在哪个空间?(判断速查)
| 特征 | 可能是 |
|---|---|
| |x|≤180 且 |y|≤85 | 经纬度(渲染或真实,看底图与上下文) |
| |x|、|y| 在 ±20037508 附近 | Web 墨卡托米 |
| X=6~8位、Y=7位的大数 | CAD 投影坐标(EPSG 分带,见 cad-epsg) |
| 其他大数 | 局部 CAD 坐标(无地理参考,需四参数) |
七、四参数(平面相似变换)
无标准坐标系的 CAD 图靠四参数 dx, dy, scale, rotate(rotate 是弧度):
x' = dx + scale·cos(r)·x − scale·sin(r)·y
y' = dy + scale·sin(r)·x + scale·cos(r)·y
- 解算:
vjmap.coordTransfromGetFourParamter(srcPts, destPts, isSetRotateZero, isConsiderPointOrder?)——第 3 参必填(true=只解平移+缩放不旋转);至少 2 对公共点、建议 4~10 对分布图四角。返回值{dx,dy,scale,rotate},ByFourParamter/ByInvFourParamter均返回GeoPoint(用.x/.y取值)。 - 正算
coordTransfromByFourParamter(pt, param);反算coordTransfromByInvFourParamter。 - 服务端接口里
fourParameter(wmsTileUrl)与fourParameterBefore/After(webMapUrl)都是"dx,dy,scale,rotate"字符串。
八、四条关键总则(高度概括)
- 渲染用经纬度:addSource / Marker / Popup / Symbol / 一切 GeoJSON 图层吃渲染经纬度。例外:CircleFill/CircleEdge/Ellipse*/MarkerCluster/DivOverlay/SvgOverlay 内部帮你
toLngLat,要直接传 CAD 坐标——详见 overlay-coords。 - 几何用 CAD:几何计算(相交、插值、偏移、旋转)在 CAD 坐标做。
- 保存至后台用 CAD:下次加载
map.toLngLat再画。 - CAD 底图时渲染经纬度没有地理含义:千万不要把它传给 GCJ02/百度 API。
九、代码对照
CAD 底图
const prj = new vjmap.GeoProjection(res.bounds);
const map = new vjmap.Map({ center: prj.toLngLat(prj.getMapExtent().center()), ... });
map.attach(svc, prj);
// 保存到后台:
const cadJson = map.fromLngLat(draw.getAll());
await fetch("/api/save", { body: JSON.stringify(cadJson) });
// 加载:
const lngLatJson = map.toLngLat(cadJson);
draw.set(lngLatJson);
互联网底图
const prj = new vjmap.LnglatProjection();
const map = new vjmap.Map({ center: prj.toLngLat([116.391, 39.907]), ... });
new vjmap.Marker().setLngLat([116.391, 39.907]).addTo(map); // 直接写真实经纬度