> **本文回答**：图纸批注 —— 箭头 / 框线 / 文字写在 CAD 图上保存（SVG + DivOverlay）。

## 一、方案

使用 Fabric.js 绘制 SVG 批注 → 导出 SVG 字符串 → 用 `vjmap.DivOverlay` 把 SVG 叠加到地图一个范围。

## 二、DivOverlay 类

```ts
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 里绘制

```js
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

```js
const svgText = canvas.toSVG();
```

### 步骤 3：包装 div + DivOverlay

```js
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：保存到后端

```js
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：恢复批注（从后端读出并渲染）

```js
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`

## 五、导出为图片保存（缩略图）

```js
const imgData = canvas.toDataURL({ format: "png", quality: 0.8 });
```

## 六、常见坑

1. **SVG 不随缩放放大** → `updateDivSize: true` + `preserveAspectRatio="xMinYMin meet"`。
2. **SVG 里有 `<desc>` 标签导致截取错误** → 从 `<desc>` 开始截避免 `<?xml` 头的命名空间。
3. **`pointer-events: none`**：SVG 批注不应干扰地图点击；若需要交互，则改 `auto`。
4. **坐标系**：p1/p2 用 CAD 坐标。DivOverlay 内部对 `bounds` 的每个点**无条件** `map.toLngLat` 再投影，没有自动判定——`bounds` 只能传 CAD 坐标（GeoBounds 或四角点数组），传经纬度会被当成 CAD 坐标再转一次而错位。
