> **本文回答**：WGS84 / GCJ02 / BD09 / EPSG:3857 / 国家 2000 / 任意 EPSG 之间的相互转换。

## 一、主流坐标系速查

| 坐标系 | 用在哪 | vjmap 枚举（真实值 / 可用别名，源码核实） |
|---|---|---|
| WGS84 (EPSG:4326) | 国外地图、GPS、OSM、国际矢量底图、天地图 | `WGS84` / `WGS1984`、`EPSG4326` |
| GCJ02（火星） | 高德、谷歌中国、腾讯地图 | `GCJ02` / `AMap` |
| BD09（百度） | 百度地图 | `BD09` / `BD09LL`、`Baidu`、`BMap` |
| BD09MC（百度墨卡托米） | 百度地图内部 | `BD09MC` / `BD09Meter` |
| EPSG:3857（Web 墨卡托） | vjmap 底层、大多数 tile 服务 | `EPSG3857` / `EPSG900913`、`EPSG102100`、`WebMercator`、`WM` |
| 国家 2000 (CGCS2000) | 国内 CAD 图、测绘局产出 | 一组编码 `EPSG:4490~4554` 等 |
| 西安 80 / 北京 54 | 国内早期测绘 | `EPSG:2401~2442` 等 |

`CRSTypes` 枚举值只有 5 个真实值（上表第二列 `/` 前），其余写法都是别名。

## 二、前端一把梭 `vjmap.transform.convert`

```js
// 单点
const gcj = [116.397442, 39.909188];
const wgs = vjmap.transform.convert(
  gcj,
  vjmap.transform.CRSTypes.GCJ02,
  vjmap.transform.CRSTypes.WGS84
);
// 结果：[116.3912, 39.907] 左右

// 整棵 GeoJSON
const data = {
  type: "FeatureCollection",
  features: [ /* ... */ ]
};
const newData = vjmap.transform.convert(
  data,
  vjmap.transform.CRSTypes.GCJ02,
  vjmap.transform.CRSTypes.WGS84
);
```

这个 API 同时支持两种调用方式：

```js
// 1) (data, fromCrs, toCrs) 指定坐标系互转
vjmap.transform.convert(data, CRSTypes.GCJ02, CRSTypes.WGS84);

// 2) (data, callback) 用户自行决定每个点怎么变
vjmap.transform.convert(data, (pt) => {
  return [pt[0] + 10, pt[1] + 10];
});
```

⚠️ 两个行为注意：
- **GeoJSON 输入是原地改坐标并返回同一对象**（点/点数组才返回新值）——要保留原始数据先自己深拷贝。
- `crsFrom === crsTo` 时 `convert` **直接原样返回**（不转换也不 clone）——别用它做"顺便拷贝一份"。

## 三、自定义 EPSG 之间转换：`svc.cmdTransform`（服务端）

适合国家 2000 / 北京 54 等国内投影坐标转经纬度：

```js
// 把 4527 坐标转成 wgs84 4326
const res = await svc.cmdTransform("EPSG:4527", "EPSG:4326", vjmap.geoPoint([x, y]));
const lngLat = res[0];   // res 是数组（批量转）
```

反向同理：`await svc.cmdTransform("EPSG:4326", "EPSG:4527", vjmap.geoPoint([lng, lat]))`。注意 points 必须 `vjmap.geoPoint(...)` 包装，传裸 `[x,y]` 数组会序列化成 `undefined,undefined`；且 `srs`=源坐标系、`crs`=目标坐标系（与 `wmsTileUrl` 的语义相反），返回 `[[x,y],...]` 数组。

## 四、前端调 proj4（离线）

```bash
npm i proj4
```

```js
import proj4 from "proj4";

proj4.defs([
  ["EPSG:4549", "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs"]
]);

const lngLat = proj4("EPSG:4549", "EPSG:4326", [cadX, cadY]);
```

## 五、四参数转换（没有 EPSG 的 CAD 图）

当 CAD 图没有标准坐标系，需要给 2 个以上的公共点：

```js
const cadPts = [vjmap.geoPoint([x1, y1]), vjmap.geoPoint([x2, y2]), ...];
const destPts = [vjmap.geoPoint([a1, b1]), vjmap.geoPoint([a2, b2]), ...];

const fourparam = vjmap.coordTransfromGetFourParamter(cadPts, destPts, false);
//  => { dx, dy, scale, rotate }

// 应用
const mapped = vjmap.coordTransfromByFourParamter(vjmap.geoPoint([x, y]), fourparam);

// 反向
const inv = vjmap.coordTransfromByInvFourParamter(vjmap.geoPoint([x, y]), fourparam);
```

## 六、典型 CAD → 火星坐标链路

国家 2000 CAD（EPSG:4527） → WGS84 → GCJ02 高德：

```js
async function cadToAMap(cadPt) {
  const [wgs] = await svc.cmdTransform("EPSG:4527", "EPSG:4326", vjmap.geoPoint(cadPt));
  const gcj = vjmap.transform.convert(
    wgs,
    vjmap.transform.CRSTypes.EPSG4326,
    vjmap.transform.CRSTypes.AMap
  );
  return gcj;
}
```

反向（用户在高德上点了一个点，还原回 CAD）：

```js
async function aMapToCad(gcj) {
  const wgs = vjmap.transform.convert(gcj, vjmap.transform.CRSTypes.AMap, vjmap.transform.CRSTypes.EPSG4326);
  const [cad] = await svc.cmdTransform("EPSG:4326", "EPSG:4527", vjmap.geoPoint(wgs));
  return cad;
}
```

## 七、EPSG 辅助 API

```js
// 通过 CAD 坐标 X 的前两位 + 坐标系族，获取 EPSG 码
vjmap.transform.getEpsgParam(
  vjmap.transform.EpsgCrsTypes.CGCS2000,
  39                                // 前两位；39 表示 3 度带号 39
).epsg;                              // 返回 "EPSG:4527"（带号变体，x_0=39500000）

// 反查：按坐标范围查候选 EPSG（X 6 位不带号时会返回 20+ 个候选）
vjmap.transform.getEpsgCode([x, y], crsType?, is3DegreeBelt?);
```

坐标系族（`EpsgCrsTypes` 枚举，大小写敏感）：`Wgs84`、`Merc3857`、`CGCS2000`、`Xian80`、`Beijing54`、`NewBeijing54`。

⚠️ **带号 vs 不带号变体**：`getEpsgParam` **只产带号变体**（4527 要求 X=39xxxxxx）。图纸 X 是 6 位不带号（≈50xxxx）时要选不带号变体——如同中央经线 117°E 的 `EPSG:4509`（x_0=500000）。两者 proj4 参数只差 x_0，选错 X 偏约 39,000,000（大到"完全看不见"，易误判成没叠上）。详见 [cad-epsg](cad-epsg.md)。

## 八、常见坑

1. **把 GCJ02 当 WGS84 直接扔进 OSM** → 偏移约 300 米。国内必做加密转换。
2. **同一点用 `convert` 转了两次火星** → 偏移翻倍。记录每个变量目前所在的 CRS。
3. **cmdTransform 没 `await`** → 拿到的是 Promise 对象。
4. **`cmdTransform` 第三参传了 `[lng, lat]` 数字数组** → 内部按 `p.x + "," + p.y` 拼请求，实际发出 `"undefined,undefined"`，换算结果恒定不变。必须 `vjmap.geoPoint([lng, lat])`。鼠标跟随不要打这个 HTTP，用本地高斯正算。
5. **proj4 没 defs EPSG** → 抛 `Non-numerical or undefined coefficient`。必须先 `proj4.defs(...)` 注册。
