gis-view-webgl2 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.d.ts +140 -60
  3. package/dist/index.js +1 -1
  4. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +73 -5
  5. package/docs/GisView.md +49 -49
  6. package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +87 -16
  7. package/docs/demos/ApiCoverageDemo.vue +1 -5
  8. package/docs/demos/BackendSyncDemo.vue +1 -2
  9. package/docs/demos/BackgroundDemo.vue +68 -1
  10. package/docs/demos/BasicDemo.vue +76 -76
  11. package/docs/demos/BatchAddDemo.vue +2 -1
  12. package/docs/demos/ChannelDemo.vue +1 -3
  13. package/docs/demos/ClusterDemo.vue +48 -1
  14. package/docs/demos/CopyMergeDemo.vue +899 -904
  15. package/docs/demos/DemoShell.vue +336 -336
  16. package/docs/demos/EventDemo.vue +300 -300
  17. package/docs/demos/ExtensionDemo.vue +200 -200
  18. package/docs/demos/GisViewDemo.vue +64 -64
  19. package/docs/demos/LabelDemo.vue +489 -489
  20. package/docs/demos/StyleDemo.vue +67 -67
  21. package/docs/demos/api-index.js +24 -29
  22. package/docs/demos/demos.js +92 -92
  23. package/docs/demos/gis-view-demo/useGisViewDemo.js +19 -14
  24. package/docs/demos/shared/demoData.js +107 -107
  25. package/docs/demos/shared/demoStyle.css +249 -249
  26. package/docs/format/02-/350/212/202/347/202/271/344/270/216/347/272/277/350/267/257.md +3 -3
  27. package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +3 -0
  28. package/docs/from0/01-/345/205/210/346/212/212/345/234/260/345/233/276/346/203/263/346/270/205/346/245/232.md +8 -7
  29. package/docs/index.html +3 -3
  30. package/docs/internals/04-/347/233/270/346/234/272/345/235/220/346/240/207/344/270/216/345/203/217/347/264/240.md +6 -4
  31. package/docs/internals/14-/350/203/214/346/231/257/345/261/202.md +2 -2
  32. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +36 -16
  33. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +38 -84
  34. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +36 -19
  35. package/docs/manual/06-/346/225/260/346/215/256/344/270/216/345/234/272/346/231/257/346/233/264/346/226/260.md +1 -1
  36. package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +7 -7
  37. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +15 -15
  38. package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +1 -1
  39. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +6 -0
  40. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +1 -1
  41. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +2 -0
  42. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +10 -8
  43. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +11 -7
  44. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +27 -8
  45. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +27 -4
  46. package/docs/manual/21-/350/257/264/346/230/216/344/270/216/345/270/270/350/247/201/351/227/256/351/242/230.md +3 -3
  47. package/docs/playground/index.html +1 -1
  48. package/docs/sample-document/345/255/227/346/256/265/350/257/246/350/247/243.md +2 -2
  49. package/docs/steps/check-doc-truth.mjs +1 -1
  50. package/docs/steps/selftest-actions.html +4 -4
  51. package/docs/steps/selftest-data.html +5 -5
  52. package/docs/steps/selftest-demos.html +1683 -1619
  53. package/docs/steps/selftest-docs.html +4 -4
  54. package/docs/steps/selftest-effects.html +1 -1
  55. package/docs/steps/selftest-features.html +548 -47
  56. package/docs/steps/selftest-full.html +566 -566
  57. package/docs/steps/selftest-labels.html +647 -647
  58. package/docs/steps/selftest-perf.html +2282 -2237
  59. package/docs/steps/selftest-select.html +53 -14
  60. package/docs/steps/step-10-full.js +1 -4
  61. package/docs/steps/step-12-events.js +1 -4
  62. package/package.json +43 -43
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 点击与样式
6
6
 
7
- 单击命中要素时会套用选中样式,命中空白处会清空选中;双击在单击之后额外派发 `onDblClick`。**这两件事都缺省关**:`clickable` 没写时点击只发回调(`action.type` 是 `click`)、不改选中集,悬停(`onHover` 与悬停样式)也不重算;写 `clickable: true` / `features: { hover: true, select: true }` 或 `features: 'view'` 才开。样式优先级是 **状态样式 > 热力图 > `override` 规则 > 要素自身字段 > 普通规则 > 全局样式(`pointStyle` / `lineStyle`)> 内置默认值**:
7
+ 单击命中要素时会套用选中样式,命中空白处会清空选中;双击在单击之后额外派发 `onDblClick`。**这两件事都缺省关**:`features.hover` / `features.select` 没写时点击只发回调(`action.type` 是 `click`)、不改选中集,悬停(`onHover` 与悬停样式)也不重算;写 `features: { hover: true, select: true }` 或 `features: 'view'` 才开。样式优先级是 **状态样式 > 热力图 > `override` 规则 > 要素自身字段 > 普通规则 > 全局样式(`pointStyle` / `lineStyle`)> 内置默认值**:
8
8
 
9
9
  ```js
10
10
  createGisView(container, {
@@ -26,9 +26,9 @@ createGisView(container, {
26
26
  })
27
27
  ```
28
28
 
29
- 状态样式按字段覆盖:`selectStyle` 里没写的字段继续沿用点 / 线自身的样式。`pointHoverStyle`、`lineHoverStyle` 同理,作用于悬停。全部留空时组件不画任何强调:点击只改选中集合、悬停只触发回调与提示,要反馈就显式配一份状态样式。同一个要素同时悬停又被选中时以选中为准;来源一换(悬停 ⇄ 选中 ⇄ 多选)样式立刻跟着换,不必等鼠标移开再回来。想退回「没配样式时用尺寸 / 宽度增幅加一圈强调环」的旧行为,把 `defaultHighlight` 设为 `true` 即可。 多选(Ctrl + 点击累加)与框选(默认 Shift + 拖动,可改 `shortcuts.boxSelect`)各有一份样式,解析顺序都是 **要素自身 > 对应来源的整体配置 > 选中样式**:`pointMultiSelectStyle` / `pointBoxSelectStyle` / `lineMultiSelectStyle` 配上就生效,不配就与选中样式完全一致。不想按修饰键就打开 `multiSelect` 开关(`options.multiSelect: true`):普通单击即取反累加,与按住多选快捷键点的是同一套行为、同一个 `multiSelectStyle`;双击的第二次点击不再取反,`onDblClick` 照常派发。`shadow` 也在状态样式链里:`selectStyle` / `lineSelectStyle`(以及 `hoverStyle`、`multiSelectStyle`)写 `shadow: { color, opacity, blur, offsetX, offsetY }` 就能给对应的状态加阴影,取消状态自动收掉。节点尺寸与线宽另有两组系数,各管一件事、可以叠加:`pointSizeScale` / `lineWidthScale` 是与地图无关的整体放大(常数倍数),`pointZoomScale` / `lineZoomScale` 是「跟随地图缩放」的指数(0 表示不跟随,1 表示与地图同倍率,基准是 `zoomBaseScale` 或首次适应视图时的比例),实际绘制尺寸是两者相乘,拾取容差跟着一起放大。
29
+ 状态样式按字段覆盖:`selectStyle` 里没写的字段继续沿用点 / 线自身的样式。`pointHoverStyle`、`lineHoverStyle` 同理,作用于悬停。全部留空时组件不画任何强调:点击只改选中集合、悬停只触发回调与提示,要反馈就显式配一份状态样式。同一个要素同时悬停又被选中时以选中为准;来源一换(悬停 ⇄ 选中 ⇄ 多选)样式立刻跟着换,不必等鼠标移开再回来。想退回「没配样式时用尺寸 / 宽度增幅加一圈强调环」的旧行为,把 `defaultHighlight` 设为 `true` 即可。 多选(Ctrl + 点击累加)与框选(默认 Shift + 拖动,可改 `shortcuts.boxSelect`)各有一份样式,解析顺序都是 **要素自身 > 对应来源的整体配置 > 选中样式**:`pointMultiSelectStyle` / `pointBoxSelectStyle` / `lineMultiSelectStyle` 配上就生效,不配就与选中样式完全一致。不想按修饰键就打开 `multiSelect` 开关(`features: { multiSelect: true }`):普通单击即取反累加,与按住多选快捷键点的是同一套行为、同一个 `multiSelectStyle`;双击的第二次点击不再取反,`onDblClick` 照常派发。`shadow` 也在状态样式链里:`selectStyle` / `lineSelectStyle`(以及 `hoverStyle`、`multiSelectStyle`)写 `shadow: { color, opacity, blur, offsetX, offsetY }` 就能给对应的状态加阴影,取消状态自动收掉。节点尺寸与线宽另有两组系数,各管一件事、可以叠加:`pointSizeScale` / `lineWidthScale` 是与地图无关的整体放大(常数倍数),`pointZoomScale` / `lineZoomScale` 是「跟随地图缩放」的指数(0 表示不跟随,1 表示与地图同倍率,基准是 `zoomBaseScale` 或首次适应视图时的比例),实际绘制尺寸是两者相乘,拾取容差跟着一起放大。
30
30
 
31
- `clickable: false` 的要素既不会悬停高亮也不会被点中;`options.clickable: false` 则整体关闭拾取。
31
+ 要素自身写 `clickable: false` 既不会悬停高亮也不会被点中(这是逐要素覆盖,不是顶层权限);整体关闭悬停与选中写 `features: { hover: false, select: false }`。
32
32
 
33
33
  ## 点击级联高亮(cascadeHighlight)
34
34
 
@@ -37,7 +37,7 @@ createGisView(container, {
37
37
  ```js
38
38
  const view = createGisView(container, {
39
39
  points, lines, channels,
40
- clickable: true,
40
+ features: { hover: true, select: true },
41
41
  cascadeHighlight: {
42
42
  scope: 'both',
43
43
  triggerOnClick: true,
@@ -154,7 +154,7 @@ createGisView(container, {
154
154
  - `onPointerDown` / `onPointerUp` 不改变组件行为:选中、拖拽、框选、拐点编辑照旧。左键的一次「点」(没有拖动)仍然走 `onClick` / `onDblClick`,两者是并列的,不会互相吞掉 —— 载荷里的 `action.type` 各说各话:按下是**意图**、松开是**刚做完的那件事**(`action.moved` 说明是拖还是点)、点击是**真正发生的那一件**。这一下如果同时改了选中,顺序固定是 `onClick → onSelect → onSelectChange`;`selectTrigger: 'dblClick'` 的第二下再在最后补 `onDblClick`。
155
155
  - 点空白处照发:`onPointerDown` / `onPointerUp` 的 `action.target` 是 `empty`(开了平移时按下是 `pan` 的意图),`onClick` / `onDblClick` 的载荷是 `null`(选中在那一刻已经清空)——「点了空白」这件事也可以看 `onSelectChange` 的 `action.type: 'clearSelection'`。
156
156
  - **中键按住就是临时平移**(`pan` 开关开着时):`onPointerDown` 的 `action.type` 是 `pan`、`originalEvent.button === 1`,按住拖动平移画布、松开结束(`onPointerUp` 的 `action.type` 也是 `pan`);这一路**不经过** `onClick`,中键也不会改选中 —— 想自己接中键(中键菜单 / 快捷操作)就按 `originalEvent.button` 认它。长按(触摸 / 触控笔)与中键一样有明确的读数:看松开 / 点击载荷的 `action.longPress` 与 `action.press` / `action.duration`。
157
- - `onContextMenu` 每次都只发这一条;命中通道回线时,分组读数在同一份载荷的 `group` / `groups` 上,可执行动作挂在 `channelContext` 上(见「通道右键交互框」),不再额外发第二个回调。**右键缺省整个不接**(`contextMenu` 开关没开时系统菜单照弹、回调也不发);接住之后一律 `preventDefault()` 收掉系统菜单(点 / 线 / 空白 / 通道都一样),回调照常发。想自己弹菜单就写 `interactive: true` / `contextMenu: true` / `features: 'view'`,想让系统菜单自己弹出来就写 `preventContextMenu: false`。
157
+ - `onContextMenu` 每次都只发这一条;命中通道回线时,分组读数在同一份载荷的 `group` / `groups` 上,可执行动作挂在 `channelContext` 上(见「通道右键交互框」),不再额外发第二个回调。**右键缺省整个不接**(`features.contextMenu` 没开时系统菜单照弹、回调也不发);接住之后一律 `preventDefault()` 收掉系统菜单(点 / 线 / 空白 / 通道都一样),回调照常发。想自己弹菜单就写 `features: { contextMenu: true }` / `features: 'view'`,想让系统菜单自己弹出来就写 `preventContextMenu: false`。
158
158
  - 命中的层级是**点 → 线 → 通道**:右键落在节点上时载荷是 `type: 'point'`,没有 `channelContext` —— 节点压在通道回线上(端点最常见)不会被当成通道右键;点空白处同样没有 `channelContext`,可以直接用它收框。
159
159
  - 悬停是高频率回调:大数据量下建议只做节流的读数或轻量样式切换(demo 里的读数就是这么做的)。
160
160
 
@@ -253,7 +253,7 @@ const view = createGisView('#map', {
253
253
  判定顺序:`拐点 → waypoint`、`通道回线 → channel`、`普通线路 → line`、`节点 → point`;正在拖动、平移画布、框选、复制与新增通道草稿这些状态优先于悬停目标(分别走 `drag` / `pan` / `box` / `copy` / `draft`);按住修饰键时先看要素自己的 `ctrlCursor`,再看 `cursorStyle.ctrl`,最后回落到 `cursorStyle` 的同名键。
254
254
  ## 拖拽点
255
255
 
256
- **拖拽缺省关**(写 `draggable: true` / `features: { drag: true }` 才拖得动)。按住可拖拽的点即可移动它:超过 3px 后进入拖拽状态,拖动过程中点坐标实时更新并派发 `onPointDrag`,`phase` 依次为 `start`、`drag`、`end`。松开后不会触发单击 / 双击,也不会平移视图;按在空白处或不可拖拽的要素上仍然是平移。
256
+ **拖拽缺省关**(写 `features: { drag: true }` 才拖得动;顶层 `draggable` 已移除,表内别名 `draggable` 只在 `features` 对象里有效)。按住可拖拽的点即可移动它:超过 3px 后进入拖拽状态,拖动过程中点坐标实时更新并派发 `onPointDrag`,`phase` 依次为 `start`、`drag`、`end`。松开后不会触发单击 / 双击,也不会平移视图;按在空白处或不可拖拽的要素上仍然是平移。
257
257
 
258
258
  跟随移动的线路有两种写法:
259
259
 
@@ -293,7 +293,7 @@ onPointDrag: payload => {
293
293
 
294
294
  ```js
295
295
  createGisView(container, {
296
- draggable: true, // 整体打开(缺省关,这里写出来才拖得动)
296
+ features: { drag: true }, // 整体打开(缺省关,这里写出来才拖得动)
297
297
  points: [{ id: 'fixed', x: 116.4, y: 39.9, draggable: false }] // 逐点关闭
298
298
  })
299
299
  ```
@@ -12,8 +12,8 @@
12
12
  | 按坐标像单击一样选中 | `view.selectAt(clientX, clientY)`(通道回线按 `channelSelectMode` 摊开) |
13
13
  | 换一份选中集 | `view.selectFeatures(refs)`(缺省 `mode: 'replace'`) |
14
14
  | 追加 / 取反 / 移除 | `view.selectFeatures(refs, { mode: 'add' \| 'toggle' \| 'remove' })` |
15
- | 多选 | 打开 `multiSelect` 后单击,或按住 Ctrl / ⌘ 单击;框选先开 `boxSelect`,再按住 Shift 拖动 |
16
- | 再次选中取消 | 打开 `toggleSelect` 后再次普通单击已选要素;Ctrl / ⌘ 先选中后再普通单击也取消 |
15
+ | 多选 | 打开 `features: { multiSelect: true }` 后单击,或按住 Ctrl / ⌘ 单击;框选先开 `features: { boxSelect: true }`,再按住 Shift 拖动 |
16
+ | 再次选中取消 | 打开 `features: { toggleSelect: true }` 后再次普通单击已选要素;Ctrl / ⌘ 先选中后再普通单击也取消 |
17
17
  | 取消指定项 | `view.deselect(refs)`,等价于 `view.selectFeatures(refs, { mode: 'remove' })` |
18
18
  | 取消全部 | 单击空白处、无其它编辑态时按 Esc、`view.deselect()`、`view.selectFeatures(null)` 或 `view.selectFeatures([])` |
19
19
  | 读回结果 | `view.isSelected(ref)` / `view.getSelections()` / `view.getSelectionData()` |
@@ -36,6 +36,8 @@ view.deselect() // 取消全部
36
36
 
37
37
  落地方式分两条:**节点**的抬层层号写在布局里(`raiseLayer = pointLayerCount + 1`,压在装饰与所有 `zIndex` 层之上),**线路**的先后写在批次顺序里(抬层的线路排在普通节点之后、抬层节点之前;`snapshotScene()` 另给 `raisedLines`,矢量导出按同一份次序绘制)。因此有三处必须跟着动,缺一处就会出现「被电压等级和原有层级限制」的观感:
38
38
 
39
+ 装饰与所属要素共用同一套抬层判断:选中线路 / 通道回线的 `decorations` 跟着进抬层层,选中节点与选中线路端点节点的装饰也一起抬层;普通线路 / 节点的装饰仍留在普通层。绘制次序为普通装饰 → 抬层线路 → 抬层装饰 → 抬层节点,`snapshotScene()` 用 `raisedDecorations` 单独给出这一段,`buildSceneSvg()` 与 WebGL 保持同一顺序。装饰颜色是否跟随选中色由各自的 `followSelectColor` 决定:打开后把所属线路或节点的最终选中色写进装饰的对应通道 —— `symbol` 为 `{ type: 'svg', path }` 时写 `strokeColor`,其他符号写 `color`;节点连接线不跟随。`selectionOnTop: false` 不改变颜色规则,只取消抬层。
40
+
39
41
  - 抬层**线路**集合一变就要重铺线路批次(`syncRaiseSets()` 里标 `lineBatchDirty`)—— 否则选中的线路 / 通道不抬层,取消选中也不还原;
40
42
  - **拖动中的整组**(`markDragMoved` → `setRaiseExtraPoints`)在起步那一帧先补线路几何、再整份重排一次(`applyDragCoordinate`),此后每帧回到「只按槽位改坐标」的快路径 —— 否则同层拖拽、拖到更高电压等级的节点上都不抬层;
41
43
  - 松手(`setRaiseExtraPoints([])`)或取消选中时按同一套集合重算,让出最上层。
@@ -44,11 +46,11 @@ view.deselect() // 取消全部
44
46
 
45
47
  ## 框选
46
48
 
47
- **框选缺省关**(写 `boxSelect: true` / `features: { boxSelect: true }` 或 `features: 'edit'` 才开)。按住 Shift(`shortcuts.boxSelect`,可改成 Ctrl / Alt 等)在画布上拖动会出现选框,松开后框内所有节点被选中,同时派发一次 `onSelect`:
49
+ **框选缺省关**(写 `features: { boxSelect: true }` 或 `features: 'edit'` 才开)。按住 Shift(`shortcuts.boxSelect`,可改成 Ctrl / Alt 等)在画布上拖动会出现选框,松开后框内所有节点被选中,同时派发一次 `onSelect`:
48
50
 
49
51
  ```js
50
52
  createGisView(container, {
51
- boxSelect: true, // 缺省关闭,要框选就显式打开
53
+ features: { boxSelect: true }, // 缺省关闭,要框选就显式打开
52
54
  selectionBoxStyle: { fill: '#2f6fd0', fillOpacity: 0.14, stroke: '#2f6fd0', strokeWidth: 1 },
53
55
  onSelect: payload => {
54
56
  if (!payload) {
@@ -60,7 +62,7 @@ createGisView(container, {
60
62
  })
61
63
  ```
62
64
 
63
- 拖动期间选框实时跟随,但事件只在松手时派发一次,不会拖动一帧报一次。**框选默认只收节点**,保持旧口径;写 `selectionKinds` 后可以把通道外的线路与通道回线一起纳入(三类任意组合),也可以改用端点口径,只收两端节点都框住的线路 / 通道,见下节。选中结果用 `getSelections()` / `getSelectionData().ids` 读回。用代码框选等价于调用 `selectInBox({ x, y, width, height })`,矩形同样是画布 CSS 像素;第二段还能临时覆盖类型过滤。框选命中的节点套用框选样式(`options.pointBoxSelectStyle`,节点自己的 `boxSelectStyle` 优先),缺省与选中样式完全一致;框选出来的线路走普通的 `lineSelectStyle`(线路没有单独的框选样式)。
65
+ 拖动期间选框实时跟随,但事件只在松手时派发一次,不会拖动一帧报一次。**框选默认只收节点**,保持旧口径;写 `selectionKinds` 后可以把通道外的线路与通道回线一起纳入(三类任意组合),也可以改用端点口径,只收两端节点都框住的线路 / 通道,见下节。线路 / 通道的 `line` / `channel` 口径按**实际折线路径与选框逐线段相交**判定,包围盒只用于候选粗筛;U 形、折返或远距离绕行的线不会因为包围盒盖住选框而被误选,通道也不会因此被整条摊开。选中结果用 `getSelections()` / `getSelectionData().ids` 读回。用代码框选等价于调用 `selectInBox({ x, y, width, height })`,矩形同样是画布 CSS 像素;第二段还能临时覆盖类型过滤。框选命中的节点套用框选样式(`options.pointBoxSelectStyle`,节点自己的 `boxSelectStyle` 优先),缺省与选中样式完全一致;框选出来的线路走普通的 `lineSelectStyle`(线路没有单独的框选样式)。
64
66
 
65
67
  ### 保留框与整组拖动(selectionBoxPersistent)
66
68
 
@@ -68,8 +70,7 @@ createGisView(container, {
68
70
 
69
71
  ```js
70
72
  const view = createGisView(container, {
71
- boxSelect: true, // 框选入口
72
- draggable: true, // 保留框拖动依赖节点可拖
73
+ features: { boxSelect: true, drag: true }, // 框选入口 + 保留框拖动依赖节点可拖
73
74
  selectionBoxPersistent: true,
74
75
  selectionBoxStyle: {
75
76
  render: 'div',
@@ -90,9 +91,9 @@ view.clearSelectionBox() // 只收起矩形,不改选中集
90
91
 
91
92
  样式可以只改颜色,也可以挂成 DOM 再交给类名接管:`selectionBoxStyle.render: 'div'` 会创建 `.gis-view__selection-box`,`className` 追加自定义类;`borderStyle` / `radius` 只在 DOM 模式生效。canvas 模式的边框恒为实线直角。保留框锚在世界坐标里,地图平移 / 缩放时两种渲染都会重新投影,DOM 框的平移只更新合成层 `transform`,缩放时才改宽高;颜色、线型和类名只在 `selectionBoxStyle` 变化后重写。它只负责显示,不接管指针,框内拖动仍由画布统一处理。
92
93
 
93
- 不想拖动时也可以用 Ctrl(macOS 为 Cmd)逐个点击节点累加选中,命中已选中的节点会把它取消掉;两种方式得到的选中集合一样,之后按住组内任意一个节点拖动就是整组移动。不想按修饰键就把多选开关打开(`options.multiSelect: true`,运行时用 `updateOptions({ multiSelect })` 切):普通单击即取反累加,线路也认(受 `channelSelectMode` 的整组 / 整条摊开约束);单击触发模式下双击的第二次点击不再取反,`onDblClick` 照常派发。多选命中的节点走多选样式(`options.pointMultiSelectStyle`,节点自己的 `multiSelectStyle` 优先),框选命中的走框选样式(`options.pointBoxSelectStyle`,节点自己的 `boxSelectStyle` 优先),两者缺省都与选中样式完全一致。注意框选样式只给节点;选中线路 / 通道回线时仍按 `lineSelectStyle` / `lineMultiSelectStyle` 上色。
94
+ 不想拖动时也可以用 Ctrl(macOS 为 Cmd)逐个点击节点累加选中,命中已选中的节点会把它取消掉;两种方式得到的选中集合一样,之后按住组内任意一个节点拖动就是整组移动。不想按修饰键就把多选开关打开(只写 `features: { multiSelect: true }`;运行时用 `setFeatures({ multiSelect: true })` 切):普通单击即取反累加,线路也认(受 `channelSelectMode` 的整组 / 整条摊开约束);单击触发模式下双击的第二次点击不再取反,`onDblClick` 照常派发。多选命中的节点走多选样式(`options.pointMultiSelectStyle`,节点自己的 `multiSelectStyle` 优先),框选命中的走框选样式(`options.pointBoxSelectStyle`,节点自己的 `boxSelectStyle` 优先),两者缺省都与选中样式完全一致。注意框选样式只给节点;选中线路 / 通道回线时仍按 `lineSelectStyle` / `lineMultiSelectStyle` 上色。
94
95
 
95
- 只想让「再次点同一个就取消」、又不想把普通单击都变成多选时,打开 `toggleSelect`。它只接管本来会改选中集、且命中项已经在选中集里的普通点击:单击模式下第一下取反、双击的第二下保持;`selectTrigger: 'dblClick'` 下由双击第二下取反,再次双击取消。Ctrl / ⌘ 先选中的要素,后续普通单击同样取消。取反生效时点击与选中变更的动作名都是 `toggleSelect`;这条取消路径不受 `selectionKinds` 限制,同一来源的级联高亮也会一起收掉。缺省关,运行时可用 `updateOptions({ toggleSelect })` 切换。
96
+ 只想让「再次点同一个就取消」、又不想把普通单击都变成多选时,打开 `features: { toggleSelect: true }`。它只接管本来会改选中集、且命中项已经在选中集里的普通点击:单击模式下第一下取反、双击的第二下保持;`selectTrigger: 'dblClick'` 下由双击第二下取反,再次双击取消。Ctrl / ⌘ 先选中的要素,后续普通单击同样取消。取反生效时点击与选中变更的动作名都是 `toggleSelect`;这条取消路径不受 `selectionKinds` 限制,同一来源的级联高亮也会一起收掉。缺省关,运行时可用 `setFeatures({ toggleSelect: true })` 切换。
96
97
 
97
98
  点中通道回线时,取消范围跟随 `channelSelectMode`:`line` 取消命中的单条回线,`group` 取消该回线所在的整组,`channel` 取消整条通道;其他已选要素保留。若该回线所属分组 / 通道已经完整选中(例如先通过右键菜单或 `selectFeatures` 选中了整组 / 通道),即使当前是 `line` 模式,再次点击也会整组 / 整条取消,不会只删命中那一条、留下仍然选中的假象。部分选中时按当前模式取消展开范围,点到组内 / 通道内的另一条回线同样生效。独立线路始终只取消它自己。
98
99
 
@@ -124,8 +125,7 @@ view.updateOptions({ selectTrigger: 'click' }) // 运行时切回单击选中
124
125
 
125
126
  ```js
126
127
  const view = createGisView(container, {
127
- boxSelect: true,
128
- clickable: true,
128
+ features: { boxSelect: true, select: true },
129
129
  selectionKinds: ['point', 'line', 'channel'], // 框选 + Ctrl 多选三类全收
130
130
  channelSelectMode: 'channel' // 通道回线一选就是整条通道
131
131
  })
@@ -256,7 +256,7 @@ view.selectFeatures('station-7', { mode: 'remove' }) // 与 deselect('sta
256
256
 
257
257
  ## 线路拐点
258
258
 
259
- 写 `waypointEditable: true`(缺省关)后,线路的中间顶点会画出可交互的拐点:按住 Ctrl 点击线路在点击位置插入拐点,单击选中,拖动即移动顶点、线路随之变成折线,双击或选中后按 Delete 删除。节点很密时 Ctrl 点击会先比较点到节点与点到线路的屏幕距离:**线路更近就按加点处理**,比节点远才走 Ctrl 多选节点,所以贴着节点云也能给线路补拐点。通道回线加点后的高亮范围与普通点选共用 `channelSelectMode`:`'line'` 只高亮点中的一条、`'group'` 连这一组、`'channel'` 整条通道。
259
+ 打开 `features: { waypoint: true }`(缺省关)后,线路的中间顶点会画出可交互的拐点:按住 Ctrl 点击线路在点击位置插入拐点,单击选中,拖动即移动顶点、线路随之变成折线,双击或选中后按 Delete 删除。节点很密时 Ctrl 点击会先比较点到节点与点到线路的屏幕距离:**线路更近就按加点处理**,比节点远才走 Ctrl 多选节点,所以贴着节点云也能给线路补拐点。通道回线加点后的高亮范围与普通点选共用 `channelSelectMode`:`'line'` 只高亮点中的一条、`'group'` 连这一组、`'channel'` 整条通道。
260
260
 
261
261
  ### 代码选中拐点(selectWaypoint)
262
262
 
@@ -271,7 +271,7 @@ view.selectWaypoint(null) // 只取消拐点选中,不动要素选中集
271
271
 
272
272
  找不到线路或下标不是可编辑拐点时不会凭空选中一个拐点;传 `null` 只清拐点高亮,原来的要素选中集保持不动。
273
273
 
274
- 需要完全停掉拐点编辑时写 `features: { waypoint: false }` 或 `waypointEditable: false`:Ctrl + 点击、`addWaypoint` / `addWaypoints` / `addChannelGroupWaypoint`、通道右键框「加拐点」、拖动已有拐点以及双击 / Delete 删除都会一起变成无操作,数据与历史都不动。拐点只有这一道总闸,没有 `waypointAdd` 之类的细分开关;`shortcuts.addWaypoint` 只决定加点用哪个组合键,不改变总闸。运行时也可以 `view.setFeatures({ waypoint: false })`,`getFeatures().waypoint` 读当前有效值。
274
+ 需要完全停掉拐点编辑时写 `features: { waypoint: false }`:Ctrl + 点击、`addWaypoint` / `addWaypoints` / `addChannelGroupWaypoint`、通道右键框「加拐点」、拖动已有拐点以及双击 / Delete 删除都会一起变成无操作,数据与历史都不动。拐点只有这一道总闸,没有 `waypointAdd` 之类的细分开关;`shortcuts.addWaypoint` 只决定加点用哪个组合键,不改变总闸。运行时也可以 `view.setFeatures({ waypoint: false })`,`getFeatures().waypoint` 读当前有效值。
275
275
 
276
276
  拐点默认只在需要的时候出现:`waypointVisibility: 'selected'`(默认)下,只有被选中的线路(以及刚用 `addWaypoint` 加过拐点的线路)才画出拐点,点击空白处清空选中后拐点一起隐藏;改成 `'always'` 可以回到常显。`getWaypoints()` 返回当前显示中的拐点,`getWaypoints(lineIndex)` 只取某条线路的,拿到的是 `{ lineIndex, pathIndex, coordinate, selected }`。
277
277
 
@@ -279,7 +279,7 @@ view.selectWaypoint(null) // 只取消拐点选中,不动要素选中集
279
279
  const lines = [{ id: 'line-a-b', from: 'station-a', to: 'station-b' }]
280
280
 
281
281
  const view = createGisView(container, {
282
- waypointEditable: true,
282
+ features: { waypoint: true },
283
283
  waypointStyle: { size: 10, color: '#ffffff', strokeColor: '#2f6fd0', strokeWidth: 2 },
284
284
  waypointSelectStyle: { size: 15, color: '#ffd166' },
285
285
  onWaypointChange: payload => {
@@ -317,7 +317,7 @@ const view = createGisView(container, {
317
317
 
318
318
  ```js
319
319
  const view = createGisView(container, {
320
- waypointEditable: true,
320
+ features: { waypoint: true },
321
321
  waypointStyle: { size: 10, color: '#ffffff', strokeColor: '#2f6fd0', strokeWidth: 2 },
322
322
  waypointSelectStyle: { size: 15, color: '#ffd166', strokeColor: '#e0a92e' },
323
323
  waypointVisibility: 'always'
@@ -57,7 +57,7 @@ view.updateOptions({ channelWaypointDragMode: 'vertex' })
57
57
 
58
58
  ### 通道右键交互框
59
59
 
60
- 右键点中通道的回线,引擎把这条通道的上下文挂在**同一份 `onContextMenu` 载荷**的 `channelContext` 上(不再有单独的 `onChannelContextMenu` / `channelMenu`)。右键本身要接得住:`contextMenu` / `interactive: true` / `features: 'view'` 任一即可:
60
+ 右键点中通道的回线,引擎把这条通道的上下文挂在**同一份 `onContextMenu` 载荷**的 `channelContext` 上(不再有单独的 `onChannelContextMenu` / `channelMenu`)。右键本身要接得住:`features: { contextMenu: true }` / `features: 'view'` 任一即可:
61
61
 
62
62
  ```js
63
63
  view.updateOptions({
@@ -380,6 +380,7 @@ view.updateOptions({
380
380
  | `angle` | `number` | `0` | 图标自身旋转角(度) |
381
381
  | `size` | `number` | `18` | 屏幕尺寸(CSS 像素) |
382
382
  | `color` / `strokeColor` / `strokeWidth` | `string` / `string` / `number` | `'#ffffff'` / 透明 / `0` | 填充 / 描边色 / 描边宽,只对形状与 SVG 符号有意义 |
383
+ | `followSelectColor` | `boolean` | `false` | 所属节点被选中时是否跟随节点的最终选中色:SVG path 写 `strokeColor`,其他符号写 `color`;取消选中后恢复自身颜色 |
383
384
  | `opacity` / `shadow` | `number` / `GisViewShadow` | `1` / 无 | 透明度与阴影,与节点同一套 |
384
385
  | `connector` | `boolean \| object` | 不画 | `true` 用默认连接线;对象可写 `color` / `width` / `opacity` / `dashed` / `lineType` |
385
386
 
@@ -410,6 +411,8 @@ view.updateOptions({
410
411
 
411
412
  装饰在解析阶段算一次(相机变化不重算),形状符号并进圆点批次、图片与 SVG 并进贴图批次,和节点一样跟着缩放联动与整体放大系数走;`opacity`、`shadow` 与节点样式同义。`color` 是填充 / 色调(缺省白色,符号里写 `currentColor` 的取的就是它),`strokeColor` / `strokeWidth` 与节点描边同义(符号里写 `stroke: 'currentStrokeColor'` 的取它们,缺省透明、不描边),三者只在 SVG 与形状符号上有意义,位图不受影响。
412
413
 
414
+ `followSelectColor`(缺省 `false`)让装饰在所属线路 / 节点被选中时取该要素的**最终选中色**(包含 `selectStyle`、多选 / 框选等状态链的结果)。颜色落到哪个通道由 `symbol` 决定:`{ type: 'svg', path }` 写 `strokeColor`,填充仍保留装饰自己的 `color`;圆、方、图片与自定义图元等写 `color`,描边仍保留自己的 `strokeColor`。节点连接线与位图本身不跟随;取消选中后恢复装饰自身颜色。装饰还会跟着所属要素一起抬层:选中线路 / 通道回线的装饰排在抬层线路之后,选中节点与选中线路端点节点的装饰排在抬层节点之前。
415
+
413
416
  锚点默认 `auto`,分两段:线路**还没有拐点**(两个顶点)时装饰按 `position` 的比例跟随几何——挂在中间就一直是中间,拖端点、拖整条线、通道骨架重排之后都保持同一个比例;线路**有了拐点**(三个及以上顶点)之后装饰就跟拐点一样**钉在线路上**:钉住的是「加拐点那一刻」装饰在路径上的那个位置,之后拖端点、拖拐点、增删拐点都不会让它沿着路径往前挪,线路几何一变就把锚点投到当前路径上离它最近的点,所以装饰始终贴着线身,不会固定在屏幕上脱离线。把拐点删回直线又会回到比例跟随(加回拐点则重新钉在当时的位置)。`anchor: 'follow'` 强制一直按比例跟随(折线也按路径比例走);写 `anchor: 'fixed'` 加 `at: [x, y]`(或 `at: { x, y }`),或者用坐标型的 `position: [x, y]` / `{ x, y }`,装饰才钉死在**世界坐标**上、线路怎么变都不动(`at` 优先级最高,也不会贴回线)。几种写法能混挂在同一条线上(demo 里的「标记定位」下拉就是在这三种之间切)。
414
417
 
415
418
  `parallel: true` 让符号跟着线路的方向转:按锚点处的路径切向算世界角再换算成屏幕角(世界 y 轴向上、屏幕向下,取的是反角),横条一类细长的符号因此会顺着线的走向躺下去;`fixed` / `at` 这类写死坐标的锚点不在折线上时,按离它最近的一段取方向。
@@ -469,6 +472,7 @@ view.beginDecorationDraft({
469
472
  symbol: { type: 'svg', path: arrowPath, viewBox: [0, 0, 32, 16], fill: 'currentColor' },
470
473
  size: 26,
471
474
  color: '#ffd166',
475
+ followSelectColor: true, // 提交后随所属线路 / 节点选中色一起变(SVG path 走描边)
472
476
  parallel: true, // 线路图标是否跟着切向转;false = 始终水平
473
477
  scope: 'channel', // line = 单条 / group = 所在分组 / channel = 整条通道
474
478
  maxDistance: 14, // 离线路 / 节点多近才出现(屏幕像素,缺省 14)
@@ -527,6 +531,8 @@ view.cancelDecorationDraft()
527
531
 
528
532
  `connector` 只对节点装饰生效:`true` 或对象都会从节点中心画一条线到装饰中心,颜色 / 线宽 / 透明度 / 虚线可逐项覆盖。预览阶段也画这条线,提交后作为节点装饰的一部分持久化。
529
533
 
534
+ 草稿的 `followSelectColor`(缺省 `false`)会写进提交后的每条装饰;`updateDecorationDraft({ followSelectColor: false })` 可在提交前关掉。提交后它按 `symbol` 类型影响 `color` 或 `strokeColor`,节点连接线仍用各自配置。
535
+
530
536
  数量与白名单:`maxPerLine` 按 `lines[].decorations.length` 计、`maxPerPoint` 按 `points[].decorations.length` 计,`0` / 缺省表示不限;达到上限的目标按「未命中」处理,不显示临时图标、不发 `hover` / `ready`。`allowedLines` / `allowedChannels` / `allowedGroups` 是三组线路白名单,三组都为空表示全部允许;任意一组有值时,线路 id、所属通道 id、所属分组 id 命中任意一项就允许。`allowedPoints` 是节点 id 白名单,空数组表示全部允许。白名单外的目标同样静默;提交前会按最新数据与提交时的 override 再复核一次,任何一条目标超限或不在白名单都整批不写,返回 `reason: 'limit'` / `'not-allowed'`。
531
537
 
532
538
  重复判定按「同一目标上已有装饰中心的屏幕距离」算:`allowDuplicate` 缺省 `true`(保持原有行为);设为 `false` 后,距离小于 `duplicateDistance`(缺省 `8px`)就算重复。`duplicateStrategy: 'reject'` 直接静默拦下,松手也不发 `ready`,`blockedReason` 为 `'duplicate'`;`duplicateStrategy: 'offset'` 会在线路上沿路径前后、或在节点上沿当前方向向外找一个空位,再按 `anchor` 落成屏幕偏移或世界坐标,找得到就把预览与提交一起挪过去,并在载荷里给 `duplicateAdjusted: true`。`scope` 展开多条线路时,重复判定会逐条检查所有目标线。
@@ -458,7 +458,7 @@ view.updateBackground({
458
458
 
459
459
  **图片**:`src` 接受 URL、dataURL、`HTMLImageElement`、`HTMLCanvasElement`、`SVGElement`,也可以直接给 SVG 路径的 `d` 字符串 —— 组件用 `getBBox()` 量出外框包成一份 SVG,用 `pathStyle`(`fill` / `stroke` / `strokeWidth` / `padding`)上色。摆放有三档:给了 `corners`(左上 / 右上 / 右下 / 左下四个世界坐标)就按四个点做仿射 + 裁剪(任意四边形都不会画歪);否则按 `bounds`(缺省用数据范围)与 `fit` 铺 —— `contain`(默认,等比铺满)、`cover`(铺满并裁剪)、`stretch`(拉伸)、`center`(原尺寸居中,`size: [宽, 高]` 给世界单位下的尺寸,不给就按图片像素 1:1)。
460
460
 
461
- **GeoJSON**:支持 `Polygon` / `MultiPolygon`(内环挖洞)、`LineString` / `MultiLineString`、`Point` / `MultiPoint` 与 `GeometryCollection`,面在 CPU 侧完成耳切三角剖分后按颜色分桶批量画。`aspectScale` 与 ECharts 的 `geo.aspectScale` 同义(经纬度底图**默认 0.75**):只压经度(x)方向、围绕整体范围中心缩放,纬度不动 —— 经纬度看起来不会拉长;坐标不是经纬度(投影坐标 / 自定义平面坐标)时传 `1`。相机落位与 `fitView` 用的是缩放前的真实范围(`getResolvedBackground().geoJson.rawBounds`),`aspectScale` 只影响画出来的形状,主图、鹰眼图与数据仍然按真实经纬度对齐。
461
+ **GeoJSON**:支持 `Polygon` / `MultiPolygon`(内环挖洞)、`LineString` / `MultiLineString`、`Point` / `MultiPoint` 与 `GeometryCollection`,面在 CPU 侧完成三角剖分后按颜色分桶批量画。`aspectScale` 与 ECharts 的 `geo.aspectScale` 同义(经纬度底图**默认 0.75**):只压经度(x)方向、围绕整体范围中心缩放,纬度不动 —— 经纬度看起来不会拉长;坐标不是经纬度(投影坐标 / 自定义平面坐标)时传 `1`。`rawBounds` 始终保留缩放前的真实范围,`bounds` 是最终画面范围;`getGeoProjection()` / `lngLatToWorld()` 会把 `aspectScale`、`geoJsonStyle.bounds` 与 `rotation` 全部计入,事件里的 `lngLat` 与画面上的底图严格对齐。解析前会去掉闭合尾点与相邻重复点;凸环走线性扇形剖分,其余环用双向链表耳切,避免高顶点数行政区在 CPU 侧反复搬数组。
462
462
 
463
463
  **GeoJSON 目标矩形与旋转(`geoJsonStyle.bounds` / `rotation`)**:不写 `bounds` 时按 GeoJSON 自身范围铺;写了 `{ minX, minY, maxX, maxY }` 就把解析后的整份底图独立缩放、平移到这个目标世界矩形,填充、描边、点、逐区域 `bounds` / 重心和区域文字位置一起搬过去。`rotation` 再绕目标矩形中心顺时针旋转(度,缺省 0),旋转后的填充、描边、点、区域范围与文字一起转;`bounds` 与 `getResolvedBackground().geoJson.bounds` 是旋转后的轴对齐外框,原始坐标仍留在 `rawBounds`。因此可以先用数据范围渲染网架,再按四角 / 八向手柄或宽高输入调整背景,不必回写 GeoJSON;目标宽高比与原始范围不同时就按 x / y 两轴独立缩放。图片的对应写法是 `image.corners`(或 `image.bounds` + `fit`),旋转时把四角按新角度算好写回去即可。运行时直接 `updateBackground()` 或 `updateOptions({ background })`。
464
464
 
@@ -42,6 +42,8 @@ await view.exportImage({ type: 'svg', background: 'none', download: '线路图.s
42
42
 
43
43
  `type: 'svg'` 走的是矢量:线(含虚线图案与相位)、点(内置圆 / 方与 SVG 符号)、线路装饰、拐点、GeoJSON 区域、扩展图形与标签都是真正的 SVG 元素,颜色 / 线宽 / 线型取的就是当前那一帧解析出来的值(悬停、选中样式同样在内),放大到任意倍都不糊。SVG 符号里的 `currentStrokeColor` 取该节点当前解析出的描边色(逐点 / 规则 / 状态样式都算),不会再回落到白色;`currentColor` 仍取填充色。PNG / JPEG / WebP 这一支先画引擎画布,再按「扩展图形 → 标签」叠上去,所以 `graphics` 里的 SVG 路径、图片与 Canvas 都在合成结果里。两点差异:热力密度场是烘焙出来的贴图、不进 SVG;位图符号(`symbol.type: 'image'`)、自定义图元(`registerSymbol` 的画笔)与图片 / Canvas 图形都以 `<image href>` 出现(前者引用原地址,后两者是原图或 data URL),把导出的 svg 当图片内嵌到别的页面时,跨域图片与外部字体会被浏览器挡掉(直接打开 svg 文件没问题)。需要那一份矢量数据自己拼别的格式时,用 `view.snapshotScene({ background, labels })`(它已经带上 `graphics`),或用导出的 `buildSceneSvg(scene, { scale })`;`svgDataUrl(markup)` 是把一段 SVG 标记转成 `data:` URL 的小工具(组件内部导 svg 图片时走它,自己拿到标记要拼进 `<img src>` 或 CSS 时也可以直接用)。
44
44
 
45
+ SVG 与位图导出的装饰层级和屏幕一致:普通装饰先画,随后是抬层线路、抬层装饰、抬层节点;选中线路 / 通道回线、选中节点以及选中线路端点节点的装饰都会进入抬层段。装饰自身的 `followSelectColor: true` 在导出时取所属要素的最终选中色:带 `path` 的 SVG 符号写 `strokeColor`,其他符号写 `color`;节点连接线不跟随,取消选中后恢复装饰自己的颜色。
46
+
45
47
  ## 导出选中的节点
46
48
 
47
49
  框选 / 多选挑出一批节点之后,`exportSelection()` 把**这一批**(可选带上两端都在其中的线路)单独导成图片或数据 —— 出图不是把整幅画面裁一块,而是临时把不在集合里的要素藏起来、把相机对到这批要素的范围上,走 `exportImage` 的合成路径出一张刚好框住它们的图,出完立刻还原,**使用方的筛选与视角一点也不动**:
@@ -59,8 +59,8 @@
59
59
  | `getLine(ref)` | 取一条线路:拾取载荷 + `path` / `bounds` / `color` / `width`(与 `listLines()` 的一项同一份口径),通道回线还带 `channelId` / `groupId` / `channelIndex`;找不到返回 `null` |
60
60
  | `getChannel(ref)` | 取一条通道:数据那一份(`index` / `item` / `id`)+ 成员表(`lineIndexes` / `skeleton` / `offsets` / `paths`)+ 分组上下文 `context`(几组 / 当前哪组 / 每组拐点数)、`from` / `to` / `anchored` / `spacing`;找不到返回 `null` |
61
61
  | `listPoints(options?)` | 按条件列节点快照 `{ index, item, x, y, radius, layerRank, screen? }`:选项见 `GisViewListOptions`(`indexes` / `inView` / `limit` / `withScreen` / `visibleOnly`),可见判定与 `isPointVisible` 是同一份 |
62
- | `getClusters()` | 当前点聚合的展示组 `GisViewCluster[]`:按数据顺序返回,含 `count: 1` 的单成员与 `count > 1` 的聚合圆;每项带 `id` / `pointIndexes` / `pointIds` / 世界坐标 `coordinate` / 当前屏幕坐标 `screen` / 半径 `radius` / 包围盒 `bounds`。原始 `points` 与 `getData()` 不变,聚合关掉时返回空表 |
63
- | `getPointCluster()` | 当前点聚合配置的归一化读数 `GisViewPointClusterReadout \| null`:`{ enabled, radius, minPoints, maxZoom, active }`,`active` 说明当前缩放档是否仍在聚合(超过 `maxZoom` 后为 `false`);引擎未就绪时为 `null` |
62
+ | `getClusters()` | 当前点聚合的展示组 `GisViewCluster[]`:按数据顺序返回,含 `count: 1` 的单成员与 `count > 1` 的聚合圆;每项带 `id` / `pointIndexes` / `pointIds` / 世界坐标 `coordinate` / 当前屏幕坐标 `screen` / 半径 `radius` / 包围盒 `bounds`,多成员圆另带颜色、文字、`symbol`、`showCount` 与 `sizeScale` 读数。原始 `points` 与 `getData()` 不变,聚合关掉时返回空表 |
63
+ | `getPointCluster()` | 当前点聚合配置的归一化读数 `GisViewPointClusterReadout \| null`:包含 `enabled` / `radius` / `minPoints` / `maxZoom` / 颜色与描边 / 文字 / `symbol` / `showCount` / `sizeScale`,以及说明当前缩放档是否仍在聚合的 `active`(超过 `maxZoom` 后为 `false`);引擎未就绪时为 `null` |
64
64
  | `getPointSpacing()` | 当前可见节点的最近正距离与最远距离 `GisViewPointSpacing`:`minDistance` / `maxDistance` 与 `nearest` / `farthest.distance` 都是当前相机比例下的 CSS 像素,和 `fitSpacing` 同一套口径;`nearest` / `farthest` 各带两端节点 `{ id, index, item }`。筛选 / 隐藏后的节点不参与;少于两个可见节点时为 `null`,全部重合时最近为 `null`、最远为 `0` |
65
65
  | `getPointsSpacing(refs)` | 指定一组节点的整体最近正距离与最远距离 `GisViewPointSpacing`,口径与 `getPointSpacing()` 相同:`refs` 可传单个或数组的 id / 下标 / `{ id }` / `{ index }`,重复引用去重、非法引用忽略;只统计显式传入的节点,筛选 / 隐藏不影响。少于两个有效节点时两列为 `null`,全部重合时最近为 `null`、最远为 `0` |
66
66
  | `listLines(options?)` | 按条件列线路快照 `{ index, item, path, bounds, color, width, channelId?, groupId?, channelIndex?, screen? }`:选项与 `listPoints` 同一套;通道回线给的是骨架推导出来的那条折线(跟图上画的一致) |
@@ -73,12 +73,14 @@
73
73
  | `project(x, y)` / `unproject(x, y)` | 世界坐标与画布 CSS 像素互转,可用于自建标签、tooltip |
74
74
  | `clientToWorld(clientX, clientY)` / `worldToClient(x, y)` | 鼠标事件坐标 ↔ 世界坐标:自动处理 canvas rect 与宿主缩放(CSS zoom),自建 DOM 浮层 / 菜单定位用它;只要画布内像素就用 `project` / `unproject` |
75
75
  | `clientToScreen(clientX, clientY)` / `screenToClient(x, y)` | 鼠标事件坐标 ↔ 画布 CSS 像素:`pick` / `getContextAt` / `nearestPointsAt` / `setDropPreview({ screen })` 这些吃画布像素的方法先用它转;自动处理 canvas rect 与宿主缩放,别自己写 `clientX - rect.left` |
76
- | `lngLatToWorld(lng, lat)` / `worldToLngLat(x, y)` | 经纬度与画出来的世界坐标互转(含 `aspectScale`);没有 GeoJSON 背景时返回 `null`,见「经纬度与数据映射」 |
76
+ | `lngLatToWorld(lng, lat)` / `worldToLngLat(x, y)` | 经纬度与最终画出来的世界坐标互转(含 `aspectScale`、`geoJsonStyle.bounds` 与 `rotation`);没有 GeoJSON 背景时返回 `null`,见「经纬度与数据映射」 |
77
77
  | `lngLatToScreen(lng, lat)` / `screenToLngLat(x, y)` | 经纬度与画布 CSS 像素互转;没有 GeoJSON 背景时返回 `null` |
78
- | `getGeoProjection()` | 当前 GeoJSON 底图的投影 `{ rawBounds, bounds, aspectScale, center, project, unproject }`,没有 GeoJSON 背景时是 `null` |
78
+ | `getGeoProjection()` | 当前 GeoJSON 底图的投影 `{ rawBounds, bounds, aspectScale, center, scaleX, scaleY, project, unproject }`;`bounds` 与正反换算都按最终画面,没有 GeoJSON 背景时是 `null` |
79
79
  | `getPointer()` | 当前光标状态 `{ inside, screen, world, lngLat }`(`onPointer` 回调的同一份数据) |
80
- | `createGeoMapper(options?)` | 建一个「数据坐标 → GeoJSON 坐标」的映射器:`mode: 'lngLat' \| 'fit' \| 'scale'`、`scale`(节点与 geojson 的大小比例)、`scaleX` / `scaleY` / `offsetX` / `offsetY` / `center` / `bounds` / `data` / `padding` / `target`,返回 `map` / `unmap` / `mapPoint` / `mapLine` / `mapChannel` / `mapData` 与最终的比例偏移;没有 GeoJSON 背景时是 `null` |
80
+ | `createGeoMapper(options?)` | 建一个「数据坐标 → 最终 GeoJSON 坐标」的映射器:`mode: 'lngLat' \| 'fit' \| 'world' \| 'scale'`、`scale`(节点与 geojson 的大小比例)、`scaleX` / `scaleY` / `offsetX` / `offsetY` / `center` / `bounds` / `data` / `padding` / `target`,返回 `map` / `unmap` / `mapPoint` / `mapLine` / `mapChannel` / `mapData` 与最终的比例偏移;`lngLat` / `scale` 以最终底图为基准,`world` 把画出来的世界坐标反投影成真实经纬度,坐标认 `[x, y]` / `{ x, y }`,`mapLine` 同时换 `path` / `start` / `end` 与固定坐标装饰,`mapChannel` 还换分组 `path` 并把 `waypointOffsets` 当向量换算;没有 GeoJSON 背景时是 `null` |
81
81
  | `mapDataToGeoJson(data, options?)` | `createGeoMapper(options).mapData(data)` 的快捷写法:把 `{ points, lines, channels }` 整份搬到 GeoJSON 坐标,原数据不动;没有 GeoJSON 背景时是 `null` |
82
+ | `mapDataToLngLat(data?, options?)` | 把当前网架的世界坐标整份转成 GeoJSON 经纬度:不传 `data` 时取视图当前 `points` / `lines` / `channels`,使用调用当时最终画出来的底图投影(含 `aspectScale`、背景编辑后的 `bounds` 与 `rotation`);返回副本、不改原数据和视图,没有 GeoJSON 背景时是 `null`。不自动 `updateData()`,适合导出 / 持久化 |
83
+ | `mapLngLatToData(data, options?)` | `mapDataToLngLat()` 的逆变换:把 `x` / `y` 存着经纬度的 `points` / `lines` / `channels` 按当前底图投影写回世界坐标,覆盖线路端点 / 路径、固定坐标装饰、通道分组路径与 `waypointOffsets` 向量;返回副本、不改入参,没有 GeoJSON 背景时是 `null`。存档回灌写成 `view.updateData(view.mapLngLatToData(saved))`,不要直接把经纬度数据 `updateData()` |
82
84
  | `pick(x, y)` | 拾取指定画布位置上的要素(`clickable: false` 的要素不会返回) |
83
85
  | `pickAt(clientX, clientY)` | 按**浏览器客户端坐标**拾取(就是鼠标事件里的 `clientX` / `clientY`):与指针事件载荷同一份结果(`type` / `index` / `item` / `distance` / `screen` / `coordinate` 与通道字段),另加 `target`(`'point'` / `'line'` / `'channel'`)与 `lngLat`;命中通道回线时 `group` 是当前组、`groups` 是整条通道的全部分组;没命中返回 `null`。只读,不改选中也不动光标 |
84
86
  | `selectAt(clientX, clientY)` | 只有客户端坐标时用它像普通单击一样立即选中命中要素:节点 / 线路直接选中,通道回线按 `channelSelectMode` 摊开;空白或聚合圆返回 `null` 且不改选中。不等待 `selectTrigger: 'dblClick'`,也不派发 `onClick` / `onDblClick`;每次成功命中都发 `onSelect`,选中集实际变化时再发 `onSelectChange`,顺序 `onSelect → onSelectChange`(`cause: 'api'`)。选中通道回线时外层带当前 `group` / 完整 `groups` / 本次选中的 `lineIndexes` / `lineIds`,多通道看 `channelContexts`;`items[]` 不挂通道字段。已知节点 / 线路 id 时直接用 `selectFeatures`,见「按坐标单击选中(selectAt)」 |
@@ -116,14 +118,14 @@
116
118
  | `exportImage(options?)` | 导出当前画面(含标签)为图片,返回 data URL;`scope: 'viewport'`(缺省)导当前视口、`'all'` 临时适应全部可见内容,另有 `padding` / `type` / `quality` / `scale` / `background` / `includeLabels` / `download`,见「导出图片」 |
117
119
  | `exportSelection(options?)` | 把选中的节点(可选带上两端都在集合里的线路)单独导成图片 / 数据:`points` 不给就用选中集,`withLines` / `format` / `padding` 见「导出选中的节点」;返回 `{ ok, reason, points, lines, counts, box, pointIndexes, lineIndexes, image }`,出图时临时藏起别的要素与相机、出完还原 |
118
120
  | `exportData(options?)` | **统一导出入口**:`scope` 选范围(`'all'` 缺省 / `'viewport'` / `'selection'`,也认 `visible` / `view` / `selected`),`parts` 选分项,`format` 选口径。`format: 'view'`(缺省)给 `{ points, lines, channels, parallelGroups }`、`'new'` 给 new.json 口径、`'test'` 给 test.json 口径、`'input'` 给与 `createGisView` 入参同构的回灌口径、`'full'` 给标准文档;`format` 也可以是注册过的自定义名字。`points` / `lines` / `channels` 可以只要一部分;`scope: 'viewport'` 可用 `selectionKinds` 按类型或 `lineEndpoints` / `channelEndpoints` 筛。解不出的引用进 `warnings`,`format` 写错(或没注册)抛异常,见「导出数据」与「自定义导出格式」 |
119
- | `snapshotScene(options?)` | 当前画面的矢量快照(屏幕坐标的线 / 点 / 标签 / 背景 / 网格):`{ width, height, background, backgroundImage, grid, regions, lines, raisedLines, points, backgroundPoints, decorations, waypoints, raised, labels }`;`raisedLines` 是抬层的选中线路 / 通道,导出时排在普通节点之后、`raised` 端点节点之前。`options.background: false` 丢背景、`options.backgroundColor` 只换底色、`options.labels` 原样带进去,见「导出图片」 |
121
+ | `snapshotScene(options?)` | 当前画面的矢量快照(屏幕坐标的线 / 点 / 标签 / 背景 / 网格):`{ width, height, background, backgroundImage, grid, regions, lines, raisedLines, points, backgroundPoints, decorations, raisedDecorations, waypoints, raised, labels }`;`raisedLines` 是抬层的选中线路 / 通道,`raisedDecorations` 是跟着选中线路 / 节点 / 选中线路端点抬层的普通装饰。导出次序为普通装饰 → `raisedLines` → `raisedDecorations` → `raised`,见「导出图片」 |
120
122
  | `addGraphic(graphic)` / `addGraphics(list)` | 新增扩展图形(SVG 路径 / 图片 / Canvas):世界坐标锚点、屏幕像素尺寸、可选 `scaleWithView`;`id` 已存在时按更新覆盖,返回归一化后的图形读数(批量返回数组)。只动图形层 DOM,不重排场景,见「扩展图形(graphic)」 |
121
123
  | `updateGraphic(id, patch)` | 改一个扩展图形的字段(写法与创建一致,`style.width` / `style.height` 也认),返回改后的读数;id 不存在返回 `null` |
122
124
  | `removeGraphic(id)` / `clearGraphics()` | 按 id 删一条 / 清空整层,分别返回删掉的读数与清掉的条数;节点 / 线路 / 标签 / 数据 / 历史都不受影响 |
123
125
  | `getGraphic(id)` / `getGraphics()` | 读一条(副本,没有时 `null`)/ 读全部(按 `zIndex`、再按加入顺序) |
124
126
  | `isLayoutReady()` | 大数据发散渲染(`progressiveInit`)是否已经铺完;没铺完时标签与拾取只看得到已经画出去的那部分 |
125
127
  | `addWaypoint(lineId, coordinate)` / `removeWaypoint(lineId, pathIndex)` | 在线路上增删拐点,等价于 Ctrl + 点击与 Delete |
126
- | `addWaypoints(lineId, coordinates, options?)` | 一次给一条线路插多个拐点:按数组顺序插入,多个拐点合成**一步历史**;坐标写错的条目跳过,返回 `{ ok, reason, count, lineId, lineIndex, coordinates }`(`coordinates` 是实际插入成功的坐标),线路不存在或 `waypointEditable: false` 时 `ok: false` |
128
+ | `addWaypoints(lineId, coordinates, options?)` | 一次给一条线路插多个拐点:按数组顺序插入,多个拐点合成**一步历史**;坐标写错的条目跳过,返回 `{ ok, reason, count, lineId, lineIndex, coordinates }`(`coordinates` 是实际插入成功的坐标),线路不存在、`features.waypoint` 关闭或线路自身 `waypointEditable: false` 时 `ok: false` |
127
129
  | `getWaypoints(lineIndex?)` | 当前显示中的拐点(受 `waypointVisibility` 影响),带坐标与选中态 |
128
130
  | `getChannels()` | 通道运行时状态:骨架折线、每条回线的偏移、推导出来的折线路径与间距;分组之后一个通道的每个分组各一条,带 `channelId` / `channelIndex`(`id` 是分组 id) |
129
131
  | `getChannelMembers()` | 通道成员表:每个通道的成员下标与各自的偏移(不复制几何),需要按通道分流绘制时用它(内置鹰眼图的「一个通道只画一根」按它取当前可见成员,再逐顶点平均成居中代表线) |
@@ -214,7 +216,7 @@
214
216
 
215
217
  返回 `this` 的方法都可以链式调用;返回其它值的方法(`get*` / `list*` / `export*` / `search` / `pick*` / `add*` / `remove*` / `copy*` / `merge*` / `locate*` 这些)在下面的「参数速查」里都标了返回类型。
216
218
 
217
- **怎么确认「组件到底有哪些属性和方法」**:上面那张表按功能分组介绍,下面的「参数速查」把实例的**每个成员逐个列全**(当前 283 个:278 个方法 + `el` / `canvas` / `gl` / `cameraAnimating` / `dataVersion` 五个属性),`options` 表列全 107 个键,数据字段表列全 275 个字段。这三份清单不靠手抄 —— 它们直接对着源码与类型对账:
219
+ **怎么确认「组件到底有哪些属性和方法」**:上面那张表按功能分组介绍,下面的「参数速查」把实例的**每个成员逐个列全**(当前 285 个:280 个方法 + `el` / `canvas` / `gl` / `cameraAnimating` / `dataVersion` 五个属性),`options` 表列全 100 个键,数据字段表列全 275 个字段。这三份清单不靠手抄 —— 它们直接对着源码与类型对账:
218
220
 
219
221
  - `node docs/steps/gen-api-index.mjs`:从 `create.js` 的实例定义、本文档的两张表与 `types.d.ts` 生成 `docs/demos/api-index.js`(`API_SUMMARY` / `API_SECTIONS` / `API_OPTIONS`),demo 的「实例 API」面板读的就是它;带 `--check` 时只校验有没有过期。
220
222
  - `node docs/steps/check-doc-coverage.mjs`:10 项双向对账 —— 实例成员 × 文档 / × 参数速查 / × 类型、参数速查与实例方法主表各自反查过期条目、类型反查过期声明、数据字段 × 文档、options 键 × 文档 / options 表反查过期字段、demo 清单是否最新。加一个实例成员、改一个 options 键而没同步文档时,这个脚本会直接报出来。
@@ -1,6 +1,6 @@
1
1
  # 17 参数速查(逐个方法)
2
2
 
3
- > 283 个成员逐个列全:参数、默认值与返回。
3
+ > 285 个成员逐个列全:参数、默认值与返回。
4
4
 
5
5
  ### 参数速查(逐个方法)
6
6
 
@@ -114,8 +114,10 @@
114
114
  | `worldToLngLat(x, y)` | `x` / `y` 世界坐标 | `[number, number] \| null` |
115
115
  | `lngLatToScreen(lng, lat)` | `lng` / `lat` 经纬度 | `[number, number] \| null` |
116
116
  | `screenToLngLat(x, y)` | `x` / `y` 画布 CSS 像素 | `[number, number] \| null` |
117
- | `createGeoMapper(options?)` | `options?: GisViewGeoMapperOptions` 映射选项(`lngKey` / `latKey` / `xKey` / `yKey` / `fit`…) | `GisViewGeoMapper \| null` |
118
- | `mapDataToGeoJson(data, options?)` | `data: { points?, lines?, channels? }` 要搬的数据;`options?: GisViewGeoMapperOptions` 同 `createGeoMapper` | `GisViewPoint[]; lines?: GisViewLine[]; channels?: GisViewChannel[] }>(data: T, options?: GisViewGeoMapperOptions): T \| null` |
117
+ | `createGeoMapper(options?)` | `options?: GisViewGeoMapperOptions` 映射选项(`mode: 'lngLat' \| 'fit' \| 'world' \| 'scale'` / `scale` / `scaleX` / `scaleY` / `offsetX` / `offsetY` / `center` / `bounds` / `data` / `padding` / `target`) | `GisViewGeoMapper \| null` |
118
+ | `mapDataToGeoJson(data, options?)` | `data: { points?, lines?, channels? }` 要搬的数据;`options?: GisViewGeoMapperOptions` 同 `createGeoMapper` | `{ points?, lines?, channels? }` 的复制(泛型 `T`);没有 GeoJSON 背景时 `null` |
119
+ | `mapDataToLngLat(data?, options?)` | `data?: { points?, lines?, channels? }` 不传时取视图当前数据;`options?: GisViewGeoMapperOptions` 额外比例 / 偏移,`mode` 固定为 `'world'` | `{ points?, lines?, channels? }` 的复制(泛型 `T`);没有 GeoJSON 背景时 `null` |
120
+ | `mapLngLatToData(data, options?)` | `data: { points?, lines?, channels? }` 的经纬度存档;`options?: GisViewGeoMapperOptions` 额外比例 / 偏移,`mode` 固定为 `'lngLat'` | `{ points?, lines?, channels? }` 按当前底图投影写回世界坐标后的复制(泛型 `T`);没有 GeoJSON 背景时 `null`。常用 `view.updateData(view.mapLngLatToData(saved))` |
119
121
  | `getPointer()` | 无 | `GisViewPointerState` |
120
122
  | `pick(x, y)` | `x` / `y` 画布 CSS 像素 | `GisViewPickResult \| null` |
121
123
  | `pickAt(clientX, clientY)` | `clientX` / `clientY` 浏览器客户端坐标(`PointerEvent` 原样给);命中通道回线时返回的 `group` 是当前组、`groups` 是整条通道的全部分组 | `GisViewPickAtResult \| null` |
@@ -135,8 +137,8 @@
135
137
  | `getLine(ref)` | `ref` 线路定位(同上) | `GisViewLineInfo \| null` |
136
138
  | `getChannel(ref)` | `ref` 通道定位:通道 id 或下标 | `GisViewChannelSnapshot \| null` |
137
139
  | `listPoints(options?)` | `options?: GisViewListOptions`:`indexes`(只看这几个下标)、`inView: boolean`(只要当前视口里的)、`withScreen: boolean`(连屏幕坐标一起给)、`visibleOnly: boolean`(跳过隐藏的)、`limit: number`(最多几条) | `GisViewListedPoint[]` |
138
- | `getClusters()` | 无 | `GisViewCluster[]`。当前展示组(含 `count: 1` 的单成员),`screen` 按当前相机现算;聚合关掉时是空表 |
139
- | `getPointCluster()` | 无 | `GisViewPointClusterReadout \| null`。归一化配置 `{ enabled, radius, minPoints, maxZoom, active }`,超过 `maxZoom` 后 `active` 为 `false` |
140
+ | `getClusters()` | 无 | `GisViewCluster[]`。当前展示组(含 `count: 1` 的单成员),`screen` 按当前相机现算;多成员项另带 `color` / `strokeColor` / `strokeWidth` / `opacity` / `textColor` / `fontSize` / `symbol` / `showCount` / `sizeScale`,聚合关掉时是空表 |
141
+ | `getPointCluster()` | 无 | `GisViewPointClusterReadout \| null`。归一化配置 `{ enabled, radius, minPoints, maxZoom, color, strokeColor, strokeWidth, opacity, textColor, fontSize, symbol, showCount, sizeScale, active }`,超过 `maxZoom` 后 `active` 为 `false` |
140
142
  | `getPointSpacing()` | 无 | `GisViewPointSpacing`:`count` / `minDistance` / `maxDistance` / `nearest` / `farthest`。距离都是当前相机比例下的 CSS 像素,与 `fitSpacing` 同一套口径;最近 / 最远各带两端 `{ id, index, item }` 与同口径的 `distance`,筛选 / 隐藏后的节点不参与 |
141
143
  | `getPointsSpacing(refs)` | `refs: GisViewFeatureRef \| GisViewFeatureRef[]`:一个或一组节点引用,支持 id / 下标 / `{ id }` / `{ index }`;重复引用去重,非法引用忽略 | `GisViewPointSpacing`:只统计显式传入的节点(筛选 / 隐藏不影响),`count` / `minDistance` / `maxDistance` / `nearest` / `farthest` 与 `getPointSpacing()` 同形,距离为当前相机比例下的 CSS 像素;少于两个有效节点时两列为 `null`,全部重合时最近为 `null`、最远为 `0` |
142
144
  | `listLines(options?)` | 同 `listPoints`(`inView` / `withScreen` / `limit`…) | `GisViewListedLine[]` |
@@ -270,6 +272,8 @@
270
272
  | `setChannelColors(id, colors)` | `id` 通道 id;`colors: string \| string[]` 逐回线颜色(不够时按顺序循环取;只给一个就整条通道一个色;改单条回线颜色用 `updateLine(id, { color })`) | `this` |
271
273
  | `setChannelPath(id, coordinates)` | `id` 通道 id;`coordinates: GisViewCoordinate \| GisViewCoordinate[]` 新的**骨架中间拐点**(不含首尾两端;传空数组 = 拉直、传单个坐标 = 只留一个拐点;两端由节点坐标补齐,与通道数据里的 `path` 同义);分过组的通道写第一组的声明(`groups[].path` 优先于顶层 `path`;「第一组」跳过 `lineIds` 空的分组声明),要改别的组用 `updateChannel(id, { groups })` | `this` |
272
274
 
275
+ 动态装饰草稿的 `GisViewDecorationDraftOptions` 还认 `followSelectColor`(缺省 `false`):打开后,提交的每条装饰在所属线路 / 节点被选中时改用其最终选中色;带 `path` 的 SVG 符号写入 `strokeColor`,其他符号写入 `color`,取消选中后恢复装饰自身颜色。`beginDecorationDraft` / `updateDecorationDraft` / `commitDecorationDraft` 都可以设置或覆盖它。
276
+
273
277
  #### 增删数据
274
278
 
275
279
  | 方法 | 参数(名字: 类型 = 默认值) | 返回 |
@@ -346,8 +350,8 @@
346
350
  | `registerCommand(name, handler)` | `name: string` 命令名(会 `trim`,同名覆盖);`handler: GisViewCommandHandler<Payload, Value>` 同步处理函数 `(payload, context) => Value`,`context` 是冻结的 `{ view, name }`。名字为空 / handler 不是函数只报 warning、不注册;视图销毁后注册不生效 | `() => void` 幂等注销;同名已被后来者覆盖时不会误删新命令 |
347
351
  | `executeCommand(name, payload?)` | `name: string` 命令名;`payload?: unknown` 原样交给 handler | `GisViewCommandResult<Value>`:`{ ok: true, name, value }` / `{ ok: false, name, reason }`(未注册 / 名字非法 / handler 抛错) |
348
352
  | `listCommands()` | 无 | `string[]` 当前注册的命令名(按注册顺序;同名覆盖不改变位置) |
349
- | `getFeatures()` | 无 | `GisViewFeatureState` |
350
- | `setFeatures(next)` | `next: GisViewFeaturesInput` 预设名(`'edit'` / `'view'` / `'static'`)、开关表或布尔值 | `this` |
353
+ | `getFeatures()` | 无 | `GisViewFeatureState`:19 个规范开关的有效值,不返回别名键 |
354
+ | `setFeatures(next)` | `next: GisViewFeaturesInput` 预设名(`'edit'` / `'view'` / `'static'`)、开关对象或布尔值;对象可写 `draggable` / `waypointEditable` / `interactive` / `clickable` 别名,规范名优先 | `this` |
351
355
  | `undo()` / `redo()` | 无:从撤销 / 重做栈回放一步;会同步数据、发 `history` 与对应的 `dataChange`,并更新相机以外的运行时读数 | `GisViewHistoryStepResult \| null`:`{ type, label, canUndo, canRedo }`;历史没开或栈空时为 `null` |
352
356
  | `canUndo()` / `canRedo()` | 无:只读当前两份栈,不触发历史变化;界面据此灰掉按钮 | `boolean` |
353
357
  | `getHistoryState()` | 无 | `GisViewHistoryState` |
@@ -43,9 +43,10 @@ view.updatePointStyle(null)
43
43
  // updateLineStyle(patch):与 updatePointStyle 同一套
44
44
  view.updateLineStyle({ width: 3, color: '#1e88e5' })
45
45
 
46
- // getOptions() / getData():整份配置 / 整份数据(读到的就是组件内部那一份,只读)
46
+ // getOptions() / getData():整份配置 / 点线数据(读到的就是组件内部那一份,只读)
47
47
  const options = view.getOptions()
48
- const { points, lines, channels } = view.getData()
48
+ const { points, lines } = view.getData()
49
+ const channels = options.channels // 通道声明从 getOptions() 读,不在 getData() 里
49
50
 
50
51
  // updateData(points, lines, channels?):整份换数据,不传的那一列不动
51
52
  view.updateData(nextPoints, nextLines) // 只换点与线
@@ -343,10 +344,10 @@ const [fixedX, fixedY] = view.screenToClient(320, 200) // 摆 position: fixed
343
344
  const worldUnderCursor = view.clientToWorld(640, 360)
344
345
  const [clientX, clientY] = view.worldToClient(120, 80) // 摆 DOM 浮层 / 菜单
345
346
 
346
- // getGeoProjection():底图的投影读数(没配 GeoJSON 背景时返回 null)
347
+ // getGeoProjection():最终底图的投影读数,已含 aspectScale / bounds / rotation(没配 GeoJSON 背景时返回 null)
347
348
  const geo = view.getGeoProjection()
348
349
 
349
- // lngLatToWorld(lng, lat) / worldToLngLat(x, y):经纬度 ↔ 世界坐标(没配 GeoJSON 时返回 null)
350
+ // lngLatToWorld(lng, lat) / worldToLngLat(x, y):经纬度 ↔ 最终世界坐标(没配 GeoJSON 时返回 null)
350
351
  const worldOfLngLat = view.lngLatToWorld(116.4, 39.9)
351
352
  const lngLatOfWorld = view.worldToLngLat(120, 80)
352
353
 
@@ -354,12 +355,20 @@ const lngLatOfWorld = view.worldToLngLat(120, 80)
354
355
  const screenOfLngLat = view.lngLatToScreen(116.4, 39.9)
355
356
  const lngLatUnder = view.screenToLngLat(320, 200)
356
357
 
357
- // createGeoMapper(options?) / mapDataToGeoJson(data, options?):把另一套坐标系的数据搬到画出来的底图上
358
+ // createGeoMapper(options?) / mapDataToGeoJson(data, options?):把另一套坐标系的数据搬到最终底图上
358
359
  const mapper = view.createGeoMapper({ mode: 'fit', padding: 0.08, data })
359
360
  const mappedPoints = view.getData().points.map(mapper.mapPoint)
360
361
  const mapped = view.mapDataToGeoJson({ points, lines, channels }, { mode: 'fit' })
361
362
  view.updateData(mapped.points, mapped.lines)
362
363
 
364
+ // mapDataToLngLat(data?, options?):背景编辑之后把网架当前世界坐标整份转成 GeoJSON 经纬度
365
+ const lngLatData = view.mapDataToLngLat() // 不传 data = 当前 points / lines / channels;返回副本
366
+ const lngLatSubset = view.mapDataToLngLat({ points: view.getData().points }) // 只转其中一列
367
+
368
+ // mapLngLatToData(data, options?):mapDataToLngLat 的逆变换,把保存的经纬度按当前底图写回世界坐标
369
+ const restoredData = view.mapLngLatToData(lngLatData)
370
+ view.updateData(restoredData) // 底图未变时世界坐标与屏幕落点原样恢复
371
+
363
372
  // getPointer():光标位置读数 { inside, screen, world, lngLat }
364
373
  const pointer = view.getPointer()
365
374
 
@@ -405,10 +414,19 @@ const listedPoints = view.listPoints({ limit: 5, withScreen: true })
405
414
  const listedLines = view.listLines({ inView: true, limit: 200 })
406
415
 
407
416
  // getClusters() / getPointCluster():读当前点聚合展示组与配置
408
- view.updateOptions({ pointCluster: { radius: 64, minPoints: 3, maxZoom: 2.5 } })
417
+ view.updateOptions({ pointCluster: {
418
+ radius: 64,
419
+ minPoints: 3,
420
+ maxZoom: 2.5,
421
+ color: '#f05a3c',
422
+ strokeColor: '#1b3358',
423
+ textColor: '#ffe08a',
424
+ symbol: 'square',
425
+ sizeScale: 1.2
426
+ } })
409
427
  const clusterGroups = view.getClusters()
410
428
  const clusteredOnly = clusterGroups.filter(group => group.count > 1)
411
- const clusterState = view.getPointCluster() // { enabled, radius, minPoints, maxZoom, active }
429
+ const clusterState = view.getPointCluster() // 归一化配置 + active
412
430
  view.updateOptions({ pointCluster: false }) // 关掉聚合,原始点全部恢复
413
431
 
414
432
  // getPointSpacing():最近 / 最远可见节点的中心距(CSS 像素,和 fitSpacing 同口径)
@@ -547,7 +565,7 @@ view.selectInBox({ x: 40, y: 40, width: 320, height: 200 }, { selectionKinds: ['
547
565
  view.selectInBox({ x: 40, y: 40, width: 320, height: 200 }, { selectionKinds: ['lineEndpoints', 'channelEndpoints'] }) // 两端节点都在框内才命中
548
566
 
549
567
  // selectionBoxPersistent / clearSelectionBox() / getSelectionBox():框选后保留矩形,并手动收起
550
- view.updateOptions({ boxSelect: true, draggable: true, selectionBoxPersistent: true })
568
+ view.updateOptions({ features: { boxSelect: true, drag: true }, selectionBoxPersistent: true })
551
569
  view.selectInBox({ x: 40, y: 40, width: 320, height: 200 })
552
570
  const retainedBox = view.getSelectionBox() // 画布 CSS 像素;没有框时为 null
553
571
  view.clearSelectionBox() // 只收框,不改选中集
@@ -1253,6 +1271,7 @@ disposeCommand() // 幂等卸载;同名覆盖时不
1253
1271
  const features = view.getFeatures()
1254
1272
  view.setFeatures('view') // 'edit' / 'view' / 'static'
1255
1273
  view.setFeatures({ drag: false }) // 单个开关
1274
+ view.setFeatures({ draggable: true, multiSelect: true }) // features 内的详细配置别名也能收口
1256
1275
  view.setFeatures(true) // 全开
1257
1276
 
1258
1277
  // undo() / redo() / canUndo() / canRedo():撤销 / 重做(历史的开关在 options.history)
@@ -37,17 +37,32 @@ view.updateOptions({ denseMode: 'off' }) // 手动退出
37
37
  ```js
38
38
  const view = createGisView(container, {
39
39
  points,
40
- pointCluster: { radius: 64, minPoints: 3, maxZoom: 2.5 }
40
+ pointCluster: {
41
+ radius: 64,
42
+ minPoints: 3,
43
+ maxZoom: 2.5,
44
+ color: '#f05a3c',
45
+ strokeColor: '#1b3358',
46
+ strokeWidth: 3,
47
+ opacity: 0.85,
48
+ textColor: '#ffe08a',
49
+ fontSize: 16,
50
+ symbol: 'square',
51
+ showCount: true,
52
+ sizeScale: 1.2
53
+ }
41
54
  })
42
55
 
43
56
  const groups = view.getClusters() // 含 count: 1 的单成员
44
- const state = view.getPointCluster() // { enabled, radius, minPoints, maxZoom, active }
57
+ const state = view.getPointCluster() // 归一化配置 + active
45
58
  const stats = view.getStats() // clusterCount / clusterPointCount / clusterMs
46
59
  ```
47
60
 
48
- - **配置**:写 `true` 用缺省档(半径 60px、至少 2 点、不限缩放);`radius` 夹在 8 ~ 500 CSS 像素,`minPoints` 最小 2,`maxZoom: 0` 表示不限。`maxZoom` 以 `zoomBaseScale`(或首次适应视图比例)为基准,相机比例超过它之后 `active` 变 `false`,原始点逐点展开。
61
+ - **配置**:写 `true` 用缺省档(半径 60px、至少 2 点、不限缩放,蓝色圆 / 白色 2px 描边 / 白色数量文字);`radius` 夹在 8 ~ 500 CSS 像素,`minPoints` 最小 2,`maxZoom: 0` 表示不限。`maxZoom` 以 `zoomBaseScale`(或首次适应视图比例)为基准,相机比例超过它之后 `active` 变 `false`,原始点逐点展开。
62
+ - **外观**:`color` / `strokeColor` / `strokeWidth` / `opacity` 控制整体,`textColor` / `fontSize` 控制数量文字(`fontSize: 0` 按半径自动取 12 / 13px),`showCount: false` 同时关闭覆盖层数量 DOM 与 SVG 导出文字。`symbol` 可用内置 `circle` / `square`、SVG、图片地址或注册的自定义图元;SVG 的 `currentColor` 取 `color`。`sizeScale` 同时缩放展示半径与拾取半径,范围 0.25 ~ 4。
49
63
  - **算法开销**:`cluster.js` 用固定网格把邻近点串成组,每个点只查相邻 9 格,单次建组按总量 O(n);成员用 `Int32Array` 链表保存,稀疏场景不为每个单成员常驻组对象。平移不重算聚合;缩放只在聚合启用且当前档有成员要重算 / 收掉时标脏,关闭聚合或当前缩放档本来就不聚合时不会触发整份场景重排。数据、配置、选中集、拖动、合并与复制预览变化仍立即标脏。相机缩放引起的成员变化在运动期先沿用上一档,停稳后自动重算一次。
50
- - **绘制与拾取**:聚合圆单独一批绘制,原始点批次过滤掉被收起的成员;标签、涟漪、节点装饰、框选、最近点查询与 `listPoints({ visibleOnly: true })` 都按同一份隐藏集合跳过内部点。`getClusters()` 为需要全量展示组的场景补上单成员,绘制与数量文字只遍历多成员圆。
64
+ - **绘制与拾取**:形状聚合圆走独立圆点批次,SVG / 图片 / 自定义图元走独立贴图批次;两者都画在普通点之上、线路拐点之下。原始点批次过滤掉被收起的成员;标签、涟漪、节点装饰、框选、最近点查询与 `listPoints({ visibleOnly: true })` 都按同一份隐藏集合跳过内部点。`getClusters()` 为需要全量展示组的场景补上单成员,多成员项同时带颜色、文字与图标读数;绘制与数量文字只遍历多成员圆。贴图异步解出来后会自动重排一次,首次出现不会一直占着空批次。
65
+ - **快照与导出**:`snapshotScene()` 与 SVG 导出复用同一份聚合样式,输出填充 / 描边 / 透明度、实际数量字号、自定义图标和 `showCount` 口径,不会出现画面有图标、导出只剩默认圆的分叉。
51
66
  - **隔离规则**:选中点、拖动成员、待确认合并的源 / 目标以及复制预览强制保持单成员,避免用户点不到正在操作的点;聚合开启时发散初始化与追加式补点暂停,保证成员关系按整份数据一次算清。
52
67
  - **和 denseMode 的关系**:两者可以同时开。`denseMode` 管标签避让 / 截断与动画降级,`pointCluster` 管点本身的展示密度;只希望更快但仍要看每个符号时可关掉聚合,只保留密集档。十万点档在 `?demo=cluster` 可现场切 2 千 / 1.2 万 / 6 万 / 10 万,并观察聚合耗时与展示项数。
53
68
 
@@ -159,6 +174,14 @@ const stats = view.getStats() // clusterCount / clusterPointCount /
159
174
 
160
175
  同一轮加的 `graphics` 扩展图形层是**独立 DOM 覆盖层**:只读相机做投影,增删改不触发引擎重排;锚点随相机平移 / 缩放,尺寸默认保持屏幕像素,`scaleWithView: true` 才按相机比例缩放。图形位于画布之上、标签之下,不参与拾取;如果只是少量线框 / 图片 / Canvas,开销与这几十个 DOM 节点相当,不会把十万级要素的渲染批次重新排一遍。
161
176
 
177
+ ### GeoJSON 三角剖分与投影
178
+
179
+ - **环归一化**:GeoJSON 常见的闭合尾点、相邻重复点与非法坐标在进入剖分前去掉,描边不会再重复铺同一段,耳朵裁剪也不会被退化顶点拖慢。
180
+ - **凸环快路径**:所有拐点同向的外环直接做 O(n) 扇形剖分;6000 点凸环的 Node 微基准从约 **49 ~ 66 ms** 降到 **4.5 ~ 10.8 ms**(同进程重复 3 次,含首次 JIT),输出三角形数量一致。
181
+ - **凹环与洞**:其余环用 prev / next 双链表维护活动顶点,摘耳朵不再 `splice` 整个数组;检查三角形内点前先按包围盒剔除,带洞面仍先桥接再剖分。6000 点交替半径凹环在本机约 **31.6 ms**。
182
+ - **旋转不逐点算三角函数**:`geoJsonStyle.bounds / rotation` 的 cos / sin 在整份底图解析时只算一次,所有填充、描边、点与原始环共用。
183
+ - **投影跟随最终底图**:`drawTransform` 记录最终缩放 / 平移 / 中心 / 角度,`getGeoProjection()`、事件 `lngLat` 与 `createGeoMapper()` 的 `lngLat` / `scale` 档都按最终画面换算,不再只按 `aspectScale` 推算。
184
+
162
185
  ### 保留框 DOM 跟随与拾取热路径
163
186
 
164
187
  - **div 保留框只写必要属性**:保留框同时保存屏幕矩形与世界范围。相机平移 / 缩放后,引擎在渲染帧从世界范围重投影;DOM 位置只写 `transform: translate3d(...)`,宽高只在缩放或拖动改变时更新,颜色 / 线型 / 类名按样式版本缓存,平移过程中不再重复写。`getSelectionBox()` 与框内命中判断也先同步到当前相机,所以读数、DOM 与框内拖动不会停在旧位置。
@@ -22,16 +22,16 @@
22
22
  ## 常见问题
23
23
 
24
24
  **拖也拖不动、点了没反应,是不是坏了?**
25
- 不是 —— 功能开关**缺省全关**(见「功能开关」),什么都不写时组件只剩画布与数据。查看类要手动打开:详细配置写 `interactive: true`(平移 / 缩放 / 右键回调)与 `clickable: true`(悬停 / 选中),或一步写 `features: 'view'`;改数据的交互各有自己的开关 —— `boxSelect: true` / `draggable: true` / `waypointEditable: true` / `copy: {…}` / `merge: {…}` / `remove: {…}` / `features: { channelDraft: true }`,`features: 'edit'` 一次全开。标签 / 提示 / 鹰眼图不在开关表里:配了 `label` / `lineLabel` / `tooltip` / `minimap` 就等于打开。
25
+ 不是 —— 功能开关**缺省全关**(见「功能开关」),什么都不写时组件只剩画布与数据。交互权限只有 `features` 一个入口:查看类写 `features: { pan: true, zoom: true, hover: true, select: true, contextMenu: true }` 或一步写 `features: 'view'`;拖动 / 框选 / 拐点分别写 `features: { drag: true }` / `{ boxSelect: true }` / `{ waypoint: true }`;复制 / 合并 / 删除写 `copy: {…}` / `merge: {…}` / `remove: {…}`;通道草稿写 `features: { channelDraft: true }`;`features: 'edit'` 一次全开。旧的顶层 `interactive` / `clickable` / `boxSelect` / `waypointEditable` 等已移除。标签 / 提示 / 鹰眼图不在开关表里:配了 `label` / `lineLabel` / `tooltip` / `minimap` 就等于打开。
26
26
 
27
27
  **想先铺网架、再对背景,或者只把网架压淡怎么办?**
28
28
  网架展示用两个顶层配置:`networkVisible: false` 真正跳过节点、线路、通道、装饰、动画与标签(拾取和框选也停止命中),但背景、网格与自定义 WebGL 层照常显示;`networkOpacity` 在逐要素透明度之后再乘一层,适合保留交互、只把网架压淡来对底图。背景图片用 `image.corners` 摆四角(旋转后重算四角即可),GeoJSON 用 `geoJsonStyle.bounds` 独立缩放 / 平移,并用 `geoJsonStyle.rotation` 绕目标矩形中心顺时针旋转,二者都不改原始数据。对底图直接用内置编辑器:`view.beginBackgroundEdit({ aspectLocked: true, rotation: true, fitView: true })` 会自动锁住网架交互(滚轮缩放与 `fitView` 仍可用),拖动手柄即可移动、缩放和旋转;样式用同一调用的 `style` 或后续 `updateBackgroundEdit({ style })` 改,`getBackgroundEdit()` 读当前状态,`endBackgroundEdit()` 退出并恢复进入前的 `backgroundEditing`。完整演示见 `?demo=background`:锁比例时四个角点,解锁后八个缩放手柄,勾选“允许旋转”才出现第九个旋转点。
29
29
 
30
30
  **怎么一次关闭 / 打开组件的所有功能?**
31
- `features: false` 或 `features: 'static'` 会把全部 17 个功能开关关掉(`'static'` 连已配置的标签 / 提示 / 鹰眼图也一起关;`false` 与缺省全关同一口径);`features: true` 或 `features: 'edit'` 全部打开。运行时用 `view.setFeatures(...)` 换、`view.getFeatures()` 读 17 项有效值,完整清单见「功能开关」一节的表。
31
+ `features: false` 或 `features: 'static'` 会把全部 19 个功能开关关掉(`'static'` 连已配置的标签 / 提示 / 鹰眼图也一起关;`false` 与缺省全关同一口径);`features: true` 或 `features: 'edit'` 全部打开。运行时用 `view.setFeatures(...)` 换、`view.getFeatures()` 读 19 项有效值,完整清单见「功能开关」一节的表。`features` 的开关表是对象(`{ drag: true, multiSelect: true }`),不是数组;`draggable` / `waypointEditable` / `interactive` / `clickable` 可以作为别名写进对象,但 `getFeatures()` 只返回规范键。
32
32
 
33
33
  **怎么完全关闭拐点的新增、移动和删除?**
34
- 写 `features: { waypoint: false }` 或 `waypointEditable: false`。`waypoint` 就是拐点编辑的唯一总闸:新增(Ctrl + 点击、`addWaypoint` / `addWaypoints` / `addChannelGroupWaypoint`、通道右键框「加拐点」)、移动已有拐点、双击 / Delete 删除会同时停掉,没有只关其中一项的细分开关。已有拐点仍可显示和选中,数据与历史都不动。
34
+ 写 `features: { waypoint: false }`。`waypoint` 就是拐点编辑的唯一总闸:新增(Ctrl + 点击、`addWaypoint` / `addWaypoints` / `addChannelGroupWaypoint`、通道右键框「加拐点」)、移动已有拐点、双击 / Delete 删除会同时停掉,没有只关其中一项的细分开关。已有拐点仍可显示和选中,数据与历史都不动。
35
35
 
36
36
  **怎么在图上加一段与节点无关的线框、图标或 Canvas?**
37
37
  用顶层 `graphics` 或 `addGraphic`:`type` 写 `'path'` / `'image'` / `'canvas'`,`x` / `y` 是世界坐标,`width` / `height` 默认是屏幕像素;想让它随相机一起放大缩小就写 `scaleWithView: true`。图形层在画布之上、标签之下,不参与拾取,PNG / SVG 导出都会带上,增删改用 `addGraphic` / `updateGraphic` / `removeGraphic` / `clearGraphics`。