> **本文回答**：`vjmap.Polyline` 线图元 —— 宽度 / 虚线 / 渐变 / 图案 / 动画。

## 一、最小示例

```js
const polyline = new vjmap.Polyline({
  data: map.toLngLat(featureCollection),
  lineColor: "#f00",
  lineWidth: 3,
  isHoverPointer: true,
  isHoverFeatureState: true
});
polyline.addTo(map);
```

## 二、核心参数（驼峰式）

| 参数 | 默认 | 说明 |
|---|---|---|
| `data` | — | GeoJSON LineString 或 FeatureCollection |
| `lineColor` | `#000` | 线颜色 |
| `lineWidth` | 1 | 线宽（像素） |
| `lineOpacity` | 1 | 透明度 |
| `lineCap` | `butt` | 端点样式：`butt` / `round` / `square` |
| `lineJoin` | `miter` | 连接样式：`bevel` / `round` / `miter` |
| `lineMiterLimit` | 2 | miter 阈值（d.ts 里的 `lineMiterMimit` 是拼写错误：该选项被静默忽略，`polyline.setLineMiterMimit()` 抛 TypeError；运行时改用 `map.setLineMiterLimit(poly.getLayerId(), v)`） |
| `lineRoundLimit` | 1.05 | round 阈值 |
| `lineOffset` | 0 | 平行偏移（像素） |
| `lineBlur` | 0 | 模糊 |
| `lineDasharray` | — | 虚线数组 `[实,虚,实,虚,...]`（单位 = 线宽倍数） |
| `linePattern` | — | 平铺图案（图像名） |
| `lineGradient` | — | 沿线渐变（需 source `lineMetrics: true`） |
| `lineTranslate` | `[0,0]` | 平移（像素） |
| `lineTranslateAnchor` | `map` | map/viewport |
| `lineGapWidth` | 0 | 平行双线间距 |
| `lineSortKey` | — | 同层排序 |
| `isHoverPointer` | false | hover 改 cursor |
| `isHoverFeatureState` | false | hover 自动 setFeatureState |

## 三、虚线

```js
new vjmap.Polyline({
  data,
  lineColor: "#00f",
  lineWidth: 2,
  lineDasharray: [2, 4]   // 2 倍线宽实线，4 倍线宽虚线
});
```

复杂虚线：

```js
lineDasharray: [1, 2, 5, 2]  // 实1 空2 实5 空2
```

## 四、渐变线（沿线颜色）

```js
// 必须在 source 上加 lineMetrics
map.addSource("route", { type: "geojson", data: lineFeatures, lineMetrics: true });

const poly = new vjmap.Polyline({
  sourceId: "route",
  data: undefined,
  lineWidth: 6,
  lineGradient: [
    "interpolate", ["linear"], ["line-progress"],
    0, "#0f0",
    0.5, "#ff0",
    1, "#f00"
  ]
});
```

注：设了 `lineGradient` 时 `lineColor` 被忽略（两者可共存）；`lineGradient` 与 `linePattern` 互斥，源必须 `lineMetrics: true`。

⚠️ 复用外部 `sourceId` 时，`poly.remove()` 会连源及该源上的**所有图层**一起删（内部 `removeSourceEx`）；只删本层用 `map.removeLayer(poly.getLayerId())`。

## 五、线型图案（沿线平铺）

```js
await map.loadImageEx("pattern", "/images/line-pattern.png");

new vjmap.Polyline({
  data,
  lineWidth: 20,
  linePattern: "pattern"
});
```

## 六、粗细随 zoom

```js
lineWidth: [
  "interpolate", ["linear"], ["zoom"],
  10, 1,
  14, 3,
  18, 8
]
```

## 七、按属性变色

```js
lineColor: [
  "match", ["get", "level"],
  "main", "#f00",
  "sub", "#0f0",
  "#888"
]
```

## 八、平行双线（公路）

```js
new vjmap.Polyline({ data, lineColor: "#000", lineWidth: 6 }).addTo(map);   // 先加：底层黑色宽线
new vjmap.Polyline({ data, lineColor: "#fff", lineWidth: 2 }).addTo(map);   // 后加：叠在上面的白色细线
```

后加的图层在上，所以先加黑线再加白线。`lineGapWidth` 不是"中间填色"：它画的是两条 `lineColor` 色的边线、中间留透明空隙（单图层就能画空心双线）。

## 九、路径动画（流光 / 跑马灯）

方案 A：`lineDasharray` 动画

```js
let offset = 0;
function animate() {
  polyline.setLineDasharray([offset % 10, 4]);
  offset += 0.1;
  requestAnimationFrame(animate);
}
animate();
```

方案 B：用 `symbol-placement: line` 让图标沿线走（见 [symbol/text-symbol](../symbol/text-symbol.md)）。

方案 C：`createAnimateLineLayer` 系列工厂一行创建动画图层：

```js
const anim = vjmap.createAnimateLineLayer(map, path);   // path = [[x,y],...]
anim.startAnimation();
anim.setSpeed(2);          // 倍速
anim.stopAnimation();
anim.remove();             // source+layer 一起删
```

- 返回 `ICreateAnimateLayerResult`，键面：`{sourceId, layerId, startAnimation, stopAnimation, remove, updateData, updateImages, setSpeed, polyline}`；`createAntPathAnimateLineLayer`（蚂蚁线）/`createArrowAnimateLineLayer`（箭头流）额外带 `createAnimateImages`；`createAnimateFillLayer` 返回 `polygon`（非 `polyline`）。
- **坑位：`createAnimateSymbolLayer(map, path)` 的 path 必须是点位数组 `[[x,y],…]`**——传裸坐标 `[x,y]` 抛 `Cannot use 'in' operator to search for 'points' in <number>`（内部按数组迭代后对每个元素做 `'points' in item` 判定，数字元素直接崩）。
- **坑位：`createAnimateImages(options)` 的 options 实必填**——需 `draw(ctx,w,h,opts)` 或 `drawFrame` 回调，无参调用抛 `Cannot read properties of undefined (reading 'draw')`；但 `createAntPathAnimateImages`/`createArrowAnimateImages` 可无参，各返 2 帧 `ImageData`。

方案 D：`animateVectorLayer`——给**已存在**的线/面/符号层做帧图案轮播：

```js
map.addGeoJSONSource("src", createLineGeoJson(path));
map.addLineLayer("lay", "src", { lineColor: "#f60", lineWidth: 6 });
const anim = vjmap.animateVectorLayer(map, "lay", {
  animateImages: vjmap.createAntPathAnimateImages(),  // 帧图集（ImageData[]）
  speed: 2,                                            // 倍速
  startAutoAnimation: true
}, /* layerType */ 0);                                 // 0=line(line-pattern) 1=fill(fill-pattern) 2=symbol(icon-image)
anim.startAnimation(); anim.setSpeed(4); anim.stopAnimation();
anim.remove();   // 恢复原 pattern，不删图层
```

- 与方案 C 工厂不同：**它不建层**，第二参是**已存在图层 id**——层不在图上时内部 `map.getPaintProperty(layerId,...)` 读 `getLayer→undefined` 直接抛 `Cannot read properties of undefined`。返回 `{startAnimation, stopAnimation, remove, updateImages, setSpeed}`（没有 `sourceId/layerId/updateData`）。

## 十、修改 / 删除

```js
polyline.setData(newFeatures);
polyline.setLineColor("#0af");
polyline.setLineWidth(5);
polyline.setLineDasharray([1, 2]);
polyline.setProperty("lineOpacity", 0.5);

polyline.hide();
polyline.show();
polyline.remove();
```

## 十一、事件

```js
polyline.clickLayer(e => alert(e.features[0].properties.name));
polyline.hoverPopup(f => `<b>${f.properties.name}</b>`, { anchor: "bottom" });
polyline.hoverFeatureState();
```

## 十二、常见坑

1. **lineWidth 太细不显示** → < 0.5px 在高分屏可能消失；设 ≥ 1。
2. **lineGradient 无效** → source 未开 `lineMetrics: true`；或同时设了 `linePattern`（lineColor 不影响，只是被忽略）。
3. **虚线密度感觉不对** → `lineDasharray` 单位 = 线宽倍数，不是像素。
4. **hover 无效** → feature 没 id 或 id 为 0（`hoverFeatureState` 用 `if (featureId)` 判断，0 被忽略）。封装类不透传 `generateId`（且 generateId 从 0 编号）：FeatureCollection 须自带非 0 `id`；或传 `[{points, properties}]`（自动 `id = 序号 + 1`）；或先自建源（`promoteId`）再传 `sourceId`。
5. **连线有毛刺** → `lineJoin: "round"` + `lineCap: "round"`。
