地图叠加(CadMapOverlay)
文档:
vjcad/doc/ui/map-overlay.md示例:vjcad/example/basic/05mapoverlay.js
提示:
map-integration/完整子文档见同名目录。本页只聚焦 UI 集成层。
1. 用途
CadMapOverlay 是把 vjcad 视图作为透明图层 叠加到 vjmap 地图上的组件。⚠️ 只支持 vjmap.Map 且已 map.attach(svc, prj):坐标换算与视图同步全靠 map.toLngLat / map.fromLngLat,非 vjmap 的地图对象没有这两个方法(只会 console.warn 并把坐标原样返回)。常见场景:
- 在卫星 / 矢量地图上叠加图纸
- 联动选择:图上点选 = 地图弹层
- 在地理坐标和 CAD 内部坐标间转换
- 用 vjmap 的图层 / 标记 / 路径分析能力
2. 最小示例
import { CadMapOverlay, DrawingManagerService } from 'vjcad';
// 前提:vjmap 地图已按官方示例初始化——
// const res = await svc.openMap({ mapid, mapopenway: vjmap.MapOpenWay.GeomRender, style: vjmap.openMapDarkStyle() });
// const mapExtent = vjmap.GeoBounds.fromString(res.bounds); // CAD 坐标范围
// 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();
const cadOverlay = new CadMapOverlay({
bounds: mapExtent, // CadBoundsLike:图纸的 CAD 坐标范围(不是经纬度)
serviceUrl: 'http://127.0.0.1:27660/api/v1',
accessToken: 'your-token',
themeMode: 0, // 0 深色,1 浅色
enableSelection: true,
onSelectionChanged: entities => { // 实参就是 EntityBase[],不是 {entities}
console.log('选中:', entities.length, '个');
}
});
await cadOverlay.addTo(map); // 返回 Promise<this>;此时才创建并初始化内部 MainView
// 要显示服务端图纸:必须在 addTo 之后,用 overlay 自己的 MainView 打开(overlay 本身没有 loadMap)
const drawingManager = new DrawingManagerService();
const r = await drawingManager.openDrawing({ type: 'imports', mapid: 'demo.dwg', version: 'v1' });
if (r.success) {
const f = new File([r.webcadJson], 'drawing.webcad', { type: 'application/json' });
await cadOverlay.getCadView().openDbDoc(f, r.webcadData); // 完整流程见 file/server.md
}
cadOverlay.zoomToExtents(); // = map.fitBounds(cadBounds, {padding:50, duration:1000})
addTo 内部创建一个 viewMode:'viewer' 的 MainView(backgroundAlpha:0、disablePointerEvents:true、showCrosshair:false),渲染的是 Engine 当前文档——图纸打开要单独走 DrawingManagerService.openDrawing + openDbDoc(见 file-operations),且必须在 addTo 之后(之前 overlay 的 MainView 还不存在,Engine.view 不是它),CadMapOverlay 没有 loadMap / closeMap。
地图 ↔ CAD 坐标换算直接调用 vjmap 的 map.toLngLat / map.fromLngLat(由 map.attach(svc, prj) 的投影决定),不是 overlay 按 bounds 自己做线性插值;bounds 是 CAD 坐标范围,用于 zoomToExtents/getCadBounds 等,应与 vjmap 打开该图得到的 res.bounds 一致。
3. CadMapOverlayOptions 关键字段
interface CadMapOverlayOptions { // d.ts 字段,仅以下这些
bounds: CadBoundsLike; // 图纸的 CAD 坐标范围(如 vjmap.GeoBounds.fromString(res.bounds))
serviceUrl: string; // WebCAD 服务地址
accessToken: string;
themeMode?: number; // 0 深色(默认),1 浅色
themeColors?: ThemeColorsPartial; // UI chrome 主题色覆盖
minZoom?: number; // 最小显示级别(默认 0)
maxZoom?: number; // 最大显示级别(默认 100)
enableSelection?: boolean; // 允许点选实体(默认 true)
onSelectionChanged?: (selection: EntityBase[]) => void;
smoothAnimation?: boolean; // true(默认)动画中 CSS transform,结束 redraw;false 实时 redraw
}
没有 workspace / backgroundColor / transparent / syncWithMap / animationMode / enableHover / showGrips / fonts / onLoad / onHover / onMapMove——这些字段写在 options 里会被静默忽略。视图跟随是硬编码行为(move/zoom/rotate 监听),不需要开关;minZoom/maxZoom 控制 overlay 在哪个缩放区间显示。
4. 关键方法
class CadMapOverlay {
constructor(options: CadMapOverlayOptions);
addTo(map: any, insertId?: string | HTMLElement): Promise<this>; // insertId 可换容器
remove(): void; // 卸载(移除 DOM + 解绑事件)
setVisible(visible: boolean, isDisplay?: boolean): void;
// isDisplay=true → style.display 显隐;否则走 _add/_remove 真正挂载/卸载
// 视图
zoomToExtents(): void; // map.fitBounds(cadBounds, {padding:50})
updateBounds(bounds: CadBoundsLike): void; // 改 bounds 并重算
getCadBounds(): number[]; // [x1,y1,x2,y2]
redraw(): void;
getCadView(): MainView | null; // 拿到底层 MainView 后可调 setTheme 等
// 实体 / 选择(都直接转发到 Engine)
addEntities(entities: EntityBase | EntityBase[]): void; // = Engine.addEntities
getSelection(): EntityBase[]; // = Engine.editor.PreSS
setSelection(entities: EntityBase[]): void; // 注意:吃实体对象数组,不是 handle
clearSelection(): void;
// 坐标转换(返回对象,不是数组)
cadToLngLat(x: number, y: number): { lng: number; lat: number };
lngLatToCad(lng: number, lat: number): { x: number; y: number };
lngLatToCad(lngLat: { lng: number; lat: number }): { x: number; y: number };
// 公共字段:minZoom / maxZoom / enableSelection / smoothAnimation 可直接读写
}
不存在:isVisible / loadMap / closeMap / zoomToBounds / flyTo / removeEntities / setAlpha / setThemeMode。删除实体走 Engine.eraseEntities;改主题经 getCadView().setTheme(...) / setThemeColors(...)。
5. 选择联动
cadOverlay = new CadMapOverlay({
enableSelection: true,
onSelectionChanged: entities => { // 实参是 EntityBase[]
if (entities.length === 0) return popup?.remove();
const e = entities[0];
// EntityBase 没有通用 center 属性;取包围盒中心
const box = Engine.getBoundsByEntities([e]);
const { lng, lat } = cadOverlay.cadToLngLat(box.center.x, box.center.y);
popup = new vjmap.Popup({ closeOnClick: false })
.setLngLat([lng, lat])
.setHTML(`<b>${e.entityType}</b><br/>图层: ${e.layer}`) // entityType 是 getter,不要加 ()
.addTo(map);
}
});
6. 坐标转换实战
import { Point2D, BoundingBox } from 'vjcad';
// 地图点击 → 找到 CAD 实体
map.on('click', e => {
const { x, y } = cadOverlay.lngLatToCad(e.lngLat.lng, e.lngLat.lat);
// BoundingBox 构造收两个 Point2D 角点,不是 4 个数字
const hits = Engine.spatialIndexManager.search(new BoundingBox(
new Point2D(x - 1, y - 1), new Point2D(x + 1, y + 1)
));
if (hits[0]) cadOverlay.setSelection([hits[0]]); // 传实体对象,不是 id 字符串
});
// CAD 实体 → 在地图上画 marker
ents.forEach(e => {
const box = Engine.getBoundsByEntities([e]);
const { lng, lat } = cadOverlay.cadToLngLat(box.center.x, box.center.y);
new vjmap.Marker().setLngLat([lng, lat]).addTo(map);
});
7. 实体添加 / 删除
const line = new LineEnt([0, 0], [100, 100]);
line.setDefaults();
line.color = 1;
cadOverlay.addEntities([line]); // 等价 Engine.addEntities,自动 redraw
// 删除没有 overlay.removeEntities——直接走引擎
Engine.eraseEntities([line]);
cadOverlay.redraw();
addEntities 走的是 vjcad 引擎(内部就是 Engine.addEntities + redraw)。
8. 视图同步(smoothAnimation)
跟随是硬编码的(监听地图 move/zoom/rotate + zoomend),没有 syncWithMap/animationMode 开关;只有渲染策略可选:
smoothAnimation |
说明 |
|---|---|
true(默认) |
拖动/缩放中用 CSS transform 平滑跟手,moveend 后 redraw |
false |
每次 move/zoom 事件实时 redraw——响应即时但可能抖动 |
9. 多图叠加(不建议)
vjcad 按单视图设计:Engine 的文档集合、当前文档、选择集、命令、事件都是全局静态的,每个 overlay 的 addTo 都会新建一个 MainView 并让 Engine.view 指向最后初始化的那个。所以多个 CadMapOverlay 并不独立——它们共享文档 / 选择 / 撤销,addEntities、openDbDoc 等操作落在同一个 Engine 上。需要多张图同屏,用 vjmap 自己的图层(WMS / 瓦片)叠其余图纸,只保留一个 CadMapOverlay 做可编辑层。
// overlay 视图固定 backgroundAlpha:0(构造时写死),无需自己传 transparent
// 想调透明度,对 getCadView() 的 DOM 操作,没有 setAlpha
cadOverlay.getCadView()?.style.setProperty('opacity', '0.6');
10. 主题切换
// 构造时用 themeMode / themeColors;运行期走底层 MainView
cadOverlay.getCadView()?.setThemeColors({ /* ThemeColorsPartial */ });
cadOverlay.getCadView()?.resetThemeColors();
// 没有 overlay.setThemeMode——themeMode 只在构造时生效
地图换主题时同步换 CAD 主题,避免黑底白线 + 白底地图的对比错乱。
红旗
bounds错了(与 mapsize 不一致)→ CAD 内容会被拉伸 / 偏移;建议用 vjmap 的工具计算图纸地理范围- overlay 透明来自内部
backgroundAlpha:0(写死),无transparent/backgroundColor选项 - 多个
CadMapOverlay并不独立:map._webcadOverlays只是登记表,Engine 是单视图全局状态(见第 9 节),后addTo的接管Engine.view;remove()只把 MainView 从 DOM 摘下并解绑地图事件,不销毁 MainView——要彻底释放先await cadOverlay.getCadView()?.destroy()再remove() cadToLngLat/lngLatToCad只是转调 vjmap 的map.toLngLat / fromLngLat(投影由map.attach(svc, prj)决定);地图不是 attach 过的vjmap.Map时会退化为原样返回坐标setSelection/getSelection与Engine.editor的选择集是同一份(PreSS/ssSetFirst)——不要和 vjmap 侧自己的选择逻辑混用addTo(map)必须在 vjmap 地图完全load之后调用;返回this可链式- 地图 zoom 超出
[minZoom, maxZoom)区间时 overlay 自动卸载/重挂(_checkVisible)
参考(官方文档 MCP)
- 文档:
vjcad/doc/ui/map-overlay.md - 示例:
vjcad/example/basic/05mapoverlay.js