annotation
本文回答:图纸批注 —— 箭头 / 框线 / 文字写在 CAD 图上保存(SVG + DivOverlay)。
一、方案
使用 Fabric.js 绘制 SVG 批注 → 导出 SVG 字符串 → 用 vjmap.DivOverlay 把 SVG 叠加到地图一个范围。
二、DivOverlay 类
new vjmap.DivOverlay({
bounds: [[minX, minY], [minX, maxY], [maxX, maxY], [maxX, minY]] // 四个 CAD 点
| new vjmap.GeoBounds(...),
element: HTMLElement, // 要附加的 div(含 svg)
width, height, // 自身 width / height
updateDivSize: true, // svg 需要放大显示时开启
minZoom, maxZoom,
maxDivSize // 超过该大小后改像素缩放
})
.addTo(map);
三、流程
步骤 1:在独立 Fabric.js canvas 里绘制
const canvas = new fabric.Canvas("draw-canvas");
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "#f00";
canvas.freeDrawingBrush.width = 3;
// 或用 fabric.Line / Circle / Rectangle / Textbox ...
步骤 2:导出 SVG
const svgText = canvas.toSVG();
步骤 3:包装 div + DivOverlay
function createDivSvg(map, eleId, p1, p2, w, h, svg) {
svg = svg.substring(svg.indexOf("<desc>"));
const div = document.createElement("div");
div.id = eleId;
div.innerHTML = `
<svg viewBox="0 0 ${w} ${h}" preserveAspectRatio="xMinYMin meet" version="1.1" xmlns="http://www.w3.org/2000/svg">
${svg}
</svg>
`;
Object.assign(div.style, {
position: "absolute",
pointerEvents: "none",
width: w + "px",
height: h + "px"
});
const overlay = new vjmap.DivOverlay({
bounds: [[p1.x, p2.y], [p1.x, p1.y], [p2.x, p1.y], [p2.x, p2.y]],
element: div,
width: w,
height: h,
updateDivSize: true
});
overlay.addTo(map);
return overlay;
}
步骤 4:保存到后端
const anno = {
mapId, version,
name: "批注-1",
imgSrc: canvas.toDataURL(),
zoom: map.getZoom(),
centerX: map.getCenter().lng,
centerY: map.getCenter().lat,
bearing: map.getBearing(),
pitch: map.getPitch(),
pt1: p1, pt2: p2,
width: w, height: h,
svg: canvas.toSVG(),
json: canvas.toJSON()
};
// 自己的 API 存数据库
await api.save(anno);
步骤 5:恢复批注(从后端读出并渲染)
for (const a of await api.load(mapId, version)) {
createDivSvg(map, a.id, a.pt1, a.pt2, a.width, a.height, a.svg);
}
四、常见批注元素(Fabric.js)
- 箭头:
fabric.Path+drawArrow(x1, y1, x2, y2, theta, headLen) - 直线 / 虚线:
fabric.Line+strokeDashArray - 圆:
fabric.Circle(半径=距离一半) - 矩形:
fabric.Rect或fabric.Path(4 点) - 三角形:
fabric.Triangle或fabric.Path - 文字:
fabric.Textbox+enterEditing() - 自由画笔:
canvas.isDrawingMode = true
五、导出为图片保存(缩略图)
const imgData = canvas.toDataURL({ format: "png", quality: 0.8 });
六、常见坑
- SVG 不随缩放放大 →
updateDivSize: true+preserveAspectRatio="xMinYMin meet"。 - SVG 里有
<desc>标签导致截取错误 → 从<desc>开始截避免<?xml头的命名空间。 pointer-events: none:SVG 批注不应干扰地图点击;若需要交互,则改auto。- 坐标系:p1/p2 用 CAD 坐标。DivOverlay 内部对
bounds的每个点无条件map.toLngLat再投影,没有自动判定——bounds只能传 CAD 坐标(GeoBounds 或四角点数组),传经纬度会被当成 CAD 坐标再转一次而错位。