> **本文回答**：CAD 图叠到天地图 / 高德 / 百度 / OSM 时坐标偏移的原因、自动叠加、四参数叠加。

## 一、叠加的两种场景

1. **CAD 为底图**：互联网瓦片作为 WMS 图层被 vjmap 后端转换后适配到 CAD 坐标。
2. **互联网为底图**：CAD 通过 vjmap 的 WMS 服务（`svc.wmsTileUrl`）适配到互联网瓦片。

无论哪种，都要解决"CAD 坐标系" ↔ "互联网地图坐标系"的转换。

## 二、两类 CAD 图

| 类型 | 标志 | 处理 |
|---|---|---|
| 有 EPSG（国家 2000 / 西安 80 / 北京 54 等） | 坐标是 6 位或 8 位 | **自动叠加**：通过 `svc.webMapUrl({srs: "EPSG:xxx"})` / `svc.wmsTileUrl({srs, crs})` |
| 没有坐标系（商铺平面图等局部图） | 坐标通常是 [0..1000] 这种小值 | **四参数叠加**：人为指定 2+ 对公共点 |

## 三、自动叠加（有 EPSG 的 CAD 图）

```js
// 以 CAD 为底图，叠加高德
const tileUrl = svc.webMapUrl({
  tileCrs: "gcj02",                // 高德用火星坐标
  tileUrl: [
    "https://webrd0{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",
  tileFlipY: false,
  mapbounds: res.bounds,
  srs: "EPSG:4527",                // 2000 坐标系 3 度带 39 带
  // 若 X 只有 6 位没带号，要加带号补齐（39 带：前缀 39000000）
  fourParameterBefore: "39000000,0,1,0"
});
map.addSource("basemap-src", { type: "raster", tiles: [tileUrl], tileSize: 256 });
map.addLayer({ id: "basemap", type: "raster", source: "basemap-src" });
map.moveLayer("basemap", map.getStyle().layers[0].id);    // 放到最底
```

- 天地图：`tileCrs: "wgs84"`。
- 其他 XYZ：`tileCrs: "wgs84"` / `"gcj02"` 按实际情况。
- ⚠️ **`tileCrs` 在 d.ts 里只声明 `"gcj02" | "wgs84"`**（注释"bd09 暂不支持"），但服务端瓦片代理已有 `tileCrs:"bd09"` 分支（官方示例未使用）；TS 工程需断言类型，生产使用前请自行验证。稳妥叠百度的方式仍是 §七"互联网为底图 + `wmsTileUrl`"方向，配 `webMapType: "BD09LL"`。

`webMapUrl` 完整参数（Service.d.ts 签名核实）：`tileCrs?`、`tileUrl: string|string[]`（必填）、`tileSize?`、`tileRetina?`、`tileMaxZoom?`、`tileShards?`、`tileToken?: string|string[]`、`tileFlipY?`、`mapbounds?`（GeoBounds 也可，模板序列化时走 `toString()` → `"x1,y1,x2,y2"`）、`fourParameterBefore?`、`fourParameterAfter?`、`srs?`。

## 四、四参数叠加（无坐标系的 CAD 图）

```js
// 1) CAD 上拾取若干点
const cadPoints = [
  vjmap.geoPoint([587464448.8435846, 3104003685.208661]),
  vjmap.geoPoint([587761927.7224838, 3104005967.655292]),
  vjmap.geoPoint([587463688.0280377, 3103796743.3798513]),
  vjmap.geoPoint([587760406.0913897, 3103793700.1176634])
];

// 2) 在百度地图上拾取对应点（百度坐标）
const webPoints = [
  vjmap.geoPoint([116.49738632967344, 39.969244215079016]),
  vjmap.geoPoint([116.50000171663382, 39.96753915494187]),
  vjmap.geoPoint([116.49843968869915, 39.96613594214925]),
  vjmap.geoPoint([116.49581440721418, 39.967790723887404])
];

// 3) 百度 -> EPSG:3857
const epsg3857Points = webPoints.map(w =>
  vjmap.geoPoint(
    vjmap.transform.convert(w, vjmap.transform.CRSTypes.BD09, vjmap.transform.CRSTypes.EPSG3857)
  )
);

// 4) 计算四参数
const param = vjmap.coordTransfromGetFourParamter(epsg3857Points, cadPoints, false);
const fourparam = [param.dx, param.dy, param.scale, param.rotate];

// 5) 构造 WMS URL（bbox 不传时默认自动带 {bbox-epsg-3857} 模板占位，无需手写）
const wmsUrl = svc.wmsTileUrl({
  mapid: "yourMapId",
  layers: "",
  srs: "EPSG:3857",
  fourParameter: fourparam,
  transparent: true
});
```

## 四之二、⚠️ `srs`/`crs`/`tileCrs` 在三个 API 里语义各不相同

| API | `srs` | `crs` | `tileCrs` |
|---|---|---|---|
| `svc.wmsTileUrl`（CAD 叠到互联网底图） | **底图**坐标系 `EPSG:3857` | **CAD 图**的 EPSG | — |
| `svc.webMapUrl`（互联网瓦片衬到 CAD 底图下） | **CAD 图**的 EPSG | — | **瓦片商**坐标系（`gcj02`/`wgs84`；`bd09` 仅服务端实现、d.ts 未声明） |
| `svc.cmdTransform`（点坐标换算） | **源**坐标系 | **目标**坐标系 | — |

同一个 EPSG（如 `EPSG:4509`）在 `wmsTileUrl` 里写给 `crs`、在 `webMapUrl` 里写给 `srs`、在 `cmdTransform` 里作源/目标——参数名相同语义不同，极易搞反。

`wmsTileUrl` 底图是高德/腾讯时加 `webMapType: "GCJ02"`——服务端出图顺带做 WGS84→GCJ02 加偏；天地图/OSM 不加。

## 四之三、后台查询结果叠到互联网底图

在互联网底图上查某张 CAD 图：`result[i].geom` 转出的是**该 CAD 图的渲染经纬度**（对这张互联网底图没有意义）。正确链路：

```js
// 1) 查询要 CAD 坐标（toMapCoordinate:true 时 .geom 不生成，读 .geojson）
//    查非当前图：geom/layer 须显式给，否则沿用当前图的打开方式与样式名（静默 {} 或 not support query type!）
const res = await svc.conditionQueryFeature({ mapid, condition:"...",
  geom:true, layer: await svc.getStyleLayerName(mapid),
  toMapCoordinate:true, includegeom:true, realgeom:true });
const cadGj = JSON.parse(res.result[0].geojson);              // CAD 坐标

// 2) CAD → WGS84（cmdTransform 逐点；GeoJSON 要拆点转完再拼回，它只收 GeoPoint）
//    无 EPSG 的局部图：cmdTransform("EPSG:4326","EPSG:4326", pt, "dx,dy,s,r") 走四参数

// 3) WGS84 → 底图系：高德底图 convert(WGS84→GCJ02)，天地图直接用
map.addSource('hl', { type:'geojson', data: gj });
```

## 五、常见偏移原因清单

| 现象 | 原因 | 修复 |
|---|---|---|
| 整体平移几百米 | 用 WGS84 给了高德/百度 | 转 GCJ02/BD09，或调整 `tileCrs` |
| 百度图叠加小区平面图对不上 | 局部图无 EPSG，没做四参数 | 走 [四参数叠加] |
| 2000 坐标 CAD 叠到 OSM 整体偏 | 前缀带号没加或 EPSG 错 | 确认 3 度带/6 度带，`fourParameterBefore: "39000000,0,1,0"` |
| 叠加某些区域准确、另一区域偏 | 投影非线性误差 | 增加公共点数量（≥ 3 或 4）；或用 proj4 做更准确的非线性变换 |
| 高层级（zoom > 18）才偏 | tile 精度或 retina 配置错 | 设置 `tileMaxZoom` / `tileRetina` 正确 |
| 查询结果在互联网底图上"完全看不到" | `.geom` 是 CAD 图的渲染经纬度不是底图经纬度 | 见 §四之三：`toMapCoordinate:true` + `cmdTransform` 链路 |

## 六、WMS 叠加原理

`svc.wmsTileUrl` 会让 vjmap 服务端根据 `srs/crs/fourParameter` 算出 bbox → 从 CAD 空间生成 tile。前端只要按标准 raster source 方式配置。

参考示例：[百度地图与 CAD 叠加](https://vjmap.com/guide/baidumap.html)、[高德叠加](https://vjmap.com/guide/gaodemap.html)。

## 七、以"互联网底图 + CAD 叠加"为基准

```js
const svc = new vjmap.Service(url, token);
const prj = new vjmap.LnglatProjection();
const map = new vjmap.Map({
  style: { /* 天地图/高德 tile */ },
  center: [116.391, 39.907],
  zoom: 11
});
map.attach(svc, prj);
await map.onLoad();

// 加 CAD WMS 图层
const wmsUrl = svc.wmsTileUrl({
  mapid: "yourCadMapId",
  layers: "",
  srs: "EPSG:3857",
  transparent: true
  // ⚠️ backgroundColor 只在 transparent:false 时才生效（SDK 源码：不透明才拼该参数），
  //    透明叠加时传了也会被忽略
});
map.addSource("cad-src", { type: "raster", tiles: [wmsUrl], tileSize: 256 });
// ⚠️ wmsTileUrl 输出默认已含 &bbox={bbox-epsg-3857}，不要再手工拼 &BBOX=（会重复参数）
map.addLayer({ id: "cad-layer", type: "raster", source: "cad-src" });
```
