> **本文回答**：几何坐标 / 渲染经纬度 / 真实经纬度 / 墨卡托 / 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` 建议用：

```js
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 坐标），不是经纬度：

```js
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](projection-api.md) 长度类 API 表——注意 `prj.toMeter(1)` 的"米"是渲染尺度不是真实地面米。

## 六、拿到一个坐标不知道它在哪个空间？（判断速查）

| 特征 | 可能是 |
|---|---|
| \|x\|≤180 且 \|y\|≤85 | 经纬度（渲染或真实，看底图与上下文） |
| \|x\|、\|y\| 在 ±20037508 附近 | Web 墨卡托米 |
| X=6~8位、Y=7位的大数 | CAD 投影坐标（EPSG 分带，见 [cad-epsg](cad-epsg.md)） |
| 其他大数 | 局部 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"` 字符串。

## 八、四条关键总则（高度概括）

1. **渲染用经纬度**：addSource / Marker / Popup / Symbol / 一切 GeoJSON 图层吃渲染经纬度。**例外**：CircleFill/CircleEdge/Ellipse*/MarkerCluster/DivOverlay/SvgOverlay 内部帮你 `toLngLat`，要直接传 CAD 坐标——详见 [overlay-coords](overlay-coords.md)。
2. **几何用 CAD**：几何计算（相交、插值、偏移、旋转）在 CAD 坐标做。
3. **保存至后台用 CAD**：下次加载 `map.toLngLat` 再画。
4. **CAD 底图时渲染经纬度没有地理含义**：千万不要把它传给 GCJ02/百度 API。

## 九、代码对照

### CAD 底图

```js
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);
```

### 互联网底图

```js
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); // 直接写真实经纬度
```
