> **本文回答**：GeoJSON source 全面用法 —— addSource / setData / 属性更新 / cluster / lineMetrics / filter。

## 一、创建 GeoJSON source

```js
map.addSource("mySrc", {
  type: "geojson",
  data: featureCollection
});

// 或 URL
map.addSource("mySrc", {
  type: "geojson",
  data: "https://example.com/a.geojson"
});

// vjmap 扩展：
map.addGeoJSONSource("mySrc", featureCollection);
```

## 二、source 选项

| 选项 | 默认 | 说明 |
|---|---|---|
| `data` | — | GeoJSON / URL |
| `generateId` | false | **为 feature 自动生成 id，feature-state 必需** |
| `lineMetrics` | false | 计算线进度，line-gradient 必需 |
| `cluster` | false | 启用点聚合 |
| `clusterRadius` | 50 | 聚合半径（像素） |
| `clusterMaxZoom` | — | 超过该 zoom 不聚合 |
| `clusterMinPoints` | 2 | 最少几点算一簇 |
| `clusterProperties` | — | 聚合时的属性聚合函数 |
| `tolerance` | 0.375 | 简化容差 |
| `buffer` | 128 | 瓦片缓冲 |
| `maxzoom` | 18 | 切瓦片的最大 zoom |
| `attribution` | — | 版权文字 |
| `promoteId` | — | 提升某个属性为 id |

## 三、更新数据 setData

```js
map.getSource("mySrc").setData(newFeatureCollection);

// 小修改 → 传新 FC，一次性替换
```

**注意**：GeoJSON source 没有 "patch"，每次都要传**完整数据**。如果只改几十个 feature，最好在前端缓存副本。

## 四、按 promoteId 把属性当 id

```js
map.addSource("mySrc", {
  type: "geojson",
  data,
  promoteId: "gid"     // feature.properties.gid → feature.id
});

// 之后就可 setFeatureState
map.setFeatureState(
  { source: "mySrc", id: 123 },
  { hover: true }
);
```

## 五、feature-state API

```js
// 读
const state = map.getFeatureState({ source: "mySrc", id: 123 });

// 写
map.setFeatureState({ source: "mySrc", id: 123 }, { hover: true });

// 清除单 feature
map.removeFeatureState({ source: "mySrc", id: 123 }, "hover");

// 清除整 source
map.removeFeatureState({ source: "mySrc" });
```

## 六、query 所有 feature

```js
// 整源查
const feats = map.querySourceFeatures("mySrc");

// 屏幕上可见的（必须指定图层）
const rendered = map.queryRenderedFeatures(e.point, { layers: ["layer1"] });

// 按 filter 查
const subset = map.querySourceFeatures("mySrc", {
  filter: ["==", ["get", "type"], "POI"]
});
```

## 七、cluster 聚合点

```js
map.addSource("pts", {
  type: "geojson",
  data: pointFc,
  cluster: true,
  clusterRadius: 50,
  clusterMaxZoom: 14,
  clusterProperties: {
    sum: ["+", ["get", "val"]]
  }
});

map.addLayer({
  id: "clusters", type: "circle", source: "pts",
  filter: ["has", "point_count"],
  paint: {
    "circle-radius": ["step", ["get", "point_count"], 15, 10, 20, 50, 25],
    "circle-color": ["step", ["get", "point_count"], "#0f0", 10, "#ff0", 50, "#f00"]
  }
});

map.addLayer({
  id: "cluster-count", type: "symbol", source: "pts",
  filter: ["has", "point_count"],
  layout: {
    "text-field": ["get", "point_count_abbreviated"],
    "text-size": 12
  }
});

map.addLayer({
  id: "unclustered", type: "circle", source: "pts",
  filter: ["!", ["has", "point_count"]],
  paint: { "circle-radius": 5, "circle-color": "#11b4da" }
});

// 点击展开
map.on("click", "clusters", e => {
  const feat = e.features[0];
  const cid = feat.properties.cluster_id;
  map.getSource("pts").getClusterExpansionZoom(cid, (err, zoom) => {
    if (err) return;
    map.easeTo({ center: feat.geometry.coordinates, zoom });
  });
});
```

## 八、lineMetrics 用法

```js
map.addSource("route", {
  type: "geojson",
  data: lineFc,
  lineMetrics: true        // 必须
});

map.addLineLayer("route-line", "route", {
  lineWidth: 6,
  lineGradient: [
    "interpolate", ["linear"], ["line-progress"],
    0, "#0f0",
    1, "#f00"
  ]
});
```

## 九、GeoJSON 格式小抄

```js
// Point
{ type: "Point", coordinates: [x, y] }

// LineString
{ type: "LineString", coordinates: [[x,y],[x,y]] }

// Polygon（外环 + 多个孔）
{ type: "Polygon", coordinates: [
  [[x,y],[x,y],...,[x,y]],      // 外
  [[x,y],...,[x,y]]             // 孔（可选）
]}

// MultiPolygon
{ type: "MultiPolygon", coordinates: [
  [ [外], [孔] ],
  [ [外] ]
]}

// FeatureCollection
{ type: "FeatureCollection", features: [
  { type: "Feature", geometry: {...}, properties: {...}, id: 1 }
]}
```

## 十、geometry-type 过滤

在多几何混合 source 上，按几何类型分图层：

```js
map.addFillLayer("poly", "mySrc", {
  fillColor: "#0af",
  filter: ["==", ["geometry-type"], "Polygon"]
});

map.addLineLayer("line", "mySrc", {
  lineColor: "#f00",
  filter: ["==", ["geometry-type"], "LineString"]
});

map.addCircleLayer("pt", "mySrc", {
  circleColor: "#f00",
  filter: ["==", ["geometry-type"], "Point"]
});
```

**注意**：老写法是 `["==", "$type", "LineString"]`，新写法 `["==", ["geometry-type"], "LineString"]`。

## 十一、常见坑

1. **setData 整份数据大 → 卡顿** → cluster / 切瓦片 / 按视野分片加载。
2. **feature-state 不生效** → 没 id（`generateId: true` 或 `promoteId`）。
3. **URL data 跨域** → 后端加 CORS。
4. **很多小的 source** → 合并成一个带 filter 的 source，性能更好。
5. **updates 时图层闪烁** → 先 `setData` 再渲染帧；别在每帧 setData。
6. **`querySourceFeatures` 返回空**：geojson 的瓦片索引**只在有图层引用该 source 且视口覆盖到数据时才生成**——只 `addSource` 不加图层、或数据在当前视口外，查询恒返回 `[]`。且 `setData` 后旧瓦片会先命中（新瓦片异步重建），需要按数量/内容轮询而非查一次。
