# 地图叠加模式

将 WebCAD 叠加到 vjmap 地图上，实现 CAD 与 GIS 的融合显示。

## 概念

`CadMapOverlay` 是一个轻量级的 CAD 叠加层，可以添加到 vjmap 地图上，支持：
- 在地图上显示 CAD 实体
- 实体选择和交互
- 视图同步（拖动/缩放/旋转）
- 独立的坐标系管理

---

## 基本用法

### 初始化 vjmap 地图

```javascript
// 动态加载 vjmap（如果尚未加载）
const loadVjmap = () => {
    return new Promise((resolve, reject) => {
        if (window.vjmap) {
            resolve(window.vjmap);
            return;
        }
        
        const link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = 'https://vjmap.com/demo/js/vjmap/vjmap.min.css';
        document.head.appendChild(link);
        
        const script = document.createElement('script');
        script.src = 'https://vjmap.com/demo/js/vjmap/vjmap.min.js';
        script.onload = () => resolve(window.vjmap);
        script.onerror = () => reject(new Error('vjmap 加载失败'));
        document.head.appendChild(script);
    });
};

const vjmap = await loadVjmap();

// 创建 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()
});

const mapExtent = vjmap.GeoBounds.fromString(res.bounds);
const prj = new vjmap.GeoProjection(mapExtent);

// 创建地图实例
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();
```

### 添加 CadMapOverlay

```javascript
const { CadMapOverlay, LineEnt, CircleEnt, TextEnt } = vjcad;

// 创建 CAD 叠加层
const cadOverlay = new CadMapOverlay({
    bounds: mapExtent,              // 地图范围
    serviceUrl: env.serviceUrl,     // 服务地址
    accessToken: env.accessToken,   // 访问令牌
    themeMode: 0,                   // 主题：0=深色, 1=浅色
    enableSelection: true,          // 启用选择
    smoothAnimation: true,          // 平滑动画
    onSelectionChanged: (selection) => {
        if (selection.length === 0) {
            console.log("选择已清空");
        } else {
            console.log(`选中 ${selection.length} 个实体`);
            selection.forEach((ent, i) => {
                console.log(`  [${i + 1}] ${ent.type}`);
            });
        }
    }
});

// 添加到地图
await cadOverlay.addTo(map);
```

---

## 添加实体

```javascript
const { LineEnt, CircleEnt, TextEnt } = vjcad;

// 获取 CAD 坐标范围
const bounds = cadOverlay.getCadBounds();
const [minX, minY, maxX, maxY] = bounds;

// 创建实体
const entities = [];

// 直线
const line = new LineEnt([minX + 100, minY + 100], [maxX - 100, maxY - 100]);
line.setDefaults();
line.color = 1;
entities.push(line);

// 圆
const centerX = (minX + maxX) / 2;
const centerY = (minY + maxY) / 2;
const circle = new CircleEnt([centerX, centerY], 50);
circle.setDefaults();
circle.color = 3;
entities.push(circle);

// 文字
const text = new TextEnt();
text.insertionPoint = [centerX, centerY + 100];
text.text = "WebCAD";
text.height = 20;
text.setDefaults();
text.color = 5;
entities.push(text);

// 批量添加实体
cadOverlay.addEntities(entities);
```

---

## 视图控制

```javascript
// 地图 fitBounds 到叠加层的 CAD 范围（构造参数 bounds / updateBounds 设的范围，不是实体实际范围）
cadOverlay.zoomToExtents();

// 显示/隐藏叠加层
cadOverlay.setVisible(true);   // 显示
cadOverlay.setVisible(false);  // 隐藏

// 获取 CAD 坐标范围
const bounds = cadOverlay.getCadBounds();
// 返回: [minX, minY, maxX, maxY]
```

---

## 选择操作

```javascript
// 启用/禁用选择
cadOverlay.enableSelection = true;
cadOverlay.enableSelection = false;

// 清除选择
cadOverlay.clearSelection();

// 监听选择变化（构造函数中配置）
const cadOverlay = new CadMapOverlay({
    // ...
    onSelectionChanged: (selection) => {
        console.log(`选中 ${selection.length} 个实体`);
    }
});
```

---

## 获取内部 MainView

```javascript
// 获取内部的 MainView 实例
const cadView = cadOverlay.getCadView();

// Engine 是全局单例——切换到该实例先显式绑定，再使用完整 WebCAD API
Engine.bindViewCache(cadView);
Engine.zoomExtents();
```

---

## 配置选项

| 选项 | 类型 | 说明 |
|------|------|------|
| `bounds` | GeoBounds | 地图范围（必需）|
| `serviceUrl` | string | 后端服务地址 |
| `accessToken` | string | 访问令牌 |
| `themeMode` | number | 主题：0=深色, 1=浅色 |
| `enableSelection` | boolean | 是否启用选择（默认 true）|
| `smoothAnimation` | boolean | 是否启用平滑动画（默认 true）|
| `onSelectionChanged` | function | 选择变化回调 |
| `minZoom` / `maxZoom` | number | 地图级别在 [minZoom, maxZoom) 内才显示叠加层（默认 0 / 100）|
| `themeColors` | object | UI / 对话框主题色（部分覆盖）|

### smoothAnimation 说明

| 值 | 行为 |
|----|------|
| `true` | 使用 CSS transform 平滑过渡，动画结束后重绘 |
| `false` | 实时重绘模式，响应更及时但可能有抖动 |

---

## API 参考

### CadMapOverlay

| 方法 | 说明 |
|------|------|
| `addTo(map)` | 添加到 vjmap 地图 |
| `getCadView()` | 获取内部 MainView 实例 |
| `addEntities(entities)` | 添加实体数组 |
| `getCadBounds()` | 获取 CAD 坐标范围 |
| `updateBounds(bounds)` | 更新 CAD 范围并重新对齐 |
| `zoomToExtents()` | 地图 fitBounds 到 CAD 范围（`bounds`，非实体范围）|
| `setVisible(visible, isDisplay?)` | 设置可见性（`isDisplay=true` 用 CSS display，否则增删 DOM）|
| `clearSelection()` | 清除选择 |
| `getSelection()` / `setSelection(entities)` | 读取 / 设置选择集 |
| `cadToLngLat(x, y)` / `lngLatToCad(lngLat)` | CAD 坐标 ↔ 经纬度（借 vjmap 地图投影）|
| `redraw()` | 重绘叠加层 |
| `remove()` | 移除叠加层并解绑地图事件 |

### 属性

| 属性 | 类型 | 说明 |
|------|------|------|
| `enableSelection` | boolean | 是否启用选择 |

---

## 完整示例

```javascript
const { 
    CadMapOverlay, 
    LineEnt, CircleEnt, TextEnt,
    CadEventManager, CadEvents,
    message 
} = vjcad;

// === 1. 初始化 vjmap ===
const vjmap = await loadVjmap();

const svc = new vjmap.Service(env.serviceUrl, env.accessToken);
const res = await svc.openMap({
    mapid: 'sys_zp',
    mapopenway: vjmap.MapOpenWay.GeomRender,
    style: vjmap.openMapDarkStyle()
});

const mapExtent = vjmap.GeoBounds.fromString(res.bounds);
const prj = new vjmap.GeoProjection(mapExtent);

const map = new vjmap.Map({
    container: 'map',
    style: svc.vectorStyle(),
    center: prj.toLngLat(mapExtent.center()),
    zoom: 1
});

map.attach(svc, prj);
await map.onLoad();

// === 2. 创建 CAD 叠加层 ===
const cadOverlay = new CadMapOverlay({
    bounds: mapExtent,
    serviceUrl: env.serviceUrl,
    accessToken: env.accessToken,
    themeMode: 0,
    enableSelection: true,
    onSelectionChanged: (selection) => {
        message.info(`选中 ${selection.length} 个实体`);
    }
});

await cadOverlay.addTo(map);

// === 3. 添加实体 ===
const bounds = cadOverlay.getCadBounds();
const [minX, minY, maxX, maxY] = bounds;
const avgSize = Math.min(maxX - minX, maxY - minY) / 20;

const entities = [];

// 随机创建 10 个实体
for (let i = 0; i < 5; i++) {
    const x1 = minX + Math.random() * (maxX - minX);
    const y1 = minY + Math.random() * (maxY - minY);
    const x2 = minX + Math.random() * (maxX - minX);
    const y2 = minY + Math.random() * (maxY - minY);
    
    const line = new LineEnt([x1, y1], [x2, y2]);
    line.setDefaults();
    line.color = Math.floor(Math.random() * 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 circle = new CircleEnt([cx, cy], avgSize * (0.5 + Math.random()));
    circle.setDefaults();
    circle.color = Math.floor(Math.random() * 7) + 1;
    entities.push(circle);
}

cadOverlay.addEntities(entities);
cadOverlay.zoomToExtents();

// === 4. 交互控制 ===
// 点击实体可选中
// ESC 取消选择
// 拖动/缩放/旋转地图，CAD 视图同步
```

---

## 使用场景

- **GIS + CAD 融合**：在地图上叠加 CAD 图形
- **设备标注**：在地理位置上标注设备信息
- **管线叠加**：将 CAD 管线图叠加到地图上
- **规划展示**：在地图上展示规划设计图
