唯杰地图 logo唯杰地图
VJMAP
EN

svg-div-overlay

vjmap/references/primitive/svg-div-overlay.md 9.0 KB221 行
原文

本文回答:vjmap.SvgOverlay / DivOverlay —— 随地图缩放的 SVG / DIV 覆盖物。

一、定位

这是非 WebGL 的 DOM 覆盖物。适合:

  • 小量交互强的 SVG(动画、鼠标交互、组态图)
  • 贴图(png / jpg)按四点精确定位
  • 框架化组件(Vue/React)作为地图装饰

缺点:

  • 性能远低于 WebGL 层
  • 数量多会卡(建议单图 <20)
  • 倾角大时可能"失真"或"消失"(SVG 有 maxPitch)

二、SvgOverlay 最小示例

const svgOverlay = new vjmap.SvgOverlay({
  maxZoom: 5,      // 超过 zoom=5 自动隐藏
  maxPitch: 60     // pitch ≥ 60 度时隐藏(显示条件 pitch < maxPitch)
});
svgOverlay.addTo(map);

svgOverlay.addElement({
  // 坐标 / 长度须用 {{}} 占位(CAD 坐标、CAD 长度),裸数字是会随刷新错位的 SVG 像素
  html: `<circle ${vjmap.SvgOverlay.attr_cx_cy_r(pt.x, pt.y, 10)} style="fill:red"/>`,
  event: container => {
    const ele = container.querySelector("circle");
    ele.addEventListener("click", () => alert("点了"));
  }
});

三、SvgOverlay 选项

选项 说明
minZoom / maxZoom 可见 zoom 区间
maxPitch 可见最大倾角
updateDivSize 是否随 zoom 放大 div(SVG 需要 true)
maxDivSize div 放大上限(超过则像素放大)
noUpdateBoundsWhenMoveend moveend 不更新 bounds
divClassName 外层 div class
svgMaxWidth / svgMaxHeight SVG 初始画布大小(默认 1000)
svgOffset 初始化偏移(默认 100)

四、增删改 SVG 元素

// 单个
const id = "myCircle";
svgOverlay.addElement({
  html: `<circle id="${id}" ${vjmap.SvgOverlay.attr_cx_cy_r(pt.x, pt.y, 10)} fill="red"/>`,
  event: parent => {
    const el = parent.querySelector(`#${id}`);
    el.addEventListener("click", () => console.log("clicked"));
  }
});

// 批量
svgOverlay.addElements([
  { html: `<circle ${vjmap.SvgOverlay.attr_cx_cy_r(p1.x, p1.y, 50)}/>`, event: () => {} },
  { html: `<line ${vjmap.SvgOverlay.attr_x1_y1_x2_y2(p1.x, p1.y, p2.x, p2.y)} stroke="red"/>`, event: () => {} }
]);

// 删:按元素的 id 字段或 html 原文匹配(不是 html 里写的 DOM id)
svgOverlay.removeElements([id1, id2]);

// 更新:按 id/html 定位后 Object.assign 合并(不是先删再加)
svgOverlay.updateElements([{ id, html: "<circle ...>", event: () => {} }]);

// 直接操作 SVG container:仅临时生效——每次 moveend 都按 elements[].html 重建 innerHTML,改动会被覆盖
const svg = svgOverlay.getSvgContainer();
svg.querySelector("#myCircle").setAttribute("fill", "blue");
// 持久修改用 updateElements([{ id, html }])

// 获取所有元素
const all = svgOverlay.getElements();

五、SVG 辅助静态函数

attr_* 返回的不是字面属性,而是 {{}} 模板占位符——update() 时把 {{x,y}} 地理坐标换算成像素、{{n}} 长度按比例缩放:

vjmap.SvgOverlay.attr_cx_cy_r(10, 20, 5);   // '{{cx=10,cy=20}} r="{{5}}"'
vjmap.SvgOverlay.attr_x1_y1_x2_y2(0, 0, 9, 9); // '{{x1=0,y1=0}} {{x2=9,y2=9}}'
vjmap.SvgOverlay.attr_x_y_w_h(1, 2, 3, 4);  // '{{x=1,y=2}} width="{{3}}" height="{{4}}"'
vjmap.SvgOverlay.attr_x_y(5, 6);            // '{{x=5,y=6}}'
vjmap.SvgOverlay.attr_fontsize(14);         // 'font-size="{{14}}"'
vjmap.SvgOverlay.attr_length(8);            // '{{8}}'
vjmap.SvgOverlay.attr_point(geoPt);         // '{{d=12.5,-3.25}}'(默认带 `d=` 键名)
vjmap.SvgOverlay.attr_point(geoPt, true);   // '{{12.5,-3.25}}'(joinComma 传 true 才是裸 '{{x,y}}'——语义反直觉,见 min.js)
vjmap.SvgOverlay.attr_points([p1, p2]);     // 'points="{{10,20}} {{30,40}}"'
vjmap.SvgOverlay.attr_path([p1, p2]);       // 'M{{12.5,-3.25}} L{{30,40}}'(内部走 joinComma=true 分支,顶点为裸形态)

用法:html: '<circle ' + attr_cx_cy_r(cx, cy, r) + ' fill="red"/>'。

⚠️ 占位符数字必须 ≥2 位或带小数点:替换正则是 \d+[.]?\d+,{{5}}、{{5,6}}(单个一位数)不匹配会原样进 DOM 报 Expected length, "{{5}}"。写 {{5.0}} 或 {{55}} 才替换。

六、DivOverlay 最小示例(贴图片)

const image = document.createElement("img");
image.style.position = "absolute";
image.style.left = "0";
image.style.top  = "0";
image.style.width  = "1280px";
image.style.height = "905px";
image.style.opacity = "0.8";
image.src = "/images/park.jpg";
image.style.pointerEvents = "none";

const divOverlay = new vjmap.DivOverlay({
  bounds: fourPoints,    // 四个 CAD 点(左下 / 左上 / 右上 / 右下)
  element: image,
  width: 1280,
  height: 905,
  minZoom: 5,
  maxZoom: 15
});
divOverlay.addTo(map);

七、DivOverlay 选项

选项 说明
bounds 四个点坐标数组 [[x,y],[x,y],[x,y],[x,y]] 或 GeoBounds
element HTML 元素
width / height 元素原生大小(像素)
minZoom / maxZoom 可见范围
maxPitch 可见最大倾角
updateDivSize 随 zoom 放大 div
maxDivSize div 放大上限

八、DivOverlay 动态更新

divOverlay.updateBounds([pt1, pt2, pt3, pt4]);   // 拖动四角调整
divOverlay.updateSize(w, h);                     // 改原生大小

divOverlay.setVisible(false);                    // 隐藏:从文档摘除 DOM
divOverlay.setVisible(true);                     // 显示:插回 DOM
divOverlay.setVisible(false, true);              // 仅切 display:none(DOM 保留)
divOverlay.setVisible(true, true);               // 仅切 display:""
// 两种方式须成对使用:setVisible(false) 摘除后再 setVisible(true, true) 只改 display,元素仍不在文档里
divOverlay.remove();                             // 彻底移除

九、组态图场景(DIV 内装 SVG)

const svgStr = `
  <svg xmlns="http://www.w3.org/2000/svg"
       viewBox="0 0 800 600"
       preserveAspectRatio="xMinYMin meet">
    <!-- 组态内容 -->
  </svg>`;

const el = document.createElement("div");
el.style.position = "absolute";
el.style.width = "800px";
el.style.height = "600px";
el.innerHTML = svgStr;

new vjmap.DivOverlay({
  bounds: mapBounds,   // GeoBounds 直接传(也接受四点数组)
  element: el,
  width: 800,
  height: 600,
  updateDivSize: true,       // SVG 随缩放放大
  maxDivSize: 6000
}).addTo(map);

十、Vue/React 组件作为 DivOverlay

// Vue3
const host = document.createElement("div");
host.style.position = "absolute";
const app = Vue.createApp(MyComp);
app.mount(host);

new vjmap.DivOverlay({
  bounds, element: host, width: w, height: h
}).addTo(map);

// 卸载时
app.unmount();
divOverlay.remove();

十一、对比

需求 首选
几千个标志 WebGL Symbol
一张背景贴图按四角对齐 DivOverlay
组态图 / 设备示意 DivOverlay (内嵌 SVG)
简单可交互几何(< 50 个) SvgOverlay
大量可交互几何 用 WebGL Polygon / Polyline + hover
Vue/React 卡片 Marker
气泡弹窗 Popup

十二、常见坑

  1. 倾斜时看不见 → maxPitch 默认 90(显示条件 pitch < maxPitch),引擎最大倾角 85,默认不会因倾角隐藏;设 maxPitch: 85 反而会在 85° 时隐藏。大倾角下变形 / 消失是 CSS 3D 矩阵变换的局限,用 map.setMaxPitch(...) 限制倾角或改用 WebGL 图层。
  2. 多个 DivOverlay 错位 → 每个子 element 记得 position: absolute; left:0; top:0;。
  3. SVG 超大 zoom 失真 → 设 maxDivSize 控制;或切成切片贴图。
  4. DivOverlay 挡鼠标 → pointerEvents: "none",或只对需要响应的子元素保留 pointer。
  5. Vue unmount 前 divOverlay 没删 → 容易内存泄漏;onBeforeUnmount 做清理。
  6. addElement 必须在 addTo(map) 之后:先于挂载调用会因内部 update() 访问 this._map.getZoom 抛 TypeError(_map 还是 undefined)。
  7. DivOverlay 的 bounds 选项类型严格:只接受 GeoBounds 或 [4×GeoPointLike] 四角点数组 + width/height 必填;传 "[x,y,x,y]" 字符串抛 Invalid input coordinate: [(文本格式要走 vjmap.GeoBounds.fromString)。
  8. 抛错的 overlay 必须 remove():构造/addTo 抛异常后若残留,该对象仍挂在地图 zoom/move 事件链上持续抛错,阻断后续监听器(残留坏 DivOverlay 导致 zoomend 不再派发)。
  9. map.removeMarkers() 事件载荷不是数组:removeMarkers 事件回调拿到的是 Evented fire 出来的事件对象(e.type === "removeMarkers"),markers 数组被 extend 展开成数字键 e[0]、e[1]……——取 e.markers/e.data 都是 undefined,遍历数字键才是移除的 Marker 列表。noIncludeDivOverlay / noIncludeSvgOverlay 为双否定参数:传 true 才排除对应覆盖物不被清理。