gis-view-webgl2 0.1.2 → 0.1.4

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 (66) hide show
  1. package/README.md +21 -14
  2. package/dist/gis-view.css +1 -1
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +421 -76
  5. package/dist/index.js +1 -1
  6. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +151 -0
  7. package/docs/GisView.md +4 -4
  8. 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 +137 -0
  9. package/docs/api/index.html +23 -9
  10. package/docs/demos/ApiCoverageDemo.vue +644 -0
  11. package/docs/demos/ChannelDemo.vue +14 -12
  12. package/docs/demos/ClusterDemo.vue +251 -0
  13. package/docs/demos/CopyMergeDemo.vue +3 -4
  14. package/docs/demos/EventDemo.vue +3 -3
  15. package/docs/demos/ExtensionDemo.vue +69 -12
  16. package/docs/demos/GisViewDemo.vue +64 -8809
  17. package/docs/demos/SearchFilterDemo.vue +13 -11
  18. package/docs/demos/api-index.js +58 -47
  19. package/docs/demos/demos.js +34 -17
  20. package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +29 -0
  21. package/docs/demos/gis-view-demo/GisViewDemoDialogs.vue +82 -0
  22. package/docs/demos/gis-view-demo/GisViewDemoFooter.vue +16 -0
  23. package/docs/demos/gis-view-demo/GisViewDemoHeading.vue +16 -0
  24. package/docs/demos/gis-view-demo/GisViewDemoPanelStack.vue +71 -0
  25. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +219 -0
  26. package/docs/demos/gis-view-demo/demo.css +1230 -0
  27. package/docs/demos/gis-view-demo/demoContext.js +12 -0
  28. package/docs/demos/gis-view-demo/panels/DataPanels.vue +303 -0
  29. package/docs/demos/gis-view-demo/panels/OpsPanels.vue +174 -0
  30. package/docs/demos/gis-view-demo/panels/StylePanels.vue +561 -0
  31. package/docs/demos/gis-view-demo/panels/ViewPanels.vue +396 -0
  32. package/docs/demos/gis-view-demo/useGisViewDemo.js +6188 -0
  33. package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +3 -2
  34. package/docs/index.html +41 -4
  35. package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +1 -1
  36. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +3 -2
  37. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +104 -16
  38. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +15 -14
  39. 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 +40 -3
  40. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +50 -3
  41. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +6 -2
  42. 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 +39 -7
  43. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +13 -4
  44. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +3 -0
  45. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +121 -49
  46. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +37 -3
  47. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +17 -8
  48. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +45 -25
  49. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +91 -20
  50. package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +3 -3
  51. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +25 -1
  52. 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 +11 -8
  53. package/docs/steps/README.md +17 -11
  54. package/docs/steps/selftest-actions.html +3 -5
  55. package/docs/steps/selftest-demo.html +27 -18
  56. package/docs/steps/selftest-demos.html +409 -32
  57. package/docs/steps/selftest-docs.html +33 -4
  58. package/docs/steps/selftest-effects.html +200 -0
  59. package/docs/steps/selftest-features.html +603 -21
  60. package/docs/steps/selftest-labels.html +74 -0
  61. package/docs/steps/selftest-perf.html +293 -20
  62. package/docs/steps/selftest-scale.html +150 -29
  63. package/docs/steps/selftest-select.html +90 -0
  64. package/docs/steps/step-10-full.js +1 -1
  65. package/docs/steps/step-12-events.js +1 -1
  66. package/package.json +1 -1
@@ -4,13 +4,13 @@
4
4
 
5
5
  ## 筛选
6
6
 
7
- `setFilter` 按属性过滤点与线,点被过滤掉时绑在它身上的线路按 `cascade` 联动消失:
7
+ `setFilter` 按属性过滤点与线,点被过滤掉时绑在它身上的线路按 `cascade` 联动消失;这个默认联动还受 `lineFollowsNode` 影响(见下)。`mode` 决定不命中的要素怎么处理:缺省 `'hide'` 只改变**显示集合**,原始数据与选中集都不动,被隐藏的选中项仍留在 `getSelections()` 里,条件放宽 / 清空后原样恢复高亮;`'remove'` 则把筛选结果直接从数据里删掉,走一条 `dataChange('filterRemove')`,开了历史时可以撤销。
8
8
 
9
9
  ```js
10
- // 只看 525kV 以上的节点:两端节点没了的线路(以及通道回线)一起隐藏
10
+ // 默认 hide:只看 525kV 以上的节点,两端节点没了的线路(以及通道回线)一起隐藏
11
11
  view.setFilter({ points: { vn_kv: { gte: 525 } } })
12
12
 
13
- // 只筛节点时可以写简写:与上一行完全等价(顶层出现 points / lines / cascade / worker 任一键才按完整格式解释)
13
+ // 只筛节点时可以写简写:与上一行完全等价(顶层出现 points / lines / cascade / mode / worker 任一键才按完整格式解释)
14
14
  view.setFilter({ vn_kv: { gte: 525 } })
15
15
 
16
16
  // 点条件 + 线条件 + 联动方式
@@ -20,6 +20,9 @@ view.setFilter({
20
20
  cascade: 'binding'
21
21
  })
22
22
 
23
+ // remove:不命中的点 / 线从数据里删掉(先删线路、再删节点,只发一条 filterRemove)
24
+ view.setFilter({ points: { station_type: 'coal' }, mode: 'remove' })
25
+
23
26
  // 也可以创建时给初始条件
24
27
  createGisView(container, {
25
28
  points,
@@ -47,10 +50,50 @@ normalizeFilter({ points: {}, cascade: 'both', nope: 1 }) // unknownKeys: ['nop
47
50
  | --- | --- | --- | --- |
48
51
  | `points` | `GisViewConditionInput<GisViewPoint>` | 无 | 点条件,缺省表示不过滤点 |
49
52
  | `lines` | `GisViewConditionInput<GisViewLine>` | 无 | 线条件,缺省表示不过滤线 |
50
- | `cascade` | `false \| 'binding' \| 'coordinate' \| 'both'` | `'binding'` | 点被过滤后哪些线路一起消失:按 `from` / `to` 绑定、按端点坐标(端点正好落在被过滤的节点上)、两者都算、或完全不联动 |
53
+ | `cascade` | `false \| 'binding' \| 'coordinate' \| 'both'` | `lineFollowsNode` 开启时 `'binding'`,关闭时 `false` | 点被过滤后哪些线路一起消失:按 `from` / `to` 绑定、按端点坐标(端点正好落在被过滤的节点上)、两者都算、或完全不联动;显式写出的值始终优先 |
54
+ | `mode` | `'hide' \| 'remove'` | `'hide'` | 不命中的要素怎么处理:`hide` 只隐藏;`remove` 从数据里删除 `hiddenPoints` / `hiddenLines`,只发一条 `dataChange('filterRemove')` |
51
55
  | `worker` | `boolean` | 跟随 `filterWorker` | 这一次筛选是否允许放进 Worker |
52
56
 
53
- 联动只影响表现,数据本身不动:`updateData` / `updateOptions` 换了数据之后按同一个条件重算。通道回线也是普通线路,跟着同一个 `cascade` 走:`binding` / `coordinate` / `both` 时绑着被过滤节点的通道回线一起收起来,`false` 时照旧画 —— 被过滤的节点仍然当锚点用(几何不跟着筛选断,回线端点还钉在它原来的坐标上),只是节点自己不显示。只有节点真的不在数据里(删掉、或者写了 `visible: false`)时,两端锚不住的通道与它的回线才整条不显示(和「两端没有节点就不显示」是同一条规则)。拖拽节点松手后,按端点坐标联动的筛选会按新坐标重算一次。需要「把某个要素找出来并居中」而不是隐藏其它要素时用 `search`,见「搜索与定位」。
57
+ `hide` 模式下的联动只影响表现,数据本身不动:`updateData` / `updateOptions` 换了数据之后按同一个条件重算。通道回线也是普通线路,跟着同一个 `cascade` 走:`binding` / `coordinate` / `both` 时绑着被过滤节点的通道回线一起收起来,`false` 时照旧画 —— 被过滤的节点仍然当锚点用(几何不跟着筛选断,回线端点还钉在它原来的坐标上),只是节点自己不显示。`cascade: false` 时仍显示的通道宽度也按被过滤节点原本的尺寸链计算,不会因为节点不画了就改按另一端尺寸或退回默认 `channelWidth`。只有节点真的不在数据里(删掉、或者写了 `visible: false`)时,两端锚不住的通道与它的回线才整条不显示(和「两端没有节点就不显示」是同一条规则)。
58
+
59
+ `lineFollowsNode: false` 把「节点退场」与「线路 / 通道退场」拆开:筛选未显式写 `cascade` 时默认不再级联,隐藏、删除节点也只动节点;线路与通道继续停在最后一次解析出的坐标、骨架与宽度。条件里显式写了 `cascade` 时仍按显式值级联。筛选条件放宽或清空、被筛掉的回线重新出现后,引擎会在线路线宽解析完成后重新排一次通道偏移,通道与回线按当前宽度回到正确车道,不会保留筛选期间的旧间距。拖拽节点松手后,按端点坐标联动的筛选会按新坐标重算一次。需要「把某个要素找出来并居中」而不是隐藏其它要素时用 `search`,见「搜索与定位」。
60
+
61
+ ### 删除型筛选(mode: 'remove')
62
+
63
+ `mode: 'remove'` 把同一套判定算出的 `hiddenPoints` / `hiddenLines` 从数据里删掉,而不是只交给引擎隐藏。删除按「先线路、再节点」执行(先删点会让线路下标挪位),因此筛选结果里没有 id 的线路也能按删除前的下标删掉。`cascade` 仍决定哪些线路算进 `hiddenLines`,但删除集合就是筛选结果本身,不会在删节点时再按 `lineFollowsNode` 额外扩一圈。
64
+
65
+ ```js
66
+ createGisView(container, {
67
+ points, lines, channels,
68
+ filter: { points: { station_type: 'coal' }, mode: 'remove' },
69
+ onDataChange: payload => {
70
+ if (payload.type !== 'filterRemove') return
71
+ console.log(payload.pointIndexes, payload.removedPoints)
72
+ console.log(payload.removedLineIndexes, payload.removedLineItems)
73
+ }
74
+ })
75
+
76
+ view.setFilter({ lines: { vn_kv: { lt: 220 } }, mode: 'remove' })
77
+ view.getFilter() // { lines: { … }, mode: 'remove' }
78
+ view.getFilterResult() // 删除完成后 hiddenPoints / hiddenLines 都是空数组
79
+ ```
80
+
81
+ 一次删除只发一条 `dataChange('filterRemove')`:删除期间中间的 `removePoints` / `removeLines` 数据变更与 `onFilter` 都被抑制,最后按删完的数据重算一次筛选并正常派发一条 `onFilter`。载荷里与删除项相关的字段:
82
+
83
+ | 字段 | 说明 |
84
+ | --- | --- |
85
+ | `pointIndexes` | 删掉的节点下标(删除前它们在 `points` 里的位置,升序) |
86
+ | `removedPoints` | 删掉的节点数据,顺序与 `pointIndexes` 对应 |
87
+ | `lineIndexes` / `removedLineIndexes` | 删掉的线路下标(删除前它们在 `lines` 里的位置,升序,二者同一份) |
88
+ | `removedLineItems` | 删掉的线路数据,顺序与 `removedLineIndexes` 对应 |
89
+ | `lineIds` | 删掉的线路 id(没有 id 的线路不会出现在这里) |
90
+ | `count` / `removedLines` / `removedChannels` | 删掉的节点数 / 线路数 / 整条消失的通道声明数 |
91
+
92
+ 载荷里的 `points` / `lines` / `channels` 与其它数据变更一样是**删除之后的最新数据**,不是删除项;删除项看 `removedPoints` / `removedLineItems`。被删掉的要素会从选中集里移除,没被删的选中项保持原样。
93
+
94
+ 开了 `history` 时整次删除只落一步 `filterRemove`。撤销会恢复数据,但当前 `remove` 条件仍保留,历史回放本身不会再触发删除(恢复出来的不命中项会先按筛选结果隐藏);重做恢复删除结果,也不会补发第二条 `filterRemove`。历史关着时删除不可撤消。
95
+
96
+ 初始筛选在同步路径下(`filterWorker: false`、数据量低于阈值,或条件里带函数)会在 `ready` 之前完成删除;如果初始筛选走了 Worker,结果异步回来,删除与 `onFilter` 会发生在 `ready` 之后,结果回来之前先按隐藏集合画首帧。运行时 `setFilter({ … mode: 'remove' })` 同理。
54
97
 
55
98
  ### 大数据下的 Worker 筛选
56
99
 
@@ -70,35 +113,51 @@ Worker 按投影版本缓存字段投影,条件变化时只传条件本身;
70
113
 
71
114
  ## 搜索与定位
72
115
 
73
- 搜索和筛选是两件事:**筛选**(`setFilter`)决定哪些要素显示,会隐藏数据;**搜索**(`search`)只把要素找出来,不隐藏任何东西,**也不动视角**:命中连 `bounds` 一起给回来,要不要聚焦、聚焦谁由调用方自己调 `focusOn`(组件只给结果,不再替使用方决定相机):
116
+ 搜索和筛选是两件事:**筛选**(`setFilter`)默认只隐藏显示,不删数据、也不改选中集,显式写 `mode: 'remove'` 才会删;**搜索**(`search`)只把要素找出来,不隐藏任何东西,**也不动视角**:命中连 `bounds` 一起给回来,要不要聚焦、聚焦谁由调用方自己调 `focusOn`(组件只给结果,不再替使用方决定相机):
74
117
 
75
118
  ```js
76
119
  // 关键字可以是字符串,也可以是配置对象
77
120
  const result = view.search('望京')
78
- view.search({ keyword: '525 变电站', scope: 'channels', limit: 50 })
121
+ view.search({ keyword: '525 变电站', scope: ['channels', 'groups'], limit: 50 })
122
+ view.search({ keyword: 'channel-a', scope: 'group' }) // 单数别名与 focusOn 的目标类型一致
79
123
 
80
124
  result.total // 命中总数
81
- result.points / result.lines / result.channels // 三类各自的命中
82
- result.hits // 三类合集,顺序是节点、线路、通道
125
+ result.points / result.lines / result.channels / result.groups // 四类各自的命中
126
+ result.hits // 四类合集,顺序是节点、线路、通道、分组
83
127
  result.truncated // 命中数达到 limit 被截断
84
128
  result.durationMs
85
129
 
86
130
  // 一条命中带命中的字段、值与范围,用来回写界面或自己定位(这份最小场景里搜不到时 hit 是 undefined)
87
131
  const hit = result.hits[0]
88
- hit?.kind // 'point' | 'line' | 'channel'
89
- hit?.index // 点 / 线在数据里的下标,通道是通道下标
90
- hit?.id / hit?.item / hit?.bounds // 范围可以直接交给 zoomToBounds
132
+ hit?.kind // 'point' | 'line' | 'channel' | 'group'
133
+ hit?.index // 点 / 线在数据里的下标,通道是通道下标,分组是 groups 结果里的下标
134
+ hit?.id / hit?.item / hit?.bounds // 范围可以直接交给 zoomToBounds / focusOn
91
135
  hit?.field / hit?.value // 命中的是哪个字段、字段值是什么(如 'props.vn_kv')
136
+ hit?.channelIndex / hit?.memberIndex // 线路 / 通道命中的所属通道与第几回(通道命中自身字段时没有 memberIndex)
137
+ hit?.memberLineIndex / hit?.memberLineId // 通道因某条成员回线命中时,那条回线在 lines 里的下标与 id
138
+ hit?.groupId / hit?.groupIndex // 分组命中:分组 id 与它在所属通道里的序号(从 0 起)
139
+
140
+ // 分组命中可以直接交给 focusOn / selectFeatures,与手写 { type: 'group', channelIndex, groupId } 等价
141
+ const grouped = view.search({ keyword: 'group-2', scope: 'group' }).groups[0]
142
+ view.focusOn(grouped) // 只把中心移到这一组,比例不动
143
+ view.focusOn(grouped, { zoom: true }) // 显式按这一组的真实 bounds 适配
144
+ view.selectFeatures(grouped) // 选中这一组的全部成员回线
92
145
 
93
146
  // focusOn:把视角移到目标上 —— 单条、一批、只居中都由它一条搞定
94
- view.focusOn(hit) // 一条命中:按它的范围适配视图
147
+ view.focusOn(hit) // 一条命中:缺省只居中,比例一点不动
148
+ view.focusOn(hit, { zoom: true }) // 显式按命中范围适配视图
95
149
  view.focusOn('station-1024', { scale: 12 }) // 直接给比例
150
+ view.focusOn({ type: 'point', id: 'station-1024' }) // 节点 / 线路同 id 时显式消歧
151
+ view.focusOn({ type: 'line', id: 'station-1024' })
96
152
  view.focusOn({ type: 'line', index: 3 })
153
+ view.focusOn({ type: 'channel', channelId: 'channel-a' }) // 通道也可以直接写 { channelId }
154
+ view.focusOn({ type: 'group', channelId: 'channel-a', groupId: 'group-2' })
155
+ view.focusOn({ type: 'group', groupId: 'group-2' }) // groupId 全局唯一时可省略通道
97
156
  view.focusOn([116.4, 39.9]) // 一个坐标点:只居中,比例不动
98
- view.focusOn(['station-1', 'station-2', 'station-3']) // 一批:取整体包围盒 —— 这三个点的中心当屏幕中心
99
- view.focusOn(['station-1', 'station-2'], { select: true }) // 顺带把它们选上(与接口选中同一份 state)
100
- view.focusOn('station-1', { maxZoom: 8 }) // 单个节点这种零面积目标也能放大到 8 倍
101
- view.focusOn({ minX: 116, minY: 39, maxX: 117, maxY: 40 }, { padding: 60 })
157
+ view.focusOn(['station-1', 'station-2', 'station-3']) // 一批:整体中心当屏幕中心,比例不动
158
+ view.focusOn(['station-1', 'station-2'], { select: true, zoom: true }) // 适配范围并顺手选上
159
+ view.focusOn('station-1', { zoom: true, maxZoom: 8 }) // 零面积目标也能放大到 8 倍
160
+ view.focusOn({ minX: 116, minY: 39, maxX: 117, maxY: 40 }, { zoom: true, padding: 60 })
102
161
 
103
162
  // 只挪中心、比例一点都不动:focusOn(target, { center: true })
104
163
  view.focusOn([116.4, 39.9], { center: true }) // 世界坐标(数组 / { x, y } 都认)
@@ -108,34 +167,39 @@ view.focusOn({ lngLat: [116.4, 39.9] }, { center: true }) // 配了 GeoJSON 底
108
167
  view.focusOn({ minX: 116, minY: 39, maxX: 117, maxY: 40 }, { center: true }) // 一段范围:取中心
109
168
  ```
110
169
 
170
+ 字符串 / 数字仍然兼容旧写法:先按节点 id 找,再按线路 id 找;同一个 id 同时出现在节点与线路里时用 `{ type: 'point' | 'line', id }` 消歧。裸字符串不表示通道;整条通道可以写 `{ channelId }`,要和通用 `id` / `index` 写法区分时再用 `{ type: 'channel', ... }`。分组必须写 `{ type: 'group', channelId?, groupId }`,`groupId` 在全部通道里唯一时可以省略通道。节点、线路、通道与分组都先解析成世界坐标范围;**缺省只按这个范围的中心调 `centerOn`,比例一点不动**,要适配就显式写 `zoom: true`,这时才交给 `zoomToBounds` 按解析出的真实 `bounds` 计算,没有为某类目标额外乘放大倍率。`scale` 的优先级高于 `zoom`;`maxZoom` 只给单点 / 单线这类零面积目标补最小范围。
171
+
111
172
  | `focusOn` 的选项 | 说明 |
112
173
  | --- | --- |
113
- | `scale` | 直接设成这个比例,不再按范围适配 |
114
- | `center: true` | 只把中心挪过去、比例一点不动(原来 `centerOn` 的能力并到了这里) |
115
- | `padding` | 按范围适配时四周留白(CSS 像素,缺省 40) |
116
- | `maxZoom` | 适配时的最大比例:单个节点 / 一条直线没有面积,靠它撑出一个最小范围 |
174
+ | `zoom: true` | 显式要求按目标范围适配视图;不写就只居中、比例不动 |
175
+ | `scale` | 直接设成这个比例,不再按范围适配(优先级高于 `zoom`);线路 / 通道 / 分组不会在此基础上再自动放大 |
176
+ | `center: true` | 显式只把中心挪过去、比例一点不动(与缺省行为一致,保留旧写法) |
177
+ | `padding` | 按范围适配(`zoom: true`)时四周留白(CSS 像素,缺省 40) |
178
+ | `maxZoom` | 适配(`zoom: true`)时的最大比例:单个节点 / 一条直线没有面积,靠它撑出一个最小范围 |
117
179
  | `select: true`(或另给一批写法) | 把这些被聚焦的要素同时变成选中状态(与接口选中同一份 state) |
118
180
  | `mode` | 配合 `select`:这一批怎么并进当前选中集(`replace` 缺省 / `add` / `toggle` / `remove`) |
119
181
 
120
182
  | 字段 | 类型 | 默认值 | 说明 |
121
183
  | --- | --- | --- | --- |
122
184
  | `keyword` | `string` | 无 | 关键字:空格分隔多个词时要在同一个字段里全部出现才命中,不区分大小写 |
123
- | `scope` | `'points' \| 'lines' \| 'channels'` 或它们的数组 | 三类都搜 | 搜索范围 |
185
+ | `scope` | `'points' \| 'lines' \| 'channels' \| 'groups'` 或它们的数组;也认单数别名 `'point'` / `'line'` / `'channel'` / `'group'` | 四类都搜 | 搜索范围 |
124
186
  | `fields` | `string[]` | 要素的全部字段 | 限定参与匹配的字段路径,如 `['name', 'props.vn_kv']` |
125
187
  | `limit` | `number` | `200` | 每类的命中上限,`0` 表示不限 |
126
188
 
127
189
  补充口径:
128
190
 
129
- - `limit` 按「节点 / 线路 / 通道」三类分别计数;省略或写成非数字 / `NaN` 时回落到默认 `200`,不会退化成不限。`0` 与负数都表示不限。
191
+ - `limit` 按「节点 / 线路 / 通道 / 分组」四类分别计数;省略或写成非数字 / `NaN` 时回落到默认 `200`,不会退化成不限。`0` 与负数都表示不限。
130
192
  - `fields` 会忽略空项并去重;数组为空或没有有效字段路径时,与不写 `fields` 一样按全部字段处理。
131
193
  - 默认全字段模式会跳过 `symbol` / `style` / `tooltip` 这类外观结构;但 `fields` 显式点名时按路径直接读取,不受这条跳过规则限制。例如 `fields: ['symbol']`、`fields: ['style.color']` 能命中对应值。
132
- - 搜索始终基于全量数据,不受当前 `setFilter` 影响:被筛选隐藏的要素仍然可以被搜到。`scope` 写错或传空数组时会回落到三类全搜;重复线路 id 按数据里的第一条解析(见 [ID 解析口径](02-坐标与数据.md#id-解析口径))。
194
+ - 搜索基于当前数据:`hide` 模式下被隐藏的要素仍在数据里,因此仍然可以被搜到;`remove` 模式下已经删掉的要素搜不到。`scope` 写错或传空数组时会回落到四类全搜;重复线路 id 按数据里的第一条解析(见 [ID 解析口径](02-坐标与数据.md#id-解析口径))。
195
+
196
+ 搜索**不动视角**:命中里带着 `bounds`,要定位就自己调 `focusOn(result.hits, …)` —— 缺省只把一批命中的整体中心当屏幕中心;要按范围适配就写 `{ zoom: true }`,`{ select: true }` 还能顺带把它们选上。`zoom` / `scale` / `padding` / `maxZoom` 属于 `focusOn` 的选项,搜索本身没有这些参数。
133
197
 
134
- 搜索**不动视角**(`focus` / `scale` / `padding` 三个选项随之取消):命中里带着 `bounds`,要定位就自己调 `focusOn(result.hits, …)` —— 一批命中的整体中心就是屏幕中心,`{ select: true }` 还能顺带把它们选上。
198
+ 匹配的是要素自己的字段:`id`、`name`、`from` / `to`,以及任意业务字段——`props` 这类普通对象会再往里摊一层,所以 `props.vn_kv` 里的值也能搜到;样式与符号字段不参与匹配。**通道连成员回线一起搜**:命中的是通道里的某条回线时,命中里会带上 `channelIndex`、`memberIndex`(第几回,0 开始)与 `memberLineIndex` / `memberLineId`,`bounds` 也换成那条回线的范围,`focusOn(hit)` 会把中心移到通道里的这条线路上,`focusOn(hit, { zoom: true })` 才按它的范围适配;反过来搜线路时,属于通道的线路也会带上 `channelIndex` / `memberIndex`。
135
199
 
136
- 匹配的是要素自己的字段:`id`、`name`、`from` / `to`,以及任意业务字段——`props` 这类普通对象会再往里摊一层,所以 `props.vn_kv` 里的值也能搜到;样式与符号字段不参与匹配。**通道连成员回线一起搜**:命中的是通道里的某条回线时,命中里会带上 `channelIndex`、`memberIndex`(第几回,0 开始)与 `memberLineIndex` / `memberLineId`,`bounds` 也换成那条回线的范围,`focusOn(hit)` 因此会直接定位到通道里的这条线路;反过来搜线路时,属于通道的线路也会带上 `channelIndex` / `memberIndex`。
200
+ **分组按分组自身字段匹配**(`id` / `channelId` / `channelIndex` / `groupIndex` / `lineCount` / `anchored` / `edited` 等)。命中项的 `item` 是轻量快照(含成员下标 `lineIndexes` 与成员 id `lineIds`,不含骨架 / 回线几何),同时带上 `channelIndex`、`groupId`、`groupIndex`;`bounds` 由这一组的骨架与成员线算出,所以 `focusOn(hit, { zoom: true })` 会按整组范围适配,`selectFeatures(hit)` 会选中组内全部回线。分组搜索结果在 `result.groups`,也包含在 `result.hits` 里。
137
201
 
138
- 搜索是同步、只读的:不改数据、不改筛选、不产生额外的渲染状态,按关键字顺序返回。要「隐藏不符合条件的要素」用 `setFilter`;要「找出来」用 `search`,要「居中 / 放大过去」自己调 `focusOn`。
202
+ 搜索是同步、只读的:不改数据、不改筛选、不产生额外的渲染状态,按关键字顺序返回。要「只隐藏不符合条件的要素」用默认的 `setFilter`(`mode: 'hide'`),要「从数据里删掉」显式写 `mode: 'remove'`;要「找出来」用 `search`,要「居中 / 放大过去」自己调 `focusOn`。
139
203
 
140
204
  ### 异步搜索:`searchAsync`
141
205
 
@@ -144,13 +208,14 @@ view.focusOn({ minX: 116, minY: 39, maxX: 117, maxY: 40 }, { center: true }) //
144
208
  ```js
145
209
  // 每次调用都会 resolve 自己那一次的命中;worker 说明这一份是在哪算的
146
210
  const result = await view.searchAsync('望京')
147
- view.searchAsync({ keyword: '525 变电站', scope: 'channels' })
211
+ view.searchAsync({ keyword: '525 变电站', scope: ['channels', 'groups'] })
212
+ view.searchAsync({ keyword: 'channel-a', scope: 'group' })
148
213
 
149
214
  // 连着敲字:先回来的那几份带着 stale,界面上该丢掉(视角也不会跟着它动)
150
215
  let latest = 0
151
216
  const search = async keyword => {
152
217
  const token = ++latest
153
- const result = await view.searchAsync({ keyword, focus: false })
218
+ const result = await view.searchAsync({ keyword })
154
219
  if (result.stale || token !== latest) return
155
220
  render(result)
156
221
  }
@@ -163,7 +228,7 @@ const search = async keyword => {
163
228
  | `worker` | `boolean` | 这一份是在 Worker 里算的(`false` 表示这条路上退回了同步计算) |
164
229
  | `stale` | `boolean` | 结果回来时数据已经改过、或者又发过一次搜索:下标与内容都可能不作数了 |
165
230
 
166
- 同步的 `search` 与 `searchAsync` 在**同一个数据版本 + 同一个请求**上结果逐字段一致(命中、命中的字段与值、`item` 引用、`bounds`)。环境不支持 Worker、数据量不到阈值或 Worker 报错时,`searchAsync` 会退回同步计算再 resolve,调用方不用写两条路。
231
+ 同步的 `search` 与 `searchAsync` 在**同一个数据版本 + 同一个请求**上结果逐字段一致(四类命中、命中的字段与值、`item` 引用、`bounds`);分组范围的当前轻量快照会随请求交给 Worker,分组字段或分组 id 改过之后也不会拿旧快照匹配。环境不支持 Worker、数据量不到阈值或 Worker 报错时,`searchAsync` 会退回同步计算再 resolve,调用方不用写两条路。
167
232
 
168
233
  ## 初始视图
169
234
 
@@ -200,12 +265,12 @@ createGisView(container, {
200
265
  「本来铺得好好的,新加一个远处的节点,整幅视图缩了一圈、两点之间的可视距离变短」——根因是**自动适应读的是数据包围盒**:新节点把包围盒撑大,`fitView()` 按更大的范围重算 `scale`,比例一变小,节点之间的屏幕距离就跟着短(节点自己的屏幕尺寸由 `pointSizeScale` 那一对系数决定,没跟着缩,看着就更挤)。先想清楚「视野由谁说了算」,再按场景选:
201
266
 
202
267
  1. **视野只由业务范围决定(推荐)**:配 `initialView: { bounds, padding }`(或 `mode: 'center'` + `scale`)把首屏钉在一块业务范围上 —— `bounds` 不跟着数据长,新节点进来包围盒变大也不影响相机;数据分布本来就广时,业务范围与数据边界分开正是想要的。
203
- 2. **新节点不进自动适应**:`fitView()` 只手动调(首屏一次、切数据集一次),`addPoint` / `addPoints` 追加之后组件不会自动适应;要展示新节点就 `focusOn(id)` 或高亮 / `setDropPreview`,别整幅重适应。
268
+ 2. **新节点不进自动适应**:`fitView()` 只手动调(首屏一次、切数据集一次),`addPoint` / `addPoints` 追加之后组件不会自动适应;要展示新节点就 `focusOn(id, { zoom: true })` 或高亮 / `setDropPreview`,别整幅重适应。
204
269
  3. **只平移不改比例**:`setCamera({ center })` 或 `focusOn(id, { center: true })` —— 中心挪过去、`scale` 一点不动,两点之间的屏幕距离就不变。
205
- 4. **按「节点间距」而不是「包围盒」算适应**:`focusOn([...几个代表性要素])` / `zoomToBounds(业务范围)`;也可以给 `minScale` 一个下界,缩到这条线就停住。
270
+ 4. **按「节点间距」而不是「包围盒」算适应**:`focusOn([...几个代表性要素], { zoom: true })` / `zoomToBounds(业务范围)`;也可以给 `minScale` 一个下界,缩到这条线就停住。
206
271
  5. **想让节点本身不跟着缩**:`pointZoomScale` / `lineZoomScale` 调小(`0` = 完全不跟随地图缩放),尺寸主要由 `pointSizeScale` / `lineWidthScale` 的常数倍数决定 —— 比例被带跑时至少节点不会一起变小。
207
272
 
208
- 6. **让整个画面的比例恒定(想省事就用它)**:打开 `zoomScaleAuto` —— 没写联动指数的那几项按 1 算,包围盒变大(相机比例变小)时节点与线宽跟着一起小,节点大小与通道间距的比例保持不变;换了一批量纲完全不同的数据、想重新校准就 `setZoomBaseScale()` 把当下这一屏钉成基准,见「自适应比例(zoomScaleAuto)」。
273
+ 6. **给节点最小间距兜底(先试它)**:打开 `fitSpacing` —— 适应视图时按可见节点最近中心距反推比例,远处孤立点撑大包围盒也不会再把主体压成一团;孤立点允许落到视口外,必要时用 `maxZoom` 限制放大倍数,见「适应视图的节点间距(fitSpacing)」。
209
274
 
210
275
  ## 缩放范围(minScale / maxScale)
211
276
 
@@ -229,30 +294,33 @@ view.updateOptions({ minScale: scale / 5, maxScale: scale * 5 })
229
294
 
230
295
  demo 的「初始视图与背景」面板里就有现成的入口(`docs/demos/GisViewDemo.vue`):两个输入框(最小比例 / 最大比例)直接写数字,改完走 `updateOptions({ minScale, maxScale })` **并顺手 `setCamera` 把当前相机夹回区间**(更新配置本身不会把相机拉回来,demo 里把这一步显式补上了);`以当前比例设限(÷5 ~ ×5)` 按当下比例算一对;`试一下下限` / `试一下上限` 各喂一个远超区间的比例给 `setCamera()`,读数里能看到相机停在哪;`解除限制` 回到组件缺省的 `0.002 ~ 20000`。自测页 `docs/steps/selftest-demo.html` 有 3 条断言盯着这一套(按当前比例设限并下发 / 越界喂值被夹在上下限 / 解除限制回到缺省)。
231
296
 
232
- ## 自适应比例(zoomScaleAuto)
297
+ ## 适应视图的节点间距(fitSpacing)
233
298
 
234
- 不同数据集 / 不同包围盒下「节点一样大、通道间距却时短时长」的根因是**两套尺子**:节点的屏幕尺寸由 `pointSizeScale`(常数倍数)定,包围盒变了也不变;节点之间的距离是世界坐标乘 `camera.scale` 得来的,包围盒一大(适应视图后 `scale` 变小)屏幕上的间距就短 —— 每换一批数据都得重新调一次尺寸系数。
299
+ 「两个本来一样的视图,只因为多了一个远处节点,节点就突然很大、很挤」——根因是自动适应读的是**数据包围盒**:远处节点把范围撑大,`fitView()` 只能把 `camera.scale` 压小,节点之间的距离随之变短;节点自己的屏幕尺寸不跟着缩,看起来就像放大了一样。
235
300
 
236
- `zoomScaleAuto` 把这两套尺子合成一套:**打开后没写联动指数的 `pointZoomScale` / `lineZoomScale` 按 1(与地图同倍率)算** —— 包围盒变大、相机比例变小时节点与线宽跟着一起小,节点大小与通道间距的比例保持不变。
301
+ `fitSpacing` 不改包围盒,而是在普通适应算完之后再做一次**节点间距兜底**:找出当前可见节点里最近的两个不同位置,按它们的世界距离算出「要让中心距达到 `minDistance` 需要多大的比例」,取普通适应与这个比例中较大的一个。它**只提高 `camera.scale`,不改变相机中心**:中心始终沿用普通适应按数据包围盒算出的那一个,所以不会因为最近节点对换了一对就突然跳到图上另一个位置。规则网格里最近距离大量并列也不会影响结果 —— 距离相同就只取同一个目标比例,没有「挑哪一对并居中」的问题。
237
302
 
238
303
  ```js
239
- // 打开自适应比例:之后换数据 / 加远处的节点,节点大小与通道间距按同一比例变
240
- const view = createGisView('#map', { points, lines, channels, zoomScaleAuto: true })
241
- view.updateOptions({ zoomScaleAuto: true }) // 运行时开关也只重画一帧(不重排场景)
304
+ // 最省事:最小中心距 24 CSS 像素,放大倍数不设相对上限(仍受 maxScale 约束)
305
+ const view = createGisView('#map', { points, lines, channels, fitSpacing: true })
306
+
307
+ // 细调:最近节点至少隔 32px;最多放大到普通适应比例的 8 倍
308
+ view.updateOptions({ fitSpacing: { minDistance: 32, maxZoom: 8 } })
309
+ view.fitView() // 改配置不会当场动相机,再适应一次才看得到
242
310
 
243
- view.getZoomBaseScale() // 基准比例:缺省是首次适应视图时的 camera.scale,等于它时要素「原样大小」
244
- view.setZoomBaseScale() // 以当下这一屏为基准(换了一批量纲完全不同的数据、想重新校准观感时调它)
245
- view.setZoomBaseScale(2.5) // 也可以直接给一个比例
311
+ view.updateOptions({ fitSpacing: false }) // 关掉,下一次 fitView 恢复普通包围盒适应
246
312
  ```
247
313
 
248
314
  几个约定:
249
315
 
250
- - 开关只给**没写**指数的那一项兜底:显式写过 `pointZoomScale` / `lineZoomScale`(哪怕写 `0`)就以显式值为准 —— 想只让线路跟随、节点恒定,就写 `zoomScaleAuto: true` + `pointZoomScale: 0`
251
- - 基准是 `zoomBaseScale`,缺省取**首次适应视图**时的 `camera.scale`:首屏观感与从前一样,之后不管范围怎么变比例都不再被带跑;`setZoomBaseScale()` 用来换一批新量纲的数据重新定「原样大小」
252
- - 跟随是幂指数:`zoomScaleAuto` 打开且没写指数时就是 1(地图放大 2 倍、要素也大 2 倍);想弱一点显式写 `pointZoomScale: 0.5`(地图放大 4 倍、节点放大 2 倍)
253
- - 节点屏幕尺寸一变,按端点尺寸取宽的通道回线位置跟着重排(`channelNodeWidth`,见「通道与多回线」),因此打开之后缩放画面时通道宽度也是稳定的
254
- - 拾取容差跟着放大系数一起放大(放大后的节点 / 粗线照样点得准),标签避让用的也是同一份半径
255
- - demo 的「样式」面板有「自适应比例」开关与「以当前比例为基准」按钮;自测页 `docs/steps/selftest-scale.html` 有 6 条断言盯着这一套(基准缺省 / 相机放大 2 倍尺寸翻倍 / 重定基准 / 显式指数优先 / 关掉不再跟随)
316
+ - **默认关**,打开后只影响「默认 fit 模式下的首次适应」与手动 `fitView()`;`zoomToBounds()` / `focusOn(..., { zoom: true })` 仍按指定范围算,不会强行套节点间距。
317
+ - 只统计**筛选 / 隐藏之后仍然可见**的节点。完全重合的坐标不算有效间距(否则两个重叠点会让整套兜底失效);少于两个不同位置时不调整。
318
+ - **只放大、不跳视点**:`fitSpacing` 不重算中心,也不挑一对节点居中。需要改变中心时用 `initialView.center` / `initialView.bounds`、`focusOn()` 或 `setCamera({ center })`。
319
+ - `minDistance` 是节点**中心之间**的屏幕距离(CSS 像素),默认 `24`。想给节点本身和描边留更多空隙就调大。
320
+ - `maxZoom` 是相对普通适应当前比例的倍数上限,默认 `0` 表示不限;设成正数可避免极端数据把画面放大到只剩局部。`minScale` / `maxScale` 始终是最后一层夹取。
321
+ - 远处孤立点仍参与普通适应的包围盒与中心计算;放大后它可以落到视口外,这是「先保证节点间距」的取舍。如果孤立点连中心都不该影响,关掉 `fitSpacing` 或改用 `initialView.bounds` / 预筛数据钉住业务范围。
322
+ - 显式配了 `initialView.scale` / `initialView.zoom` 时以使用方给的为准;只有没有写死比例 / 倍数的 fit 模式才走间距兜底。
323
+ - demo 的「样式」面板有「适应节点间距」开关、最小中心距与最大放大输入;自测页 `docs/steps/selftest-scale.html` 有 8 条断言盯着这一套(配置回显 / 提高比例且忽略重合点 / 保持普通适应中心 / 不跳到最近节点对 / 屏幕间距 / `maxZoom` / 关闭恢复 / 规则网格并列时仍保持原中心)。
256
324
 
257
325
  ## 背景层
258
326
 
@@ -439,6 +507,8 @@ createGisView(container, {
439
507
 
440
508
  交互与开销:**在框上按下直接拖框**(世界位移 = 屏幕位移 ÷ 小图比例),**空白处单击不改变视角**,按住超过 `longPressDelay` 才进入「跟着拖」,拖动过程中始终跟着光标;光标停在小图上滚动滚轮按光标位置缩放主图(`wheelZoom: false` 关掉)。每个有位移的手势结束时可通过 `onInteraction` 收到一条通知,不必自己在小图 DOM 上再挂一套指针监听。底图画的是筛选之后真正画出来的线,通道骨架与回线都在内(直接问引擎要几何,不用自己再推一遍通道偏移),画法是先铺线、再把节点整层盖上去 —— 节点层级高于线路,密集底图上的节点不会被线咬掉。底图只在数据或筛选结果变化时重画(节点直接写进像素缓冲,十万级也只在重建时花一次时间),相机变化只改视口框的 `transform`,所以拖动小图不掉帧。小图还会缓存**上一次的节点像素结果**与**数据范围**:一次改动排在前后两帧各重画一次(引擎重排之后要再取一次几何),第二次与第一次的数据、范围、尺寸完全一样时直接复用那份像素,十万点下第二次重画从 ~20ms 降到 0.3ms(配了 `pointMapZoomScale` / `lineMapZoomScale` 时把「主视图倍数」也并进缓存的键,倍数变了才重铺);`extent: auto` 的取舍跟相机有关,相机挪进 / 挪出那份小范围时才补一次重画(每帧只做几次比较,判断用的范围是缓存的)。小图拖框 / 小图滚轮带来的相机变化不算主画布上的交互:配了 `hideOnInteract` 的图层不会在这时被收起来,拖动结束还会补一次真实排版 —— 不会出现「动一下鹰眼图,主视图标签全没了」。真在主画布上拖动过之后、两层标签同时收着时,收尾计时器也照样挂上 —— 覆盖层停手的收尾是无条件的,不会因为「两层都在收着、当下没东西可排」把恢复一起跳过。
441
509
 
510
+ 线路层另有自己的离屏 canvas 与缓存:缓存键包含画布尺寸、范围、投影、小图倍率与线路样式,命中时整层直接合成,不再重复投影与描边;`collectMinimapData()` 复制路径时顺路算出每条线的包围盒,完全落在小图视野外的线不建路径。只有布局尺寸实际变化时才重设 canvas 尺寸,单纯补画或移动视口框不会清空这份缓存。
511
+
442
512
  `zoomable: true` 之后小图自身也能整体放大缩小,空白处的拖动换成下面两种动作(单击不再需要长按,空白处不再走 `longPressDelay` 那条路):
443
513
 
444
514
  - **平移小图**(`dragAction: 'pan'`,默认):光标往哪拖,小图内容跟着走,主图不动;内容夹在数据范围内,拖到头就停住。主图视口框跟着内容一起挪,必要时会贴在边上指示方向。
@@ -468,7 +538,7 @@ createGisView(container, {
468
538
  | `addChannel` 顶层 `channelId` / `groupId` / `groupIndex` ↔ `resolution: { type: 'joinGroup', … }` | 同一件事的两种写法(与 `updateLine` 的分组指令同款):并进已有通道时指定「哪条通道、哪一组」;只给 `channelId` 按这条通道自己的推荐顺序挑(新分组 → 第一个能并的分组)。结果里的 `channelLineIds` 是更新后的完整成员表,见「新增通道」 |
469
539
  | `project(x, y)` / `unproject(x, y)` ↔ `clientToScreen(clientX, clientY)` / `screenToClient(x, y)` ↔ `clientToWorld(clientX, clientY)` / `worldToClient(x, y)` | 同一套换算链的三档入口:`project` / `unproject` 收**画布 CSS 像素**,`clientTo*` 这一对吃鼠标事件里的 `clientX` / `clientY`(自动减 canvas rect、除宿主缩放)只换到画布像素,再接一层就是世界坐标(`clientToWorld` / `worldToClient`);经纬度那一对是 `lngLatToScreen` / `screenToLngLat` |
470
540
  | `fitView(padding?)` / `applyInitialView()` / `resetView()` / `zoomToBounds(bounds, padding?)` | 视角的四个语义:适应**数据范围**(并记下首次落位)/ 按 `options.initialView` 落位(可反复调用)/ 回到**首次落位那一刻**的相机 / 按给定的世界坐标范围适应 |
471
- | `focusOn(ref, options?)` ↔ `zoomToBounds(bounds, padding?)` | `focusOn` 认要素(id / 下标 / 搜索命中 / 坐标 / 矩形 / 一批),内部算出范围再走适应那一条,另带 `center` / `scale` / `maxZoom` / `select` 这些选项 |
541
+ | `focusOn(ref, options?)` ↔ `zoomToBounds(bounds, padding?)` | `focusOn` 认要素(id / 下标 / 搜索命中 / 坐标 / 矩形 / 一批),缺省只按范围中心居中;写 `{ zoom: true }` 才走适应那一条,另带 `center` / `scale` / `maxZoom` / `select` 这些选项 |
472
542
  | `pick(x, y)` ↔ `pickAt(clientX, clientY)` | 坐标口径不同:`pick` 收画布 CSS 像素,`pickAt` 直接吃鼠标事件里的客户端坐标(结果里另加 `target` 与 `lngLat`) |
473
543
  | `hoverAt(clientX, clientY)` / `clearHover()` / `getHover()` | 三个动作:程序化悬停 / 撤掉并补发 `onHover(null)` / 读当前那一份(`hoverAt` 的返回与 `onHover` 载荷同一份) |
474
544
  | `getData()` / `getVisibleData()` / `getOptions()` | 读的层不同:全量数据副本(含拖拽后的最新坐标)/ 筛掉隐藏之后的点与线 / 整份配置与数据 |
@@ -485,4 +555,6 @@ createGisView(container, {
485
555
 
486
556
  ---
487
557
 
558
+ **相关章节**:[03 配置项(options)](03-配置项.md) · [07 指针与要素编辑](07-指针与要素编辑.md) · [08 选中与拐点](08-选中与拐点.md) · [16 实例方法总表](16-实例方法总表.md)
559
+
488
560
  [← 13 动效与图元](13-动效与图元.md) · [手册目录](../GisView.md) · [15 导出与集成 →](15-导出与集成.md)
@@ -11,6 +11,7 @@ const url = await view.exportImage({ scale: 2 }) // 位图:
11
11
  const plain = await view.exportImage({ background: 'none' }) // 位图:透明底
12
12
  const svg = await view.exportImage({ type: 'svg' }) // 矢量:放大不糊
13
13
  const svgPlain = await view.exportImage({ type: 'svg', background: 'none' }) // 矢量 + 透明底
14
+ const full = await view.exportImage({ scope: 'all', padding: 32 }) // 全部内容:临时适应数据范围后出图
14
15
  // 也可以直接触发下载:download: true(用默认文件名)或 download: '线路图.png'
15
16
  await view.exportImage({ download: '线路图.png' })
16
17
  await view.exportImage({ type: 'svg', background: 'none', download: '线路图.svg' })
@@ -21,6 +22,8 @@ await view.exportImage({ type: 'svg', background: 'none', download: '线路图.s
21
22
  | `type` | `'image/png' \| 'image/jpeg' \| 'image/webp' \| 'svg'` | `'image/png'` | 导出格式:位图(`jpeg` / `webp` 可以用 `quality`)或矢量 SVG(`'svg'` 与 `'image/svg+xml'` 都认) |
22
23
  | `quality` | `number` | 无 | 0 ~ 1,只对 `jpeg` / `webp` 有效 |
23
24
  | `scale` | `number` | 设备像素比(SVG 为 `1`) | 导出倍率(相对画布 CSS 像素);SVG 只写进 `width` / `height` 两个属性,`viewBox` 始终是 CSS 像素 |
25
+ | `scope` | `'viewport' \| 'all'` | `'viewport'` | `'viewport'` 只导当前可视区域;`'all'` 临时把相机适应到全部当前可见内容,导完恢复原相机。写其它值直接抛异常 |
26
+ | `padding` | `number` | `24` | 只对 `scope: 'all'` 生效:适应全部内容时四周留白(CSS 像素,负数按 0 处理);`scope: 'viewport'` 忽略 |
24
27
  | `background` | `string \| false` | 带背景 | 见下表:不带背景 = 透明底,颜色字符串 = 换一层底色 |
25
28
  | `includeLabels` | `boolean` | `true` | 是否把标签一起画进去 |
26
29
  | `download` | `boolean \| string` | `false` | 是否触发浏览器下载,字符串同时作为文件名 |
@@ -33,9 +36,11 @@ await view.exportImage({ type: 'svg', background: 'none', download: '线路图.s
33
36
  | `false` / `'none'` / `'transparent'` | **不带背景**:上面那些都不画,输出**透明底**(位图是带 alpha 通道的 png / webp,矢量则干脆不写这些元素) |
34
37
  | `'#ffffff'` 这类颜色字符串 | **带背景,但换底色**:只把最底下那一层清屏色换成它,底图、GeoJSON 区域填充与网格照旧(固定白底 / 深色底就写它;`jpeg` 不支持透明,建议给一个色) |
35
38
 
39
+ `scope: 'all'` 不是按原始比例拼一张超大的图,而是**临时改相机把全部可见内容框进当前视口**,因此输出尺寸仍由画布 CSS 尺寸 × `scale` 决定;筛选 / `visible: false` 隐藏的要素仍不会进入导出。位图与 SVG 两条路都支持,`finally` 中会恢复相机并重排标签、扩展图形与鹰眼图,使用方看到的视角不会停在导出那一版。
40
+
36
41
  导出用的是当前这一帧的实际画面:位图内部先同步重画一帧再取像素 —— 透明底与换底色这两种写法出的都是「只给导出用」的那一帧,读完像素立刻补画一帧正常画面的把画布还原,屏幕上不会停在导出那一版。整个过程不需要开 `preserveDrawingBuffer`,所以也没有常驻的额外显存开销。
37
42
 
38
- `type: 'svg'` 走的是矢量:线(含虚线图案与相位)、点(内置圆 / 方与 SVG 符号)、线路装饰、拐点、GeoJSON 区域、扩展图形与标签都是真正的 SVG 元素,颜色 / 线宽 / 线型取的就是当前那一帧解析出来的值(悬停、选中样式同样在内),放大到任意倍都不糊。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 时也可以直接用)。
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 时也可以直接用)。
39
44
 
40
45
  ## 导出选中的节点
41
46
 
@@ -347,7 +352,7 @@ const focusSelectionPlugin = view => {
347
352
  }
348
353
  const selection = view.getSelections()
349
354
  if (selection.length) {
350
- view.focusOn(selection, { padding: 80 })
355
+ view.focusOn(selection, { zoom: true, padding: 80 })
351
356
  }
352
357
  }
353
358
  window.addEventListener('keydown', onKeydown)
@@ -380,7 +385,35 @@ dispose() // 卸载:跑上面那个 destroy,从实例的清理列表里摘
380
385
  - 与插件并列的另一条扩展点是**自定义图元**:插件加的是「视图级行为」(快捷键、面板、URL 同步…),自定义图元加的是「点怎么画」—— 注册一支画笔 `view.registerSymbol('三角', painter)`,或在数据里写 `{ type: 'custom', draw }`,见「自定义图元」一节。
381
386
  - 第三条扩展点是**自定义条件运算符**:插件加行为、图元加画法,运算符加的是「条件语言里的判定动词」—— `view.registerOperator('mod2', value => current => …)` 之后规则 / 筛选 / 标签的 `when` 都能写 `{ vn_kv: { mod2: true } }`,见「条件语言 → 自定义运算符」。
382
387
 
383
- ### 五类扩展点怎么选
388
+ ## 实例命令
389
+
390
+ `registerCommand` 把「业务动作」注册到当前视图实例上,适合面板按钮、快捷键和外接工具栏共用同一个入口。命令只在当前实例有效,不进全局注册表;handler 同步执行,拿到原样传入的 payload 与冻结的 `{ view, name }` 上下文:
391
+
392
+ ```js
393
+ // 业务动作注册一次,按钮 / 快捷键 / 宿主脚本都走 executeCommand
394
+ const dispose = view.registerCommand('focusHighestLoad', (payload = {}, context) => {
395
+ const threshold = Number(payload.threshold ?? 0.8)
396
+ const hit = view.search({ keyword: String(threshold), scope: 'points' }).points[0]
397
+ if (!hit) {
398
+ return { focused: false, threshold }
399
+ }
400
+ context.view.focusOn(hit.id, { select: true, zoom: true, maxZoom: 12 })
401
+ return { focused: true, id: hit.id, command: context.name }
402
+ })
403
+
404
+ view.listCommands() // ['focusHighestLoad']
405
+ const result = view.executeCommand('focusHighestLoad', { threshold: 0.85 })
406
+ // 成功:{ ok: true, name: 'focusHighestLoad', value: { focused: true, ... } }
407
+ // 未注册 / 名字非法 / handler 抛错:{ ok: false, name, reason }
408
+ dispose() // 幂等注销;同名已被后来者覆盖时不会误删
409
+ ```
410
+
411
+ - 名字会 `trim`;同名再注册以后一次为准,覆盖不会改变它在 `listCommands()` 里的位置。返回的注销函数只摘掉自己那一份注册。
412
+ - 名字为空 / handler 不是函数只报一条 `onWarning`,本次注册不生效;执行未注册的命令同样走 warning 并返回 `ok: false`。
413
+ - handler 抛出的异常由组件收成 `onError`,`executeCommand` 返回 `{ ok: false, name, reason }`,不会把异常甩给调用方。
414
+ - 命令是同步调用;要串异步流程就让 handler 自己启动 Promise,再用事件 / 状态读数回报结果。`view.destroy()` 会清空实例命令表。
415
+
416
+ ### 六类扩展点怎么选
384
417
 
385
418
  | 扩展点 | 适合做什么 | 作用域 | 清理方式 |
386
419
  | --- | --- | --- | --- |
@@ -389,6 +422,7 @@ dispose() // 卸载:跑上面那个 destroy,从实例的清理列表里摘
389
422
  | `view.registerOperator(name, test)` | 给规则 / 筛选 / 标签增加条件动词,例如 `mod2`、`evenIndex` | 模块级条件运算符表,同页所有实例共享 | 返回幂等注销函数,只删除自己这次注册;注销后旧条件按「不成立」处理 |
390
423
  | `view.addWebGLLayer(layer)` | 直接拿组件的 WebGL2 上下文画自定义标记、特效或调试几何;层画在内置内容之后,不参与拾取与数据导出 | 单个视图实例 | 返回卸载函数;也可用 `updateWebGLLayer` / `removeWebGLLayer` / `clearWebGLLayers` 维护,画布外还可通过 `view.gl` 直接取上下文 |
391
424
  | `defineExportFormat(name, adapter)` | 把标准文档映射成后端 DTO、业务表列名或项目自己的嵌套结构(同步导出器) | 模块级导出格式表,所有实例的 `exportData` 共享 | 返回幂等清理函数,只删除自己这次注册;需要按名字全局摘除时用 `unregisterExportFormat(name)` |
425
+ | `view.registerCommand(name, handler)` | 把业务动作注册成命令:面板按钮、快捷键与宿主脚本共用 `executeCommand` 入口,handler 里可继续调任意实例 API | 单个视图实例 | 返回幂等注销函数;同名覆盖时旧卸载函数不会误删新命令,`view.destroy()` 会清空命令表 |
392
426
 
393
427
  ## 自建界面
394
428
 
@@ -35,11 +35,11 @@
35
35
  | `zoomIn(step?)` / `zoomOut(step?)` / `zoomBy(factor, origin?)` | 相对缩放,`origin` 为画布 CSS 像素锚点(缺省视口中心) |
36
36
  | `zoomTo(scale, origin?)` | 直接设成指定比例(像素 / 世界单位) |
37
37
  | `zoomToBounds(bounds, padding?)` | 把一段世界坐标矩形适应到视图内,退化成一点时只居中不改比例 |
38
- | `focusOn(target, options?)` | 把视角移到目标上:单条(搜索命中 / 要素 id / `{ type, index }` / 坐标 / 矩形 / 通道写法 / `{ lngLat }`)或**一批**(数组取整体包围盒 —— 搜到三个节点就是这三个点的中心当屏幕中心);`options` 认 `scale`、`center: true`(只居中、比例一点不动)、`padding`、`maxZoom`、`select`(顺带选中这批,`mode` 决定怎么并进当前选中集) |
39
- | `search(keyword \| options)` | 按关键字搜索节点 / 线路 / 通道并返回命中(**不动视角**:要不要聚焦由调用方自己调 `focusOn`),见「搜索与定位」 |
38
+ | `focusOn(target, options?)` | 把视角移到目标上:单条(搜索命中 / 要素 id / `{ type: 'point' \| 'line' \| 'channel', id \| index }` / `{ channelId }` 整条通道 / `{ type: 'group', channelId?, groupId }` / 坐标 / 矩形 / `{ lngLat }`)或**一批**(数组取整体包围盒 —— 搜到三个节点就是这三个点的中心当屏幕中心);字符串先节点后线路,裸字符串不表示通道,同 id 用 `type` 消歧,分组必须显式写 `type: 'group'`(全局唯一 `groupId` 可省略通道)。**缺省只居中、比例一点不动**;`options.zoom: true` 才按解析出的真实范围适配(节点 / 线路 / 通道 / 分组共用这一条,不额外放大),`scale` 优先级高于 `zoom`。`options` 还认 `center: true`(显式只居中,与缺省一致)、`padding`、`maxZoom`、`select`(顺带选中这批,`mode` 决定怎么并进当前选中集) |
39
+ | `search(keyword \| options)` | 按关键字搜索节点 / 线路 / 通道 / 通道分组并返回命中:`scope` 认复数的 `points` / `lines` / `channels` / `groups`,也认与 `focusOn` 一致的单数别名 `point` / `line` / `channel` / `group`;结果分 `points` / `lines` / `channels` / `groups` 四类,分组命中带 `channelIndex` / `groupId` / `groupIndex`,可直接交给 `focusOn` / `selectFeatures`。**不动视角**:要不要聚焦由调用方自己调 `focusOn`,见「搜索与定位」 |
40
40
  | `searchAsync(keyword \| options)` | 同一份口径的异步版:数据量大时把扫描放进 Worker(10 万点 / 10 万线下同步要占主线程 40 ms 上下的那一段),返回 `Promise<GisViewSearchResult>`;结果带 `worker` 与 `stale` 两个字段,见「异步搜索」 |
41
41
  | `setCamera(camera)` / `getCamera()` | 读写 `{ center, scale }` |
42
- | `getZoomBaseScale()` / `setZoomBaseScale(scale?)` | 读 / 重定**缩放联动的基准比例**(缺省是首次适应视图时的比例):不传参数就取当下相机的比例 —— 把现在这一屏当成「原样大小」,之后放大缩小都从它算倍数;与自适应比例开关 `zoomScaleAuto` 配套,见「自适应比例(zoomScaleAuto)」 |
42
+ | `getZoomBaseScale()` / `setZoomBaseScale(scale?)` | 读 / 重定**缩放联动的基准比例**(缺省是首次适应视图时的比例):不传参数就取当下相机的比例 —— 把现在这一屏当成「原样大小」,之后放大缩小都从它算倍数;配合 `pointZoomScale` / `lineZoomScale` 使用 |
43
43
  | `getBounds()` | 数据范围(没有点时回退到背景 GeoJSON 的 `rawBounds`,也就是 `aspectScale` 之前的真实经纬度范围) |
44
44
  | `getViewRect()` | 当前可视范围的世界坐标矩形;自建缩略图 / 坐标读数用它换算视口框,内置鹰眼图见「鹰眼图」 |
45
45
  | `getMinimapZoom()` / `setMinimapZoom(zoom)` / `zoomMinimapBy(factor, origin?)` | 读 / 写鹰眼图自身的倍数:`set` 夹在 `minimap.minZoom` ~ `maxZoom` 之间,`zoomMinimapBy` 以画布 CSS 像素 `origin` 为锚点缩放(缺省小图中心),没配 `minimap` 时是空操作 |
@@ -47,6 +47,8 @@
47
47
  | `getLine(ref)` | 取一条线路:拾取载荷 + `path` / `bounds` / `color` / `width`(与 `listLines()` 的一项同一份口径),通道回线还带 `channelId` / `groupId` / `channelIndex`;找不到返回 `null` |
48
48
  | `getChannel(ref)` | 取一条通道:数据那一份(`index` / `item` / `id`)+ 成员表(`lineIndexes` / `skeleton` / `offsets` / `paths`)+ 分组上下文 `context`(几组 / 当前哪组 / 每组拐点数)、`from` / `to` / `anchored` / `spacing`;找不到返回 `null` |
49
49
  | `listPoints(options?)` | 按条件列节点快照 `{ index, item, x, y, radius, layerRank, screen? }`:选项见 `GisViewListOptions`(`indexes` / `inView` / `limit` / `withScreen` / `visibleOnly`),可见判定与 `isPointVisible` 是同一份 |
50
+ | `getClusters()` | 当前点聚合的展示组 `GisViewCluster[]`:按数据顺序返回,含 `count: 1` 的单成员与 `count > 1` 的聚合圆;每项带 `id` / `pointIndexes` / `pointIds` / 世界坐标 `coordinate` / 当前屏幕坐标 `screen` / 半径 `radius` / 包围盒 `bounds`。原始 `points` 与 `getData()` 不变,聚合关掉时返回空表 |
51
+ | `getPointCluster()` | 当前点聚合配置的归一化读数 `GisViewPointClusterReadout \| null`:`{ enabled, radius, minPoints, maxZoom, active }`,`active` 说明当前缩放档是否仍在聚合(超过 `maxZoom` 后为 `false`);引擎未就绪时为 `null` |
50
52
  | `listLines(options?)` | 按条件列线路快照 `{ index, item, path, bounds, color, width, channelId?, groupId?, channelIndex?, screen? }`:选项与 `listPoints` 同一套;通道回线给的是骨架推导出来的那条折线(跟图上画的一致) |
51
53
  | `getLineChannel(ref)` | 这条线路属于哪条通道:`getLineChannel('line-a')`(id)/ `getLineChannel(12)`(`lines` 下标)给 `{ lineIndex, channelId, channelIndex, channel, groupId, group }`,不在通道里返回 `null`。点了一条回线想知道它归哪条通道 / 哪一组时用它,不必自己扫通道定义 |
52
54
  | `getElementBounds(keys)` | 一批要素 `GisViewFeatureKey[]`(`{ type: 'point' \| 'line', index }`,`getSelections()` 的结果可以直接传)的世界包围盒 `{ minX, minY, maxX, maxY }`;一个都没对上返回 `null`(数据已经换过) |
@@ -68,11 +70,13 @@
68
70
  | `hoverAt(clientX, clientY)` / `clearHover()` / `getHover()` | 程序化悬停:`hoverAt` 让指针「停」到某个客户端坐标上(悬停高亮、光标与 `onHover` 一起更新,返回与 `onHover` 载荷同一份读数,空白处 `null`;不走悬停阈值,也不用先订阅事件)、`clearHover()` 撤掉并补发一条 `onHover(null)`、`getHover()` 随时读当前这一份 —— 外部列表 / 表格 hover 到某一行时联动高亮图上的要素用它 |
69
71
  | `getFeaturesInRect(rect)` | 画布 CSS 像素矩形里有什么(与框选 / `selectInBox` 同一套坐标与判定):返回 `{ rect, worldRect, points, lines }` —— `points` 是框内的节点(与 `onSelect` 的 `items` 同一口径)、`lines` 是包围盒沾到框的线路(与 `listLines` 同一份)、`worldRect` 是这块像素对应的世界坐标范围;只读,不改选中也不画框 |
70
72
  | `getChannelsInView()` | 当前视口里有哪几条通道(同一通道的几回线只算一条):每条给 `{ channelId, channelIndex, channel, lineIndexes, groupIds }`,侧边栏「画面里的通道」、按视口统计用它 |
71
- | `selectFeatures(refs, options?)` | 接口选中(换一份选中集;`selectFeatures(null)` / 空数组就是清空):`refs` 认 id、id 数组、`{ type, index }`、通道写法(`{ type: 'channel', channelId }` / `{ channelId, groupId }` / `{ channelId, lineIds }`)以及拾取载荷与搜索命中;`options.mode` 给 `'replace'`(缺省,换一份)/ `'add'` / `'toggle'`(在就取消、不在就选上)/ `'remove'`,见「接口选中(selectFeatures)与读回选中」 |
73
+ | `selectFeatures(refs, options?)` | 接口选中(换一份选中集;`selectFeatures(null)` / 空数组就是清空):`refs` 认节点 / 线路 id、id 数组、`{ type, index }`、通道写法(`{ type: 'channel', channelId }` / `{ channelId, groupId }` / `{ channelId, lineIds }`)、分组写法(`{ type: 'group', channelId?, groupId }`,全局唯一 `groupId` 可省略通道)以及拾取载荷与搜索命中;`options.mode` 给 `'replace'`(缺省,换一份)/ `'add'` / `'toggle'`(在就取消、不在就选上)/ `'remove'`,见「接口选中(selectFeatures)与读回选中」 |
74
+ | `selectWaypoint(lineRef, pathIndex?, options?)` | 代码选中一个拐点:`selectWaypoint('line-1', 2)` / `{ lineIndex, pathIndex }` / `{ lineId, pathIndex }`,并同时选中所在线路;有效下标从 `getWaypoints()` 取。传 `null` 只取消拐点选中、不改要素选中集,见「代码选中拐点(selectWaypoint)」 |
72
75
  | `deselect(refs?)` | 取消选中:写法与 `selectFeatures` 完全一样,把这些从当前选中集里去掉、其余选中原样留着;不传 / `null` 等于取消全部(也就是 `selectFeatures(null)`),空数组是「没有目标」什么都不做,见「取消选中(deselect / selectFeatures(null))」 |
73
76
  | `getSelections()` | 读回选中结果 `[{ type, index, item, … }]`(按选中顺序):要 id 列表 / 个数 / 包围盒看 `getSelectionData()` |
74
77
  | `isSelected(ref)` | 这个要素在不在当前选中集里:`isSelected('node-0')`(id)/ `isSelected({ type: 'line', index: 12 })`(也可以只写下标,按 id 认不出来时当下标),返回 `true` / `false`。面板上高亮「当前这一行」时不必自己比对 `getSelections()`;选中集变化时缓存一份键集合(见「接口选中」),十万级选中下这一问也是常数级 |
75
78
  | `getSelectionData()` | 选中集一次取全:读数(`ids` / `count` / `pointCount` / `lineCount` / `channelCount`)加要素本身(`points` / `lines` / `channels`(按通道去重,与 `getData()` 同一批对象)),另有 `items`(与 `getSelections()` 同一份)、`pointIndexes` / `lineIndexes` 与 `bounds`(世界包围盒,空选中时 `null`),见「接口选中(selectFeatures)与读回选中」 |
79
+ | `getCascadeHighlight()` | 当前点击级联高亮的读数:`{ origin, scope, points, lines, pointIndexes, lineIndexes }`;没有高亮或 `cascadeHighlight` 关着时返回 `null`。它只读、不改选中集,见「点击级联高亮」 |
76
80
  | `selectInBox(box, options?)` | 按画布 CSS 像素矩形框选,等价于 Shift + 拖动(跟随 `shortcuts.boxSelect`);`options.selectionKinds` 可只覆盖这一次的节点 / 线路 / 通道类型过滤 |
77
81
  | `beginCopyPoint(options?)` | 开始复制一个节点:给 `id` / `index` 直接出预览(`commit: true` 出预览即落地,`follow: true` 跟随鼠标,`autoCommit: false` 停在预览态),不给来源就是「先点按钮再选节点」模式,见「节点复制」 |
78
82
  | `commitPointCopy()` / `cancelPointCopy()` | 落地 / 取消预览中的复制(返回 `end` / `cancel` 载荷) |
@@ -92,7 +96,7 @@
92
96
  | `cancelRemove()` | 取消这次删除(与 Esc 同一条路):数据一个字节都不动,返回取消掉的那份载荷(`invalidated: true` 表示请求因为数据变过而作废) |
93
97
  | `removeSelected(options?)` | 移除**当前选中的那一批**(工具栏「删除选中」同一条路):选中的节点连同绑在它们身上的线路一起删、剩下的选中回线也删掉,删完清空选中;返回 `{ ok, reason, pointIndexes, lineIndexes, pointIds, lineIds, points, lines, removedChannels }`(`lineIds` 里也含「因为节点被删而连带走掉」的线路),没有选中东西时 `ok: false` 并带回原因。一次调用只落一条历史、只发一条 `removeSelected` 数据变更 |
94
98
 
95
- | `exportImage(options?)` | 导出当前画面(含标签)为图片,返回 data URL,见「导出图片」 |
99
+ | `exportImage(options?)` | 导出当前画面(含标签)为图片,返回 data URL;`scope: 'viewport'`(缺省)导当前视口、`'all'` 临时适应全部可见内容,另有 `padding` / `type` / `quality` / `scale` / `background` / `includeLabels` / `download`,见「导出图片」 |
96
100
  | `exportSelection(options?)` | 把选中的节点(可选带上两端都在集合里的线路)单独导成图片 / 数据:`points` 不给就用选中集,`withLines` / `format` / `padding` 见「导出选中的节点」;返回 `{ ok, reason, points, lines, counts, box, pointIndexes, lineIndexes, image }`,出图时临时藏起别的要素与相机、出完还原 |
97
101
  | `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` 可以只要一部分,解不出的引用进 `warnings`,`format` 写错(或没注册)抛异常,见「导出数据」与「自定义导出格式」 |
98
102
  | `snapshotScene(options?)` | 当前画面的矢量快照(屏幕坐标的线 / 点 / 标签 / 背景 / 网格):`{ width, height, background, backgroundImage, grid, regions, lines, points, backgroundPoints, decorations, waypoints, raised, labels }`,`options.background: false` 丢背景、`options.backgroundColor` 只换底色、`options.labels` 原样带进去,见「导出图片」 |
@@ -119,7 +123,7 @@
119
123
  | `addChannelGroupWaypoint(channelId, groupId?, coordinate?)` | 给某个分组加拐点、把这一组单独拉走:不给坐标就按默认位置放一个(第 n 组沿法线往外让 n 档),其余分组一根拐点都不动,见「分组拐点」 |
120
124
  | `removeChannelGroupWaypoint(channelId, groupId?, pathIndex?)` | 删掉某个分组的拐点(缺省删最后一个) |
121
125
  | `runChannelAction(actionId, options?)` | 执行右键交互框里的动作:`select-group` / `deselect-group` / `add-waypoint` / `remove-waypoint` / `split` / `merge`(`options` 带 `channelId` / `groupId` / `coordinate` / `pathIndex` / `size`),返回执行之后的上下文 |
122
- | `beginAddChannel(options?)` | 进入「新增通道」:点第一个节点当起点,一条线跟随光标,点第二个节点出方案;`options` 支持 `kind`(`'channel'` 新建通道 / `'line'` 新建独立线路,缺省 `'channel'`)/ `circuits`(一次加几条,缺省 1)/ `autoCommit` / `continuous` / `chain` / `name` / `lineId` / `lineProps` / `lineItems`(第二段,连同线路一起新建)/ `routing`(`'straight'` / `'orthogonal'`)/ `path`(中间拐点)/ `channelProps` / `channelDraftStyle`(本次草稿的预览样式)/ `handoff`(交接模式:ready 发完收起草稿,自己调 `addChannel` 落地)/ `select`(点中的节点顺手选中),见「新增通道」 |
126
+ | `beginAddChannel(options?)` | 进入「新增通道」:缺省点两个节点选起点 / 终点,或由 `options.from` 预指定起点、只需再点终点;`options` 还支持 `kind`(`'channel'` 新建通道 / `'line'` 新建独立线路,缺省 `'channel'`)/ `circuits`(一次加几条,缺省 1)/ `autoCommit` / `continuous` / `chain` / `name` / `lineId` / `id` / `lineProps` / `linePropsByMember` / `lineItems`(第二段,连同线路一起新建)/ `routing`(`'straight'` / `'orthogonal'`)/ `path`(中间拐点)/ `channelProps` / `channelDraftStyle`(本次草稿的预览样式)/ `handoff`(交接模式:ready 发完收起草稿,自己调 `addChannel` 落地)/ `select`(点中的节点顺手选中)/ `originalEvent`,见「新增通道」 |
123
127
  | `getAddChannelDraft()` | 当前新增通道草稿(两个节点的信息与可选方案),没有草稿时为 `null` |
124
128
  | `updateAddChannelStyle(patch?)` | 字段级合并当前草稿的预览样式(`color` / `width` / `dot` / `dotColor` / `arrow` / `arrowSize` / `arrowPositions` / `arrowReverse` / `arrowColor` / `circuitSpacing` / `opacity` / `customDraw`);传 `null` 回到全局 `channelDraftStyle`,没有草稿时什么也不做 |
125
129
  | `continueAddChannel(options?)` | 开下一段草稿:连续模式落地时内部自动调(发 `phase: 'restart'`),也可以手动调;草稿已经退出时按 `options` 重新进来 |
@@ -148,7 +152,7 @@
148
152
  | `beginDecorationDraft(options?)` / `getDecorationDraft()` / `updateDecorationDraft(patch?)` / `commitDecorationDraft(overrides?)` / `cancelDecorationDraft(options?)` | 动态装饰器草稿:`target` 取 `'line'`(缺省)/ `'point'` / `'both'`,指针移到线路 / 回线或节点上就画临时装饰,移出立即收掉;`both` 按屏幕距离取更近者、等距优先节点,最近目标被白名单或上限拦住时尝试另一类。线路按 `scope`(`line` 单条 / `group` 同组 / `channel` 整条通道,非通道线退回单条)展开;节点侧 `placement` 只选位置来源(`offset` 相对偏移 / `fixed` 固定坐标 / `pointer` 鼠标位置),`anchor` 再选落地关系(`follow` 保存相对节点的屏幕偏移 / `fixed` 保存世界坐标),两者可自由组合,缺省按 `offset → follow`、`fixed / pointer → fixed` 推导。`allowedLines` / `allowedChannels` / `allowedGroups` / `allowedPoints` 与 `maxPerLine` / `maxPerPoint` 在悬浮与提交两处拦截,`allowDuplicate: false` 时按 `duplicateDistance` 检测同位置重复,`duplicateStrategy` 选 `offset` 自动避让或 `reject` 直接拦住。左键在目标上松开发 `ready`,空白处不发;`get` 取当前载荷,`update` 边交互边改图标 / 大小 / 方向 / 范围 / 位置来源 / 落地关系;`commit` 把线路与节点目标放在同一个 `batch` 里写入(一次 `dataChange`、一条历史),`cancel` 只清假装饰(Esc / 右键等价),数据与历史一个字节不动 |
149
153
  | `setChannelLineWidth(id, width)` / `setChannelColors(id, colors)` / `setChannelPath(id, coordinates)` | 只改一条通道的回线线宽 / 逐回线颜色 / 骨架拐点:前两个把**成员回线连同通道声明**一起改(一次 `batch`,只发一条数据变更、只落一步历史),颜色不够时按顺序循环取、只给一个就整条通道一个色(改**单条回线**的颜色用 `updateLine(id, { color })`,不必走整条通道的批量写法);`colors` 传字符串或字符串数组,`setChannelPath` 只写中间拐点(两端由节点坐标补齐);分过组的通道写第一组的声明(`groups[].path` 优先于顶层 `path`;「第一组」跳过 `lineIds` 空的分组声明 —— 引擎会把这种组整组丢掉),要改别的组用 `updateChannel(id, { groups })` |
150
154
  | `getParallelGroups()` | 新增拐点集合:被加过 / 拖过拐点的通道与线路,结构与 `test.json` 的 `parallel_groups` 一致 |
151
- | `setFilter(filter \| null)` | 设置或清空筛选条件(传 `null` 清空):完整格式 `{ points, lines, cascade, worker }`,只筛节点时可以简写成节点条件本身;见「筛选」 |
155
+ | `setFilter(filter \| null)` | 设置或清空筛选条件(传 `null` 清空):完整格式 `{ points, lines, cascade, mode, worker }`,只筛节点时可以简写成节点条件本身。`mode: 'hide'`(缺省)只隐藏、不删数据、也不清空选中集;`mode: 'remove'` 把筛选结果从数据里删掉,只发一条 `filterRemove` 数据变更与最终一条 `onFilter`,见「筛选」 |
152
156
  | `getFilter()` / `getFilterResult()` | 当前条件与最近一次结果:隐藏的点 / 线下标、可见数量、是否用了 Worker、耗时 |
153
157
  | `getVisibleData()` | 过滤之后的数据副本(`points` / `lines`) |
154
158
  | `getMovedPoints()` | 位置对账:相对上次对账(建视图 / 整份换数据 / 上一次 `clearMovedPoints`)位置变过的节点,按 id 去重、一趟扫出 `[{ index, id, x, y, item }]` —— 拖动 / `updatePoint` / `patchPoints` / 撤销都算,`removePoints` 删掉的记录顺手清掉,见「节点位置对账」 |
@@ -169,6 +173,9 @@
169
173
  | `unregisterOperator(name)` | 注销一个自定义运算符(返回它之前是否注册着):还在用这个名字的条件按认不出来处理(不成立,一个要素都不命中) |
170
174
  | `listOperators()` | 现在能用哪些运算符:内置 14 个在前,自定义按注册顺序跟在后面 |
171
175
  | `use(plugin)` | 运行时挂一个插件(写法与 `options.plugins` 里的一项一致):调用即执行,返回幂等的卸载函数(跑插件自己的 `destroy`),见「插件」 |
176
+ | `registerCommand(name, handler)` | 注册一个实例命令:`handler(payload, context)` 同步执行,`context` 是冻结的 `{ view, name }`;同名覆盖,返回幂等的注销函数,旧卸载函数不会误删后来注册的同名命令,见「实例命令」 |
177
+ | `executeCommand(name, payload?)` | 执行实例命令:成功给 `{ ok: true, name, value }`;名字非法 / 未注册 / handler 抛错给 `{ ok: false, name, reason }`,见「实例命令」 |
178
+ | `listCommands()` | 当前注册着哪些实例命令(按注册顺序;同名覆盖不改变原来的位置) |
172
179
  | `getFeatures()` / `setFeatures(next)` | 读当前生效的功能开关(跟随详细配置的也解析成 `true` / `false`)/ 换一份(预设名 / 开关表 / `true` / `false`),换完立刻生效并发一条 `onFeatureChange` |
173
180
  | `undo()` / `redo()` | 撤销 / 重做一步:返回 `{ type, label, canUndo, canRedo }`,历史没开或没得撤销 / 重做时返回 `null` |
174
181
  | `canUndo()` / `canRedo()` | 能不能撤销 / 重做(界面据此灰掉按钮) |
@@ -188,7 +195,7 @@
188
195
 
189
196
  返回 `this` 的方法都可以链式调用;返回其它值的方法(`get*` / `list*` / `export*` / `search` / `pick*` / `add*` / `remove*` / `copy*` / `merge*` / `locate*` 这些)在下面的「参数速查」里都标了返回类型。
190
197
 
191
- **怎么确认「组件到底有哪些属性和方法」**:上面那张表按功能分组介绍,下面的「参数速查」把实例的**每个成员逐个列全**(当前 249 个:246 个方法 + `el` / `canvas` / `gl` 三个属性),`options` 表列全 91 个键,数据字段表列全 242 个字段。这三份清单不靠手抄 —— 它们直接对着源码与类型对账:
198
+ **怎么确认「组件到底有哪些属性和方法」**:上面那张表按功能分组介绍,下面的「参数速查」把实例的**每个成员逐个列全**(当前 256 个:253 个方法 + `el` / `canvas` / `gl` 三个属性),`options` 表列全 96 个键,数据字段表列全 250 个字段。这三份清单不靠手抄 —— 它们直接对着源码与类型对账:
192
199
 
193
200
  - `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` 时只校验有没有过期。
194
201
  - `node docs/steps/check-doc-coverage.mjs`:10 项双向对账 —— 实例成员 × 文档 / × 参数速查 / × 类型、参数速查与实例方法主表各自反查过期条目、类型反查过期声明、数据字段 × 文档、options 键 × 文档 / options 表反查过期字段、demo 清单是否最新。加一个实例成员、改一个 options 键而没同步文档时,这个脚本会直接报出来。
@@ -196,4 +203,6 @@
196
203
 
197
204
  ---
198
205
 
206
+ **相关章节**:[08 选中与拐点](08-选中与拐点.md) · [17 参数速查(逐个方法)](17-参数速查.md) · [18 逐方法示例(复制就能跑)](18-逐方法示例.md)
207
+
199
208
  [← 15 导出与集成](15-导出与集成.md) · [手册目录](../GisView.md) · [17 参数速查(逐个方法) →](17-参数速查.md)