> **本文回答**：vjmap 每个覆盖物（Marker/Popup/Symbol/GeoJSON/CircleFill/MarkerCluster/DivOverlay/SvgOverlay）的坐标参数到底该传哪个空间——CAD 坐标还是渲染经纬度。

## 一、判断原则

**同名"坐标"参数，有的要渲染经纬度，有的要 CAD 坐标**——区别只看这个对象内部有没有调 `map.toLngLat`。

> 记忆法：画"几何形状"的（圆/椭圆/任意 div/svg 区域）内部生成图形坐标再统一转——传 CAD；摆"位置"的（Marker/Popup/Symbol 点）和通用 GeoJSON 管线——传渲染经纬度。
> 互联网底图下两列等价（`toLngLat` 是恒等），但混传的习惯带到 CAD 底图就会出 bug。

## 二、传「渲染经纬度」（调用方自己 `map.toLngLat` 转换）

| 对象 | 入口 | 说明 |
|---|---|---|
| `vjmap.Marker` | `setLngLat(lngLat)` | 引擎原生，只吃渲染经纬度；传 CAD（y>90）**直接抛 `Invalid LngLat latitude value`**，不是静默错位 |
| `vjmap.Popup` | `setLngLat(lngLat)` | 同上 |
| `vjmap.Text` | `setLngLat(lngLat)` | 内部包了一层 Marker |
| `vjmap.Symbol` | `data` | `createPointGeoJson/createLineGeoJson` → geojson source，**零转换**（data 原样进 source） |
| `vjmap.Circle` | `data` | 点符号图层（不是几何圆！半径是像素/样式），不做转换 |
| `vjmap.Polygon` | `data` | 不做转换 |
| `vjmap.Polyline` | `data` | 不做转换 |
| `vjmap.PolylineArrow` | `path` | 直接当 LineString 坐标，不做转换 |
| `vjmap.FillExtrusion` / `vjmap.Heatmap` | `data` | 不做转换 |
| `map.addSource/addLayer` 的 geojson | `data` | 底层入口，一律渲染经纬度 |

## 三、传「CAD 坐标」（内部帮你 `map.toLngLat`）

| 对象 | 入口 | 内部行为 |
|---|---|---|
| `vjmap.CircleFill` | `center` + `radius` | `map.toLngLat(getCirclePolygonCoordinates(center, radius,…))`——**几何在 CAD 空间生成再整体转**，`radius` 是 CAD 单位 |
| `vjmap.CircleEdge` | 同上 | 同上（只描边）。⚠️ **当前版本运行时损坏**：`updateData()` 调用未打包进产物的 `getCirclePolylineCoordinates`（min.js 中仅定义 `getCirclePolygonCoordinates`），`addTo` 必抛 `getCirclePolylineCoordinates is not defined`。需描边圆请改用 `CircleFill` 配透明填充 + 边色，或 `Polyline` 自算 |
| `vjmap.EllipseFill` / `EllipseEdge` | `center` + 轴半径 | 同上 |
| `vjmap.MarkerCluster` | `data[].point` | 内部 `map.toLngLat(point)` → project 到屏幕；d.ts 原文注释"传入坐标为CAD地理坐标" |
| `vjmap.DivOverlay` | `bounds` 四角 | 内部 `toLngLat` → `projectEx` |
| `vjmap.SvgOverlay` | 元素 `{{x,y}}` / `bounds` | 坐标是地理坐标（CAD 空间），内部转像素；`{{r}}` 长度占位符也是 **CAD 单位** |

`updateData/setCenter` 与 `addTo` 走同一条转换路径，约定一致。

### MarkerCluster 的 `createMarker` 是必填回调

缺了直接抛 `this.options.createMarker is not a function`；且回调里要**自己**把 CAD 点转成渲染经纬度：

```js
new vjmap.MarkerCluster({
  data: [{ point: cadPt, properties:{...} }],                          // CAD 坐标
  createMarker: d => new vjmap.Marker().setLngLat(map.toLngLat(d.point))  // 自己转
}).addTo(map);
```

## 四、快速代码对照（CAD 底图）

```js
const cadPt = [4512345.6, 3456789.0];          // CAD 几何坐标
const ll = map.toLngLat(cadPt);                // 渲染经纬度（无地理意义）

new vjmap.Marker().setLngLat(ll).addTo(map);                      // ✅ 转后传
new vjmap.Popup().setLngLat(ll).setText('x').addTo(map);          // ✅
new vjmap.Symbol({ data: ll, iconImage: 'xx' }).addTo(map);       // ✅
map.addSource('s', { type:'geojson', data: map.toLngLat(fc) });   // ✅ FeatureCollection 整体转（走 cloneDeep，不改原对象）
// ⚠️ 但 map.toLngLat(单个 Feature / 裸 Geometry) 是【原地改原对象】并返回同一对象，
//    重复调用 = 二次转换。详见 projection-api.md「原地修改陷阱」

new vjmap.CircleFill({ center: cadPt, radius: 500 }).addTo(map);  // ✅ 传 CAD，半径是 CAD 单位
new vjmap.MarkerCluster({ data: [{ point: cadPt }], createMarker: d => ... }).addTo(map); // ✅ 传 CAD

new vjmap.Marker().setLngLat(cadPt).addTo(map);                   // ❌ y>90 直接抛 Invalid LngLat
new vjmap.CircleFill({ center: ll, radius: 500 }).addTo(map);     // ❌ 二次转换——会生成几何落到 [-28644,-90] 完全出界
```
