# CadMapOverlay — 把 vjcad 叠到 vjmap 地图

> 文档：`vjcad/doc/tutorial/map-overlay.md`、`vjcad/doc/ui/map-overlay.md`
> 示例：`vjcad/example/basic/05mapoverlay.js`

> UI 角度的 `CadMapOverlay` API 已在 `ui/map-overlay.md` 列出；本页聚焦 **实战流程**：vjmap 加载 → 地图初始化 → overlay attach → 添加实体 → 选择联动 → 控制面板。

## 1. 完整初始化流程（CDN 加载 vjmap）

```js
const { CadMapOverlay, LineEnt, CircleEnt, TextEnt, message } = vjcad;

// 1) 动态加载 vjmap CSS + JS
const loadVjmap = () => new Promise((res, rej) => {
    if (window.vjmap) return res(window.vjmap);
    const css = document.createElement('link');
    css.rel = 'stylesheet';
    css.href = 'https://vjmap.com/demo/js/vjmap/vjmap.min.css';
    document.head.appendChild(css);
    const s = document.createElement('script');
    s.src = 'https://vjmap.com/demo/js/vjmap/vjmap.min.js';
    s.onload = () => window.vjmap ? res(window.vjmap) : rej(new Error('vjmap 未挂载'));
    s.onerror = () => rej(new Error('vjmap 加载失败'));
    document.head.appendChild(s);
});
const vjmap = await loadVjmap();
```

```js
// 2) 创建 vjmap 服务并打开图
const svc = new vjmap.Service(env.serviceUrl, env.accessToken);
const res = await svc.openMap({
    mapid: 'sys_zp',
    mapopenway: vjmap.MapOpenWay.GeomRender,
    style: vjmap.openMapDarkStyle()
});
if (res.error) throw new Error(res.error);

const mapExtent = vjmap.GeoBounds.fromString(res.bounds);
const prj       = new vjmap.GeoProjection(mapExtent);

// 3) 创建 vjmap 地图
const map = new vjmap.Map({
    container: 'map',
    style: svc.vectorStyle(),
    center: prj.toLngLat(mapExtent.center()),
    zoom: 1, pitch: 0,
    renderWorldCopies: false
});
map.attach(svc, prj);
await map.onLoad();
map.addControl(new vjmap.NavigationControl(), 'top-right');
```

```js
// 4) 创建 CadMapOverlay 并挂到 map 上
const cadOverlay = new CadMapOverlay({
    bounds: mapExtent,
    serviceUrl: env.serviceUrl,
    accessToken: env.accessToken,
    themeMode: 0,                    // 0 深色 / 1 浅色
    enableSelection: true,
    smoothAnimation: true,           // CSS transform 平滑过渡
    onSelectionChanged: sel => {
        if (!sel.length) return message.info('选择已清空');
        message.info(`选中 ${sel.length} 个实体`);
    }
});
await cadOverlay.addTo(map);
```

至此 CAD 叠到地图上。`cadOverlay` 可作为 `MainView` 替身使用。

## 2. 在 CAD 范围内添加实体

```js
const [minX, minY, maxX, maxY] = cadOverlay.getCadBounds();
const avgSize = Math.min(maxX - minX, maxY - minY) / 20;

const entities = [];
for (let i = 0; i < 5; i++) {
    const line = new LineEnt(
        [minX + Math.random() * (maxX - minX), minY + Math.random() * (maxY - minY)],
        [minX + Math.random() * (maxX - minX), minY + Math.random() * (maxY - minY)]
    );
    line.setDefaults();
    line.color = (i % 7) + 1;
    entities.push(line);
}
for (let i = 0; i < 5; i++) {
    const cx = minX + Math.random() * (maxX - minX);
    const cy = minY + Math.random() * (maxY - minY);
    const c  = new CircleEnt([cx, cy], avgSize * (0.5 + Math.random()));
    c.setDefaults();
    c.color = (i % 7) + 1;
    entities.push(c);
}
cadOverlay.addEntities(entities);
cadOverlay.zoomToExtents();
```

`getCadBounds()` 返回的是 **CAD 内部坐标空间** 的范围；不是经纬度。

## 3. 选择联动 + 控制面板

```js
const ctrl = document.createElement('div');
ctrl.style.cssText = 'position:absolute;top:10px;left:10px;background:#fff;padding:10px;z-index:100;';
ctrl.innerHTML = `
    <label><input type="checkbox" id="enableSel" checked> 允许选择</label>
    <button id="btnZE">全图</button>
    <button id="btnAdd">加实体</button>
    <button id="btnClear">清选择</button>
    <button id="btnToggle">显隐</button>
`;
document.getElementById('map').appendChild(ctrl);

let visible = true;
document.getElementById('enableSel').onchange = e =>
    cadOverlay.enableSelection = e.target.checked;
document.getElementById('btnZE').onclick    = () => cadOverlay.zoomToExtents();
document.getElementById('btnAdd').onclick   = () => addRandomEntities();
document.getElementById('btnClear').onclick = () => cadOverlay.clearSelection();
document.getElementById('btnToggle').onclick = () => {
    visible = !visible;
    cadOverlay.setVisible(visible);
};
```

## 4. 拿到内部 MainView / Engine

```js
const innerCadView = cadOverlay.getCadView();   // MainView 实例（无 .Engine 成员）
// Engine 是 vjcad 顶层单例：单视图时直接用全局 Engine；
// 页面有多个 CAD 视图时先 Engine.bindViewCache(innerCadView) 再取成员
Engine.zoomExtents();
// ⚠️ 叠加模式下 CAD 画布 pointer-events: none（disablePointerEvents），CadEvents.PointerDown 等指针事件不会触发；
// 点击坐标从地图事件取：
map.on('click', e => {
    const p = cadOverlay.lngLatToCad(e.lngLat);   // → {x, y} CAD 坐标
    console.log('CAD 点:', p.x, p.y);
});
```

`cadOverlay.addEntities` 等于 `Engine.addEntities`。需要更细的命令系统时直接走 `Engine.editor.executerWithOp(...)`。

## 5. `smoothAnimation` 取舍

| 值 | 行为 | 适合 |
|---|---|---|
| `true`（默认） | CSS transform 平滑过渡，动画结束才重绘 | 美观，对性能宽容 |
| `false` | 实时重绘 | 响应快但可能抖动 |

移动端 / WebGL 资源紧张推荐 `true`；高刷重绘场景 `false`。

## 6. 配置项速查

```ts
interface CadMapOverlayOptions {         // d.ts ，仅这些字段
    bounds: CadBoundsLike;              // 必需，CAD 坐标范围（[minX,minY,maxX,maxY] / GeoBounds / {min,max}）
    serviceUrl: string;
    accessToken: string;
    themeMode?: number;                 // 0 深色 / 1 浅色
    themeColors?: ThemeColorsPartial;
    minZoom?: number;                   // 默认 0
    maxZoom?: number;                   // 默认 100
    enableSelection?: boolean;          // 默认 true
    smoothAnimation?: boolean;          // 默认 true
    onSelectionChanged?: (sel: EntityBase[]) => void;   // 实参是数组本身
}
// 无 workspace / transparent / backgroundColor / animationMode / fonts 等键
```

## 7. 常用方法

| 方法 | 说明 |
|---|---|
| `addTo(map)` | 添加到 vjmap 地图 |
| `getCadView()` | 内部 `MainView` |
| `getCadBounds()` | CAD 内部坐标范围 |
| `addEntities(entities)` | 添加实体 |
| `zoomToExtents()` | 全图 |
| `setVisible(boolean)` | 显示 / 隐藏 |
| `clearSelection()` | 清空选择 |
| `enableSelection` (属性) | 开 / 关选择 |

## 红旗

- vjmap 必须 **先** `await map.onLoad()` 再 `cadOverlay.addTo(map)`，否则 overlay 找不到 canvas
- 对齐由 `map.attach(svc, prj)` 的投影决定（`cadToLngLat` 就是 `map.toLngLat`）；`bounds` 只用于缩放比取样和 `zoomToExtents()`，填错不会拉伸实体，但会让 `zoomToExtents()` 飞错范围。建议直接用 `vjmap.GeoBounds.fromString(res.bounds)`
- 地图缩到太小（zoom < 1）时 `smoothAnimation: true` 仍会让 CAD 实体出现"先平移再重绘"的延迟
- `cadOverlay.setVisible(false)` 只是隐藏；`cadOverlay.remove()` 只摘 DOM、解绑地图事件，**不释放内部 MainView**——彻底卸载还要 `await cadOverlay.getCadView()?.destroy()`
- `Engine` 是全局单例、同一时刻只服务一个视图（`Engine.currentDoc`/`currentSpace` 共用），每个 overlay 的视图同步都改写同一个 `Engine.currentSpace`；**不建议同一页面叠多个 CadMapOverlay**
- `enableSelection: true` 时如果你也开了 vjmap 的选择 / 弹层，**两套交互会同时响应同一次点击**，通常二选一

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/tutorial/map-overlay.md`、`vjcad/doc/ui/map-overlay.md`
- 示例：`vjcad/example/basic/05mapoverlay.js`
