> **本文回答**：`map.addControl` 的各种控件 —— 导航 / 比例尺 / 全屏 / 鼠标位置 / 鹰眼 / 按钮组。

## 一、支持的控件

| 控件 | 说明 |
|---|---|
| `NavigationControl` | 缩放 + 指南针 |
| `ScaleControl` | 比例尺 |
| `FullscreenControl` | 全屏切换 |
| `MousePositionControl` | 鼠标坐标 + 可选 zoom |
| `MiniMapControl` | 小地图 / 鹰眼 |
| `ButtonGroupControl` | 自定义按钮组 |

## 二、基本用法

```js
map.addControl(new vjmap.NavigationControl(), "top-right");
map.addControl(new vjmap.ScaleControl(), "bottom-left");
map.addControl(new vjmap.FullscreenControl(), "bottom-right");
map.addControl(new vjmap.MousePositionControl({ showZoom: true, showLatLng: true }), "top-left");
```

位置 = `top-left | top-right | bottom-left | bottom-right`。

## 三、移除

```js
map.removeControl(nav);
```

## 四、比例尺

```js
map.addControl(new vjmap.ScaleControl({
  maxWidth: 100,
  unit: "metric"     // "imperial" / "nautical"
}));
```

vjmap 已改写 `ScaleControl`：只要地图投影不是 `LnglatProjection`（即普通 CAD 图），比例尺按 **CAD 坐标距离** 计算（1 个 CAD 单位按 1 m 显示 m / km 标签）；图纸单位不是米时标签数值需自行换算，或改用自定义比例尺（见 [overlay/legend-north-scale](../overlay/legend-north-scale.md)）。

## 五、鼠标位置

```js
new vjmap.MousePositionControl({
  showZoom: true,      // 显示 zoom
  showLatLng: true     // 显示 lng, lat；CAD 图一般用 showLatLng: false
});
```

## 六、鹰眼（MiniMap）

```js
map.addControl(new vjmap.MiniMapControl({
  containerStyle: {
    background: "#31346a85",
    transform: "scale(0.6)",
    transformOrigin: "100% 0%"     // 对应 top-right
    // 'top-right' => '100% 0%'
    // 'top-left' => '0% 0%'
    // 'bottom-right' => '100% 100%'
    // 'bottom-left' => '0% 100%'
  }
}), "top-right");
```

## 七、按钮组 ButtonGroupControl

构造函数签名是 `constructor(options?: Record<string, any>)`——**传 options 对象，自定义按钮放在 `buttons`（或 `addControls`）数组里**。直接把按钮数组传给构造函数不会渲染任何按钮（数组没有 `.buttons` 属性）：

```js
map.addControl(new vjmap.ButtonGroupControl({
  buttons: [
    {
      id: "home",
      icon: "url('data:image/svg+xml;base64,...')",   // 非 <svg 开头的值原样当 CSS background-image，须带 url()
      title: "回到主页",
      onActivate: () => map.fitMapBounds()
    },
    {
      id: "3d",
      icon: "...",
      title: "切换 3D",
      onActivate: () => map.setPitch(map.getPitch() === 0 ? 60 : 0)
    }
  ]
}), "top-left");
```

按钮项字段（见 vjmap.min.js `addButtons`）：

| 字段 | 说明 |
|---|---|
| `id` | 按钮 id；`options.controls[id] === false` 可单独隐藏该按钮 |
| `icon` | 图标。`<svg ...` 开头的字符串自动转成 `background-image` data-uri（缺 `width=` 时补 `width="24" height="24"`）；其他值原样作为 CSS `background-image` |
| `text` | 按钮文字（innerHTML） |
| `title` | 悬停提示，缺省取 `id` |
| `style` / `className` / `container` | 自定义样式、类名、挂载容器 |
| `isCheckbox` + `checked` | 渲染为复选框，`change` 事件触发回调 |
| `onActivate(ctx, e)` | 点击回调（每次点击都调，没有"再点取消"的切换）；`ctx` 是控件实例，`e` 是 DOM 事件。**不是 `onClick`** |
| `onDeactivate` | 可传但**永不触发**（源码中调用处已被注释掉）；开关逻辑请在 `onActivate` 里自己维护状态，或用 `isCheckbox` |

按钮没有 `onActivate` 时，点击会向地图派发 `buttonGroupControl_event` 事件，载荷 `{ event: "onActivate", id, context }`（`isCheckbox` 项另带 `e`），可用 `map.on("buttonGroupControl_event", ...)` 统一兜底。

> **坑位（removeControl 残留）**：`ButtonGroupControl.onRemove` 只删按钮元素（`ui.removeButtons()`），**不移除控件容器本身**——`map.removeControl(bg)` 后空的 `.vjmapgis-ctrl` div 残留在地图上。需手动清理：
>
> ```js
> map.removeControl(bg);
> bg.ControlContainer?.parentNode?.removeChild(bg.ControlContainer);
> ```

## 八、本地化

`locale` 是 `Map` 构造选项（**不是** `vjmap.setLocale` 函数——包里没有该方法），在 `new vjmap.Map` 时传入：

```js
const map = new vjmap.Map({
  container: "map",
  style,
  center, zoom: 2,
  locale: {
    "FullscreenControl.Enter": "进入全屏",
    "FullscreenControl.Exit":  "退出全屏",
    "NavigationControl.ZoomIn":  "放大",
    "NavigationControl.ZoomOut": "缩小",
    "NavigationControl.ResetBearing": "方位角复位",
    "ScaleControl.Meters":     "m",
    "ScaleControl.Kilometers": "km"
  }
});
```

源：`map2d/vjmap-playground/src/04control_控件/controlAddRemove.js`；`MapOptions.locale` 见 `vjmap.d.ts`。

## 九、Geolocate / Attribution

```js
map.addControl(new vjmap.GeolocateControl({ trackUserLocation: true }), "bottom-right");
map.addControl(new vjmap.AttributionControl({ compact: true }));
```

## 十、自定义控件

```js
class MyControl {
  onAdd(map) {
    this.map = map;
    this.container = document.createElement("div");
    this.container.className = "vjmapgis-ctrl vjmapgis-ctrl-group";
    this.container.innerHTML = `<button>🔍</button>`;
    this.container.onclick = () => map.fitMapBounds();
    return this.container;
  }
  onRemove() {
    this.container.parentNode.removeChild(this.container);
    this.map = undefined;
  }
}
map.addControl(new MyControl(), "top-right");
```

> 自定义控件建议沿用 `vjmapgis-ctrl` / `vjmapgis-ctrl-group` 这两个 CSS 类名，这样可以直接继承 vjmap 内置控件的外观（圆角、阴影、hover 样式），开发者工具里也能看到 vjmap 内置控件同样带这些类名。

## 十一、常见坑

1. **控件样式丢失**：必须引入 vjmap 的样式文件（`vjmap.min.css`）。
2. **MiniMap 选错 transformOrigin**：4 个位置对应不同 origin，否则缩放后位移。
3. **NavigationControl 的指南针需要 pitch > 0** 才能显示 pitch 可视化；需 `visualizePitch: true`。
4. **多控件挤在 top-left** → 换位置或用 ButtonGroupControl 集中。
