> **本文回答**：Polyline / 底座图元的"单独建 source + 图层"写法 —— `addGeoJSONSource` / `addLineLayer` / `addFillLayer` / `addCircleLayer` / `addSymbolLayer`。

## 一、为什么要单独建 source

`new vjmap.Polyline({ data })` 会把 source 和 layer 放在一起创建，**同一份数据只渲染一种样式**。

但如果需要：

- **同一份 GeoJSON 同时渲染 fill + line + symbol**（三种样式共享数据）
- **程序动态切换不同 layer**
- **多图层共享 source，setData 一次全部更新**

→ 需要单独建 source，然后挂多个 layer。

## 二、addGeoJSONSource

```js
map.addGeoJSONSource("my-src", {
  type: "FeatureCollection",
  features: [...]
});

// 等价于
map.addSource("my-src", {
  type: "geojson",
  data: { type: "FeatureCollection", features: [...] }
});
```

常用选项：

```js
map.addSource("my-src", {
  type: "geojson",
  data: geojson,
  generateId: true,       // 自动生成 feature.id（feature-state 要用）
  cluster: true,          // 点聚合
  clusterRadius: 50,
  clusterMaxZoom: 14,
  clusterProperties: {
    sumValue: ["+", ["get", "value"]]
  },
  lineMetrics: true,      // 给 line-gradient 用
  tolerance: 0.375        // 简化容差（默认）
});
```

## 三、addLineLayer（不用区分 layout/paint）

```js
map.addLineLayer("my-line", "my-src", {
  lineCap: "round",
  lineJoin: "round",
  lineColor: "#f00",
  lineWidth: 3,
  filter: ["==", ["geometry-type"], "LineString"]
});
```

## 四、addFillLayer

```js
map.addFillLayer("my-fill", "my-src", {
  fillColor: "#0af",
  fillOpacity: 0.6,
  fillOutlineColor: "#003",
  filter: ["==", ["geometry-type"], "Polygon"]
});
```

## 五、addCircleLayer

```js
map.addCircleLayer("my-circle", "my-src", {
  circleColor: "#f00",
  circleRadius: 5,
  filter: ["==", ["geometry-type"], "Point"]
});
```

## 六、addSymbolLayer

```js
map.addSymbolLayer("my-sym", "my-src", {
  iconImage: ["get", "icon"],
  textField: ["get", "name"],
  textSize: 12,
  filter: ["==", ["geometry-type"], "Point"]
});
```

## 七、同一 source 三种样式

```js
map.addGeoJSONSource("features", mixedGeoJson);

map.addFillLayer("features-poly", "features", {
  fillColor: "#0af",
  fillOpacity: 0.4,
  filter: ["==", ["geometry-type"], "Polygon"]
});

map.addLineLayer("features-line", "features", {
  lineColor: "#f00",
  lineWidth: 2,
  filter: ["==", ["geometry-type"], "LineString"]
});

map.addCircleLayer("features-pt", "features", {
  circleColor: "#f00",
  circleRadius: 5,
  filter: ["==", ["geometry-type"], "Point"]
});

// 一次更新所有图层
map.getSource("features").setData(newGeoJson);
```

## 八、事件 —— 单独建的 layer 用 map.on 而不是 `clickLayer`

```js
map.on("click", "my-line", e => {
  console.log(e.features[0]);
});

map.on("mouseenter", "my-line", () => map.getCanvas().style.cursor = "pointer");
map.on("mouseleave", "my-line", () => map.getCanvas().style.cursor = "");
```

## 九、层序控制

```js
// beforeId 参数决定插入位置
map.addLineLayer("my-line", "my-src", {...}, "other-layer-id");

// 或移动层序
map.moveLayer("my-line", "building-layer");   // 放在 building-layer 下面
map.moveLayer("my-line");                     // 移到最上
```

## 十、移除

```js
map.removeLayer("my-line");
map.removeSource("my-src");

// vjmap 扩展：一次移除 layer + 无人使用的 source
map.removeSourceEx("my-src");
```

## 十一、何时用封装类 vs 手写

| 场景 | 推荐 |
|---|---|
| 单图层，快速开发 | `new vjmap.Polyline` / `Polygon` / `Circle` 等 |
| 同 source 多图层 | `addGeoJSONSource` + `add*Layer` |
| 复杂表达式调试 | `addLayer`（原生） |
| 框架里批量管理 | 统一用 `addGeoJSONSource` 更可控 |

## 十二、常见坑

1. **layerId 冲突** → 先 `map.getLayer("id") && map.removeLayer("id")`。
2. **addSymbolLayer 图标不显示** → 图标名 `loadImage` 了吗。
3. **多图层 click 冒泡** → `map.on("click", "layerA", ...)` 只在该层 hit 时触发；如果要"所有层共用一个 handler"，用 `map.queryRenderedFeatures(e.point, { layers: [...] })`。
