gis-view-webgl2 0.1.4 → 0.1.6

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 (37) hide show
  1. package/README.md +10 -7
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.ts +225 -20
  4. package/dist/index.js +1 -1
  5. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +109 -0
  6. package/docs/GisView.md +53 -53
  7. 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 +91 -0
  8. package/docs/demos/ConnectDemo.vue +114 -3
  9. package/docs/demos/MinimapDemo.vue +216 -17
  10. package/docs/demos/SearchFilterDemo.vue +113 -1
  11. package/docs/demos/api-index.js +28 -20
  12. package/docs/demos/demos.js +1 -1
  13. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +25 -16
  14. package/docs/demos/gis-view-demo/useGisViewDemo.js +27 -16
  15. package/docs/index.html +3 -3
  16. package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +8 -5
  17. package/docs/internals/16-01-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/212.md +18 -5
  18. package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +11 -1
  19. package/docs/internals/18-/346/200/247/350/203/275/345/256/236/346/265/213/344/270/216WebGPU/350/257/204/344/274/260.md +73 -1
  20. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +14 -5
  21. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +2 -2
  22. package/docs/manual/05-/347/224/273/350/264/250/344/270/216/346/211/271/351/207/217/346/233/264/346/226/260.md +1 -1
  23. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +1 -1
  24. 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 +423 -3
  25. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +99 -30
  26. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +12 -7
  27. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +14 -6
  28. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +137 -6
  29. 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 +4 -0
  30. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +8 -3
  31. 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 +6 -3
  32. package/docs/steps/README.md +6 -6
  33. package/docs/steps/selftest-demos.html +252 -9
  34. package/docs/steps/selftest-docs.html +3 -3
  35. package/docs/steps/selftest-full.html +357 -13
  36. package/docs/steps/selftest-perf.html +599 -382
  37. package/package.json +1 -1
@@ -40,7 +40,7 @@ getStats().quality // onStats 里也是同一份读数:读数面
40
40
  | `gl.LINES`(每段两个顶点) | 112 ms/帧 | 23 ms/帧 |
41
41
 
42
42
  - **`antialias`**(缺省开):MSAA 会把这类细长三角形放大约 5 倍,是最省事的一根杠杆 —— 关掉线条边缘会有锯齿(点与圆符号本来就有距离场抗锯齿,几乎看不出来)。软件光栅(远程桌面 / 驱动被拉黑回落 SwiftShader)下引擎自动关掉它;读数看 `getStats().antialias`。
43
- - **`interactionThinLines`**(缺省开):交互期细线快路径 —— 可见线段全部细到 3px 以内、且整批没有虚线时,拖动 / 缩放期间改用 `gl.LINES` 画(1 设备像素、不画光晕),停手立刻回到带像素宽度与虚线的四边形展开。两种画法共用同一张实例缓冲,所以位置不会跳、选中与悬停都不受影响,只是交互那几帧的线更细更硬。同一页交替开关实测:交互期帧耗时 p50 **194 ~ 208 ms → 132 ~ 139 ms**,同一 2.2 s 窗口的帧数 **13 → 19 ~ 24**;这一帧走没走快路径看 `getStats().thinLines`。
43
+ - **`interactionThinLines`**(缺省开):交互期细线快路径 —— 可见线段最大半宽 ≤ 0.6 设备像素(dpr = 1 时约 1.2px 线宽)、且整批没有虚线时,拖动 / 缩放期间改用 `gl.LINES` 画(1 设备像素、不画光晕),停手立刻回到带像素宽度与虚线的四边形展开。2px 实线不会走这一档,但全实线四边形另有轻量片元;两种画法共用同一张实例缓冲,所以位置不会跳、选中与悬停都不受影响,只是交互那几帧的线更细更硬。同一页交替开关实测:交互期帧耗时 p50 **194 ~ 208 ms → 132 ~ 139 ms**,同一 2.2 s 窗口的帧数 **13 → 19 ~ 24**;这一帧走没走快路径看 `getStats().thinLines`。
44
44
  - **`interactionNoMsaa`**(缺省开):上一轮把账拆到「MSAA 4× 把细长三角形放大约 5 倍」,那就干脆不带 MSAA 画 —— 拖动 / 缩放 / 框选期间与动画在跑的那些帧,整帧先画进一张不带 MSAA 的后备贴图(分辨率跟画布一致,画布尺寸变了当帧重建;建不出来就放弃这一档、这一帧照旧直接画),帧末用一笔满屏四边形整幅贴回画布;停手 160ms(或动画停帧)之后的下一帧照旧直接画进带 MSAA 的画布,所以**静止画面一个像素都没变**,缺的只是这些活动帧的边缘抗锯齿。真机实测(898×558 画布、10 万点 / 12 万线、`quality: 'high'`):交互期单帧(`readPixels` 强制同步读回)**133 → 36.6 ms**,真实输入长跑(120 步)拖动 p50 **130 → 16 ~ 22 ms**、缩放 p50 **239.5 → 16.7 ms**、平移 p95 **129 → 17 ms**;同一台机器上的动画帧(10 万点 + 12 万线、涟漪与线路动点都开)**212 → 53 ms/帧**(只开涟漪 187 → 48 ms、只开线路动点 199 → 47 ms,2.6 s 窗口里的帧数 12 ~ 15 → 49 ~ 57);同一交互状态下开 / 关后备缓冲各抓一帧整幅对拍,差异像素 6.4%、平均差 0.53 / 255(都在抗锯齿边缘上)。这一帧走没走看 `getStats().noMsaa`,`updateOptions({ interactionNoMsaa })` 可以运行时改;透明底 / 自定义底色的导出场景(要逐像素可控)与本来就没开 MSAA 的画布不走这条。
45
45
  - **点符号的方框也留了一圈**(这一轮):上一条「静止画面一个像素都没变」当初对线路成立、对**节点符号并不成立** —— `gl.POINTS` 的方框正好是形状的外接框,MSAA 那一遍按方框的采样覆盖率合成、光栅化又按「像素中心落在方框内」判覆盖,方框边压住描边的那圈像素于是只在 MSAA 那一遍被削掉;交互期走非 MSAA 后备缓冲时没人削它,拖动节点就是「描边一厚一薄、一动一动」。现在方框每边多留 1 个设备像素(`shaders.js` 的 `POINT_EDGE_PAD`):顶点着色器按 `size + 2` 定 `gl_PointSize` 并传出 `v_quadRatio = quadSize / size`,片元里把 `gl_PointCoord` 缩回形状自身的尺度,距离场与线宽一个字节都没动。真机(`antialias: true`)对拍:同一节点拖动帧与松手后的静止帧**逐像素一致(差异 0 字节)**,改之前同一窗口差异 86 字节 / 最大差 35;代价是方框面积大了一圈,10 万点满屏实测 10 → 11.8 ms/帧(0.5 个设备像素实测还躲不干净,所以取 1.0)。
46
46
  - **贴图符号的方框吸附到设备像素边界的「栅格」上**(这一轮,接着上一条):上一条管的是 `gl.POINTS` 那条形状管线,SVG / 图片走的是四边形贴图 —— 它的吸附一直在**引擎像素**坐标里做(上一轮那版 `floor(anchor - halfSize) + halfSize` 只挪了「吸中心还是吸边」),而引擎像素坐标的原点在**画布中心**(`gl_Position = pixel * 2 / resolution`)、画布中心又正好落在某个设备像素的中心上:设备像素的边界在引擎坐标里是一张固定的栅格 —— 分辨率是偶数时落在整数上、奇数时落在半整数上(就是「分辨率一半」的小数部分)。只在整数上吸附的话,奇数分辨率(DPR 1.5、奇数宽度的画布)下四条边全压在设备像素中心上:MSAA 那一遍按采样点给半格覆盖率、非 MSAA 那一遍(`interactionNoMsaa`)按「像素中心在方框里」判覆盖、整个像素丢,于是拖动 / 缩放 / 平移期间图标最外圈那圈描边整行消失、停手又回来(「一闪一闪」),与图标的尺寸、圆方都无关(上一轮「只在奇数尺寸时露出来」是 318×238 那块偶数画布带出来的读数)。现在 `SPRITE_VERTEX_SHADER` 按这张栅格吸附:`vec2 grid = fract(u_resolution * 0.5); anchor = floor(clip * 0.5 * u_resolution + u_offsetPx - halfSize - grid) + halfSize + grid;` —— 四条边永远落在设备像素边界上,两条路径要么整格画、要么整格不画;偶数分辨率退化成上一轮那一行、逐像素不变(318×238 画布上 20 / 21 / 22 px × 四个位置共 12 个图标与旧写法 0 差异),贴图也还是 1:1 落在屏幕上。真机对拍(`antialias: true`):319×238 画布上 20 / 21 px 的贴图符号旧写法「平移帧 vs 交互帧」差 120 / 126 字节、本轮 0;DPR 1.5 的 449×357 画布上中心吸附 / 上一轮那行 / 本轮是 298 / 255、373 / 255、0 / 0。
@@ -241,7 +241,7 @@ Demo 的「通道与数据」页直接接了两类假接口:通道分组拐点
241
241
 
242
242
  - **加点 / 删点 / 拖点只作用于同一分组**:同一分组共用一条骨架,其余分组的线路不动。
243
243
  - 分组结果会写回 `channels[].groups`(`{ id?, lineIds, path?, waypointOffsets? }`),下次 `updateOptions` / 换数据按它重建;也可以一开始就手写 `groups` 而不是 `lineIds` + `path`。
244
- - `getChannelMembers()` 与鹰眼图都按分组各成一份:`channelCircuits: false` 时每个分组各画偏移最靠中间的那一根回线。
244
+ - `getChannelMembers()` 与鹰眼图都按分组各成一份:`channelCircuits: false` 时每个分组把当前可见成员逐顶点平均成一根居中代表线(双回线落在两根正中间,三回线落在中间那条)。
245
245
 
246
246
  ---
247
247
 
@@ -102,6 +102,8 @@ view.beginAddChannel({ from: { id: 'station-a' }, select: true })
102
102
  view.continueAddChannel({ from: 'station-c', kind: 'line' }) // 连续新增的下一段也能换指定起点
103
103
  ```
104
104
 
105
+ > `beginAddChannel` / `continueAddChannel` 只支持预指定 **起点** `from`,没有 `to`。`beginAddChannel({ from: 'station-a', to: 'station-b' })` 里的 `to` 会被忽略:起点已经选好,仍要再点一次终点才能进入 `ready`。想完全程序化地指定两端并落地,用 `addChannel` / `addLine`,不要先开草稿。
106
+
105
107
  **直线 / 正交折线(`routing` / `path`)**:`beginAddChannel()` / `continueAddChannel()` 新增 `routing: 'straight' | 'orthogonal'` 与 `path`。`routing` 缺省 `'straight'`;写 `'orthogonal'` 且没有有效中间拐点时,组件自动补一个「先横后纵」的拐点 `[to.x, from.y]`。`path` 是**中间拐点**(不含两端),写了有效拐点就按它走,此时不会再追加自动拐点;草稿与 `commitAddChannel` / `addLine` / `addChannel` 共用同一份路由解析,预览和落地的折线完全一致。`onChannelDraft` / `onChannelDraftClick` 载荷里也带 `routing` / `path`,两端选好后 `path` 是实际会落地的中间拐点。
106
108
 
107
109
  | 字段 | 类型 | 缺省 | 说明 |
@@ -162,7 +164,7 @@ view.beginAddChannel()
162
164
  **取消、默认添加,或自行落地**:选完两个节点后草稿只是预览,除非 `autoCommit: true`,否则数据一个字节都不动。三种收尾方式:
163
165
 
164
166
  - `cancelAddChannel()`:收起草稿并补一条 `phase: 'cancel'`;`endAddChannel()` 静默收起。两者都不写数据。
165
- - `commitAddChannel()`:按推荐方案(或传入的 `resolution`)走内置落地,草稿里的 `routing` / `path` 会一起交给 `applyChannelAdd`。
167
+ - `commitAddChannel()`:按推荐方案(或传入的 `resolution` / 新增线路 / 通道对象)走内置落地,草稿里的 `routing` / `path` 会一起交给 `applyChannelAdd`;对象字段优先,见下文「对象直传」。
166
168
  - `addLines()` / `addChannels()`:绕开提交,用草稿读数自己落地。独立线路写完整 `path`;通道只把 `path` 当中间拐点。落地后调用 `endAddChannel()` 收起假通道。
167
169
 
168
170
  ```js
@@ -302,7 +304,7 @@ view.addLine({ from: 'station-a', to: 'station-b' }, [{ id: 'n-1' }, { id: 'n-2'
302
304
  | `lineItems` | `GisViewChannelLineItems` | 无 | 连同线路一起新建的一条 / 一批;给了几条就建几条 |
303
305
  | `routing` | `'straight' \| 'orthogonal'` | `'straight'` | 直连,或自动补一个先横后纵的拐点 |
304
306
  | `path` | `GisViewCoordinate[]` | 无 | 中间拐点(不含两端);有效时优先于 `routing` 的自动拐点 |
305
- | `channelDraftStyle` | `GisViewChannelDraftStyle` | 全局 `channelDraftStyle` | 本次草稿的预览样式,草稿结束即丢弃 |
307
+ | `channelDraftStyle` | `GisViewChannelDraftStyle` | 全局 `channelDraftStyle` | 本次草稿的预览样式,草稿结束即丢弃;含两端示例点 `dots` 与示例线路层级 `layer`,见下文「示例点与层级」 |
306
308
  | `handoff` | `boolean` | `false` | `ready` 发完就收掉假通道,由使用方自己落地;与 `autoCommit` 同给时先自动落地 |
307
309
  | `select` | `boolean` | `false` | 点节点时同步进选中集(走 `onSelect` / `onSelectChange`);预指定 `from` 时起点当场选中 |
308
310
  | `channelProps` | `Partial<GisViewChannel>` | 无 | 新通道透传字段(宽度、节点宽度、偏移方式…) |
@@ -332,7 +334,16 @@ view.updateOptions({
332
334
  console.log(payload.from.id, payload.to.id, payload.options, payload.preferred)
333
335
  // blocked 为 true 时只能放弃,reason 说明原因(同一个节点 / 已有通道的每个方案都不可用)
334
336
  // 手动提交:resolution 缺省用 preferred,也可以给 { type: 'joinGroup', groupId }
335
- view.commitAddChannel(payload.options.find(option => option.type === 'joinGroup' && option.enabled))
337
+ const chosen = payload.options.find(option => option.type === 'joinGroup' && option.enabled)
338
+ if (chosen) {
339
+ // 方案读数是 { type, enabled, count, lineIds, ... },不能整条回传;只取 resolution 认识的四个键
340
+ view.commitAddChannel({
341
+ type: chosen.type,
342
+ groupId: chosen.groupId,
343
+ groupIndex: chosen.groupIndex,
344
+ channelId: chosen.channelId
345
+ })
346
+ }
336
347
  }
337
348
  if (payload.phase === 'commit') {
338
349
  // 落地结果:line 是写进 lines 的新回线(已自动选中)
@@ -365,6 +376,415 @@ const plan = view.getChannelAddOptions('station-a', 'station-b')
365
376
  - **移除新增**:`removeLine(lineId)` 把刚落地的这条回线从数据里摘掉,通道声明(`lineIds` 与 `groups`)一起改,通道一条回线都不剩就整条删掉;`removeChannel(channelId)` 整条通道一起删。两个方法都返回结果对象(`{ ok, line / lineIds, channelId, removedChannel, reason? }`),并且已经处理好选中下标 —— 删掉线路之后 `getSelections()` 不会指到别的线上。
366
377
  - `getAddChannelDraft()` 读当前草稿(没有草稿时为 `null`);方案表也可以直接问:`getChannelAddOptions(from, to, count?, kind?)`,实例层按节点 id、引擎层同名方法按下标;只要「这两个点之间到底有没有通道 / 有几组」的读数(不做方案判断)用 `getChannelBetween(from, to)` —— 有通道时 `hasChannel: true`、`channel` / `groups` / `groupCount` / `currentGroupId` 与方案表同一份,没有通道时 `hasChannel: false` / `channel: null` / `groups: []`,端点认不出来返回 `null`。
367
378
 
379
+ **示例点与层级(`channelDraftStyle`)**
380
+
381
+ `channelDraftStyle.dots` 控制草稿两端的示例点:缺省 `true` 按 `dot` / `dotColor` 画,`false` 两个都不画;写成对象可统一设置 `show` / `size` / `color` / `shape`(`'circle'` 或 `'square'`),再用 `dots.from` / `dots.to` 分别覆盖某一端。示例点始终画在**真正点中的节点中心**,不会跟着多回线的法线偏移跑到节点旁边;`dots: false` 只关示例点,预览线仍在。
382
+
383
+ `channelDraftStyle.layer` 决定示例线路画在真实节点的哪一侧:`'above'`(默认)连同箭头 / `customDraw` 一起压在真实节点上;`'below-points'` 先画示例线、箭头与 `customDraw`,再把真实节点整层盖上去,示例线因此位于实际节点层级之下。示例点不受这个开关影响,始终在真实节点之后绘制,保证两端仍看得见。
384
+
385
+ #### `commitAddChannel`:签名、全部传参与五种写法
386
+
387
+ ```ts
388
+ commitAddChannel(input?, options?): GisViewChannelAddResult
389
+
390
+ input?:
391
+ | 'auto' | 'newChannel' | 'newGroup' | 'joinGroup' | 'newLine'
392
+ | { type?: 'newChannel' | 'newGroup' | 'joinGroup' | 'newLine'; groupId?: string | number; groupIndex?: number; channelId?: string | number }
393
+ | GisViewLine // 直接线路对象
394
+ | GisViewChannel // 直接通道对象(必须有 lineIds)
395
+ | GisViewChannelAddCommitInput // 容器写法
396
+ | undefined | null
397
+
398
+ options?: {
399
+ auto?: boolean
400
+ continuous?: boolean
401
+ originalEvent?: Event | null
402
+ }
403
+ ```
404
+
405
+ **调用前提**:它只负责把当前草稿落地,不能替代 `addChannel` / `addLine` 做无草稿的程序化新增。调用前必须已经按下面的顺序走到 `ready`:
406
+
407
+ 1. `beginAddChannel(options?)` 进入草稿,或由 `continueAddChannel(options?)` 开出下一段。
408
+ 2. 通过点击选好起点 / 终点;写了 `options.from` 时起点已预选,只需再点终点。草稿配置只支持 `from`,没有 `to`;写 `{ from, to }` 时 `to` 会被忽略。
409
+ 3. `getAddChannelDraft()` 返回 `phase: 'ready'`,且 `plan`、`from`、`to` 都有值;`onChannelDraft` 也会收到同一次 `ready`。
410
+ 4. 调 `commitAddChannel(...)`。成功后草稿结束、新回线自动选中;连续模式则在原地重开下一段。
411
+
412
+ 以下两条任一不满足时不会写数据:
413
+
414
+ | 情况 | 返回 | 草稿 |
415
+ | --- | --- | --- |
416
+ | 功能开关 `channelDraft` 关着 | `{ ok: false, reason: '新增通道已被功能开关关闭' }` | 保留 |
417
+ | 没有草稿、仍在 `start` / `from` / `pick`,或没有可选方案 | `{ ok: false, reason: '还没有选好两个节点' }` | 保留 |
418
+
419
+ **五种写法一览**。识别顺序是:先认无参 / 旧 resolution;其余对象先看有没有容器键,有就按容器处理;没有容器键再按 `lineIds` 或线路形状识别直接对象;实在认不出的对象最后仍交回 `resolution`。所以只要写成下列任一形状,就不需要先调 `addLine` / `addChannel`:
420
+
421
+ | 写法 | 识别条件 | 缺省落地方式 | 典型用途 |
422
+ | --- | --- | --- | --- |
423
+ | 无参 | `input` 为 `undefined` / `null` | 用草稿的 `preferred` 方案 | 接受界面推荐的「新通道 / 新分组 / 并入某组」 |
424
+ | 旧 resolution | 字符串;或只含非 `undefined` 的 `type` / `groupId` / `groupIndex` / `channelId` 的对象 | 按方案表挑一条;`'auto'` 的优先级是 `newChannel → newGroup → joinGroup` | 让用户明确选「新通道 / 新分组 / 并入哪组」 |
425
+ | 直接线路对象 | 普通对象、没有 `lineIds` 数组,且至少有一个:`kind: 'line'`、`path` 数组、`from`、`to`、`start`、`end` | 按独立线路落地,不走通道方案表 | 最后一步直接把业务线路对象交给组件 |
426
+ | 直接通道对象 | 普通对象且 `Array.isArray(lineIds)`;同时不能再出现容器键 `channel` / `line` / `lines` / `lineData` / `lineItems` / `channelGroups` / `group` / `channelProps` / `resolution` | 缺省 `resolution: 'newChannel'`;`lineIds` 是回线 id 种子,`groups` 是分组声明 | 一次写入一条新通道及其全部成员 |
427
+ | 容器对象 | 出现任一容器键:`channel` / `line` / `lines` / `lineData` / `lineItems` / `channelGroups` / `group` / `channelProps` / `resolution`;只写 `lineProps` 也会按容器处理 | 各段分别覆盖草稿;`resolution` 不写时沿用草稿推荐方案 | 需要逐条回线字段、显式覆盖 `routing`,或把通道提交到已有分组 |
428
+
429
+ > 空对象 `{}` 不是「新建通道」,会按旧 resolution 交给方案表(通常得到「指定的通道 / 分组没有可用方案」)。想接受推荐方案请直接不传参。直接线路对象里的 `start` / `end` 只参与形状识别,**不会**替代 `from` / `to`;要让落地点与草稿不同,请写 `from` / `to`。
430
+
431
+ **旧 resolution 的字符串与对象**
432
+
433
+ | `resolution` | 行为 |
434
+ | --- | --- |
435
+ | 不写 / `null` / `'auto'` | 依次找可用的 `newChannel` → `newGroup` → `joinGroup`;前一个不可用就退下一个。独立线路草稿直接按 `newLine` 落地 |
436
+ | `'newChannel'` | 新建通道;两个节点之间已经有通道时找不到同类型可用方案,返回 `ok: false` |
437
+ | `'newGroup'` | 在已有通道里新加一组;没有通道、组数已满或回数余量不够时失败 |
438
+ | `'joinGroup'` | 并入一个可用的已有分组;不带 `groupId` 时沿用方案表挑中的那一组 |
439
+ | `'newLine'` | 只在 `kind: 'line'`(独立线路草稿)时有效;普通通道草稿传它会失败 |
440
+ | `{ type, groupId?, groupIndex?, channelId? }` | 按字段逐项收窄;`type` 缺省时,只写 `groupId` / `groupIndex` 会自动当作 `joinGroup` |
441
+ | `{ groupId }` / `{ groupIndex }` | `joinGroup` 的简写。二者都写时以 `groupId` 为准 |
442
+ | `{ channelId }` | 在指定通道内按「新分组 → 第一个能并的分组」挑;没有已有通道时 `channelId` 只是新通道 id,不会当成并组 |
443
+ | `payload.options.find(option => option.enabled)` | **不能整条回传**。方案读数还带 `enabled` / `count` / `lineCount` / `lineIds`,不满足旧 resolution 的「只含四个键」条件;只取 `{ type, groupId, groupIndex, channelId }` 这个子集 |
444
+
445
+ ```js
446
+ // 1. 接受推荐方案
447
+ const accepted = view.commitAddChannel()
448
+
449
+ // 2. 明确要「新建通道」或「并进某个分组」
450
+ view.commitAddChannel('newChannel')
451
+ view.commitAddChannel({ type: 'joinGroup', groupId: 'corridor-1::2' })
452
+
453
+ // 3. 让界面把用户点中的那条方案交回来(只取 resolution 认识的字段)
454
+ view.updateOptions({
455
+ onChannelDraft: payload => {
456
+ if (payload.phase === 'ready' && !payload.blocked) {
457
+ const chosen = payload.options.find(option => option.enabled && option.type === 'joinGroup')
458
+ if (chosen) {
459
+ const result = view.commitAddChannel({
460
+ type: chosen.type,
461
+ groupId: chosen.groupId,
462
+ groupIndex: chosen.groupIndex,
463
+ channelId: chosen.channelId
464
+ })
465
+ console.log(result.ok, result.lineIds, result.channelLineIds)
466
+ }
467
+ }
468
+ }
469
+ })
470
+ ```
471
+
472
+ > `ready` 里的 `option` 是**方案读数**,不是可直接回传的 `resolution`。建议固定只取四个定位字段;整条传进来时,带 `lineIds` 的方案可能被识别成直接通道对象,其余带容器键的对象也可能走成容器,而不是按方案表选组。
473
+
474
+ #### 直接线路对象:字段怎么分、`path` 怎么算
475
+
476
+ 直接线路对象面向「最后一步已经有一份线路业务数据」的场景。它不要求先建独立线路草稿;只要当前草稿已经 `ready`,传入对象就会按 `kind: 'line'` 落地一条不挂通道的线路。
477
+
478
+ | 对象字段 | 去向 | 说明 |
479
+ | --- | --- | --- |
480
+ | `from` / `to` | 新线路两端 | 覆盖草稿两端;必须是数据里真实存在的节点 id。两端相同会按方案表给出 `blocked` 原因 |
481
+ | `id` | 新回线 id 种子 | 与 `options.lineId` 同义;与已有线路撞车时自动改成 `-1` / `-2` |
482
+ | `name` | 新线路名 | 不写时沿用草稿名(草稿缺省按两端节点生成) |
483
+ | `path` | **中间拐点** | 和草稿的 `path` 同一口径:首尾仍由 `from` / `to` 覆盖;要整条现成折线请用容器里的 `lines: [{ id, path: [首, …, 尾] }]` |
484
+ | `lineProps` | 额外线路字段 | 最后合并,优先级高于对象上的普通业务字段 |
485
+ | 其余字段(`color` / `width` / `lineType` / `voltageLevel`…) | `lineProps` | 原样透传到每一条新回线,可参与热力图、提示、筛选 |
486
+ | `kind` | 只用于识别 | 写 `'line'` 可让一个没有 `from` / `to` / `path` 的对象也按独立线路解释 |
487
+
488
+ ```js
489
+ // 直接把业务对象交给草稿提交:虚线、颜色、线宽和自定义字段都会写进新线路
490
+ const result = view.commitAddChannel({
491
+ id: 'drop-line-1',
492
+ name: '备用联络线',
493
+ from: 'station-a',
494
+ to: 'station-b',
495
+ color: '#ff7a45',
496
+ width: 3,
497
+ lineType: 'dashed',
498
+ voltageLevel: '10kV',
499
+ // 中间拐点,不含首尾;首尾仍是 station-a / station-b 的坐标
500
+ path: [[116.72, 39.91], [116.86, 39.95]]
501
+ })
502
+
503
+ console.log(result.ok) // true
504
+ console.log(result.kind) // 'line'
505
+ console.log(result.lineIds) // ['drop-line-1']
506
+ console.log(result.channelId) // null
507
+ ```
508
+
509
+ #### 直接通道对象:`lineIds`、`groups` 与样式怎么写
510
+
511
+ 直接通道对象适合「一次给全通道 + 成员」。识别它的硬条件是 `lineIds` 必须是数组;没有 `lineIds` 的对象即使写了 `groups`,也只会被当成旧 resolution 或无法识别的对象。
512
+
513
+ | 对象字段 | 去向 | 说明 |
514
+ | --- | --- | --- |
515
+ | `lineIds` | 新回线 id 种子 | 每个 id 生成一条新回线;`lineIds` 为空数组时回线数量回落到草稿的 `circuits` / `lineItems`,不会清空草稿 |
516
+ | `groups` | 分组声明 | 等价于容器的 `channelGroups`;组的 `lineIds` 应使用这次生成的成员 id。第一组不写成员时拿本次全部回线,其余空组会被丢掉 |
517
+ | `id` | 新通道 id | 只决定新通道的身份;回线 id 一律来自 `lineIds`,这个 `id` 不参与逐条回线的 id 或前缀 |
518
+ | `name` | 通道名与新线路名 | 新线路没有逐条 `name` 时一起使用 |
519
+ | `from` / `to` | 新通道两端 | 覆盖草稿;实际回线端点也按它落 |
520
+ | `path` | 通道骨架的中间拐点 | 不含两端;分组各自写了 `path` 时以分组那一份为准 |
521
+ | `lineProps` | 全部新回线的公共字段 | 直接通道对象自己的 `lineProps` 会生效,优先级低于每一条逐条字段;容器写法里的 `channel.lineProps` **不合并**,见下文容器优先级 |
522
+ | 其余字段(`width` / `nodeWidth` / `inset` / `tooltip` / `customCode`…) | `channelProps` | 原样写到新通道上 |
523
+
524
+ ```js
525
+ // 通道对象:两个 lineIds 各生成一条回线;groups 把它们分成两组
526
+ view.commitAddChannel({
527
+ id: 'object-channel-1',
528
+ name: '东侧双回',
529
+ from: 'station-a',
530
+ to: 'station-b',
531
+ lineIds: ['object-line-1', 'object-line-2'],
532
+ groups: [
533
+ { id: 'object-channel-1::1', lineIds: ['object-line-1'], path: [[116.75, 39.86]] },
534
+ { id: 'object-channel-1::2', lineIds: ['object-line-2'], path: [[116.75, 39.98]] }
535
+ ],
536
+ width: 28,
537
+ nodeWidth: 1,
538
+ offsetMode: 'tangent',
539
+ lineProps: { color: '#2f6fd0', width: 2.4, lineType: 'dashed' },
540
+ owner: '运维一班'
541
+ })
542
+ ```
543
+
544
+ > 直接通道对象的回数只由 `lineIds` 决定;`lineIds: []` 时回落到草稿的 `lineItems` / `circuits`。直接通道对象上另外写的 `circuits` 不会改回数,它会作为普通通道字段收进 `channelProps`;要覆盖本次回数,请用容器顶层 `circuits`,或在 `beginAddChannel` 时配置。
545
+ >
546
+ > `groups[].lineIds` 应写本次 `lineIds` / `lines` 真正生成的回线 id。写了不存在的 id 不会让整次提交失败:组件会报 warning,并在声明落地前按本次成员表调和(剔掉不属于本次的回线、把没落组的成员补进第一组)。
547
+
548
+ > **直接通道对象缺省按新通道提交**。要在提交对象的同时并进已有分组,必须换成容器写法并写 `resolution`,例如 `{ resolution: { type: 'joinGroup', groupId: 'corridor-1::2' }, lines: [...] }`。直接把 `resolution` 塞进「带 `lineIds` 的通道对象」会把它识别成容器,顶层 `lineIds` 不再作为通道成员使用。
549
+
550
+ #### 容器对象:逐段覆盖与精确优先级
551
+
552
+ 容器是表达能力最完整的一种写法。所有段都可省略,写了哪一段就覆盖哪一段,没写的继续沿用当前草稿:
553
+
554
+ | 容器键 | 类型 | 作用 |
555
+ | --- | --- | --- |
556
+ | `channel` | `GisViewChannel` | 新通道声明:`id` / `name` / `from` / `to` / `path` / `lineIds` / `groups` 与业务字段 |
557
+ | `line` | `GisViewLine` | 一条独立线路;缺省把本次 `kind` 定成 `'line'`,不是「只含一条的 `lines`」。只有同时显式写 `kind: 'channel'` 时,它才作为一条成员回线挂进通道 |
558
+ | `lines` | 一条或一批线路数据 | 逐条回线字段,支持对象、裸字符串 / 数字 id;**给了几条就建几条** |
559
+ | `lineData` / `lineItems` | 同上 | `lines` 的兼容别名;三者同时写时的优先级是 `lines > lineData > lineItems` |
560
+ | `channelGroups` / `group` | 一组或整份分组声明 | `channelGroups` 优先于 `group`;都写时以 `channelGroups` 为准 |
561
+ | `channelProps` | `Partial<GisViewChannel>` | 最后覆盖新通道字段,优先级高于 `channel` 上的普通字段 |
562
+ | `lineProps` | `Partial<GisViewLine>` | 公共回线字段;合并顺序是草稿 `lineProps` → 容器顶层 `lineProps` → `line.lineProps`,最后被逐条 `lines[i]` 覆盖。`channel.lineProps` 在容器里不参与合并 |
563
+ | `resolution` | 旧 resolution 的字符串 / 对象 | 不写时:有 `channel` 按 `newChannel`,否则沿用草稿推荐方案 |
564
+ | `kind` | `'channel' \| 'line'` | 显式改这一条是通道还是独立线路 |
565
+ | `from` / `to` / `path` / `routing` | 同草稿 | 顶层端点、中间拐点与走线方式;`routing` 只认容器这一层 |
566
+ | `circuits` | `number` | 没有有效 `lines` 时一次建几回;有 `lines` 时按条数(`circuits` 被忽略) |
567
+ | `id` / `lineId` | 线路 id 种子 | `lineId` 优先;通道 id 请写在 `channel.id` 或 `channelId` |
568
+ | `channelId` / `channelName` | 通道 id / 名称 | `channelId` 在无已有通道时是新通道 id;已有通道时是并组筛选条件 |
569
+
570
+ > 容器顶层只有 `channelGroups` / `group` 是分组键;裸写 `groups` 不会被读取。分组直接写在 `channel.groups`,或用 `channelGroups` / `group`。
571
+
572
+ **逐字段优先级(从高到低)**:
573
+
574
+ | 目标字段 | 优先级 |
575
+ | --- | --- |
576
+ | `from` / `to` | 容器顶层 → `channel` → `line` → `lineItems` 里第一个对象项 → 草稿。前面的裸字符串 / 数字 id 跳过,不贡献端点 |
577
+ | `path`(中间拐点) | 容器顶层 → `line` → `channel` → 草稿 |
578
+ | `routing` | 容器顶层 → 草稿(直接线路 / 直接通道对象没有这一层) |
579
+ | 分组 | `channelGroups` → `group` → `channel.groups` → 不声明(新通道只建一组,组 id 用通道 id) |
580
+ | 逐条回线 | `lines` → `lineData` → `lineItems` → `line` → `channel.lineIds.map(id => ({ id }))` → 草稿 |
581
+ | 线路公共字段(直接线路对象) | 对象上的普通业务字段 → 对象自己的 `lineProps` |
582
+ | 线路公共字段(容器) | 草稿 `lineProps` → 容器顶层 `lineProps` → `line.lineProps` → 逐条 `lines[i]`;**不读 `channel.lineProps`** |
583
+ | 通道字段 | 草稿 `channelProps` → `channel` 上的业务字段 → 容器 `channelProps` |
584
+ | 名称 | 容器 `name` → `channelName` / `channel.name` → `line.name` → 草稿名称 |
585
+ | `resolution` | 容器 `resolution` → 直接通道对象固定 `newChannel` → 草稿 `preferred` |
586
+
587
+ **空数组的规则**:`lines: []`、`lineData: []`、`lineItems: []` **不会清空**草稿里已有的逐条回线,而是沿用草稿。想放弃草稿里的线路,重新 `beginAddChannel()`(不要传 `lineItems`)或改用 `addLine` / `addChannel` 自行落地。
588
+
589
+ ```js
590
+ // 容器写法:通道声明、两条回线、分组、样式、走线方式一次给全
591
+ view.commitAddChannel({
592
+ resolution: 'newChannel',
593
+ from: 'station-a',
594
+ to: 'station-b',
595
+ channel: {
596
+ id: 'container-channel-1',
597
+ name: '容器通道',
598
+ nodeWidth: 1,
599
+ width: 32,
600
+ customCode: 'CH-001' // 不在通道保留键里的字段会进 channelProps
601
+ },
602
+ lines: [
603
+ { id: 'container-line-1', color: '#dd4b2e', width: 2.2, name: '一回' },
604
+ { id: 'container-line-2', color: '#2ea36b', width: 2.2, name: '二回', voltageLevel: '10kV' }
605
+ ],
606
+ channelGroups: [
607
+ { id: 'container-channel-1::1', lineIds: ['container-line-1'], path: [[116.78, 39.88]] },
608
+ { id: 'container-channel-1::2', lineIds: ['container-line-2'], path: [[116.78, 39.96]] }
609
+ ],
610
+ routing: 'straight',
611
+ lineProps: { opacity: 0.9 },
612
+ channelProps: { tooltip: false }
613
+ })
614
+ ```
615
+
616
+ **只给种子、逐条字段另写**:`channel.lineIds` 不会被 `lines` 覆盖成通道成员表 —— 容器里的 `lines` 才是本次新建的回线;`channel.lineIds` 只在没有 `lines` 时提供 id 种子。下面的结果是两条回线、一条新通道:
617
+
618
+ ```js
619
+ view.commitAddChannel({
620
+ channel: {
621
+ id: 'seed-channel',
622
+ from: 'station-a',
623
+ to: 'station-b',
624
+ lineIds: ['seed-line-1', 'seed-line-2']
625
+ },
626
+ lineProps: { color: '#2f6fd0', width: 2 }
627
+ })
628
+ ```
629
+
630
+ **逐条回线用 `lines` / `lineData` / `lineItems`**:它们接受单对象、对象数组,也接受裸 id。每个对象里的 `id` 就是回线 id;不写 id 时按「通道 id-序号」自动编号,撞车自动加后缀:
631
+
632
+ ```js
633
+ view.commitAddChannel({
634
+ channel: { id: 'mixed-channel', from: 'station-a', to: 'station-b' },
635
+ lines: [
636
+ { id: 'mixed-1', color: '#dd4b2e', width: 2 },
637
+ 'mixed-2',
638
+ { id: 'mixed-3', lineType: 'dotted', label: '三回' }
639
+ ],
640
+ group: { id: 'mixed-group', path: [[116.8, 39.9]] }
641
+ })
642
+ ```
643
+
644
+ #### 覆盖草稿的端点、路由、名称与 id
645
+
646
+ 对象字段覆盖草稿,未写的继续用草稿。最常改的是这四组:
647
+
648
+ ```js
649
+ // 端点 / 名称 / 线路 id 种子全部由容器覆盖
650
+ view.commitAddChannel({
651
+ from: 'station-c',
652
+ to: 'station-d',
653
+ name: '改道后的联络线',
654
+ lineId: 'rerouted-line',
655
+ lines: [{ id: 'rerouted-line', color: '#7c4dff' }],
656
+ routing: 'orthogonal'
657
+ })
658
+
659
+ // 只改公共线路字段,其余(含推荐方案、circuits、端点、名称)全部沿用草稿
660
+ view.commitAddChannel({ lineProps: { width: 3.5, opacity: 0.8 } })
661
+
662
+ // 只改通道字段:通道对象的 lineIds 仍按当前草稿回线数生成
663
+ view.commitAddChannel({
664
+ channel: { id: 'styled-channel', from: 'station-a', to: 'station-b', nodeWidth: 0.8 },
665
+ channelProps: { width: 40, offsetMode: 'inset', inset: 0.2 }
666
+ })
667
+ ```
668
+
669
+ - `routing: 'orthogonal'` 只在**容器顶层**生效;直接线路 / 直接通道对象上的 `routing` 会进业务字段,不会改变走线。
670
+ - 顶层 `path` 会盖过 `line.path` / `channel.path`;没有顶层 `path` 时才依次看线路、通道和草稿。
671
+ - `lineId` 只是 id 种子:一次多条时自动展开成 `lineId-1` / `lineId-2`;每条自己的 `lines[i].id` 优先级更高。
672
+ - `from` / `to` 可以覆盖草稿端点,但必须能在当前数据里找到;找不到时返回 `{ ok: false, reason: '找不到这两个节点' }`,草稿保留。
673
+
674
+ #### 提交已有通道 / 已有分组:必须显式写 `resolution`
675
+
676
+ 直接通道对象默认新建通道。要可预期地并进已有通道 / 指定分组,换成容器写法并在顶层显式写 `resolution`;容器不写时只是沿用草稿推荐方案,不能保证一定是并组。并组时成员由 `lines` 给出,`channel` 段不再负责新建:
677
+
678
+ ```js
679
+ // 并进已有通道的指定分组
680
+ const joined = view.commitAddChannel({
681
+ resolution: { type: 'joinGroup', groupId: 'corridor-1::2' },
682
+ lines: [
683
+ { id: 'join-line-1', color: '#dd4b2e' },
684
+ { id: 'join-line-2', color: '#2ea36b' }
685
+ ],
686
+ lineProps: { width: 2.4 }
687
+ })
688
+
689
+ console.log(joined.ok) // true
690
+ console.log(joined.channelId) // 'corridor-1'
691
+ console.log(joined.groupId) // 'corridor-1::2'
692
+ console.log(joined.channelLineIds) // 并完之后这条通道的完整成员表
693
+ ```
694
+
695
+ - `resolution: 'newGroup'` 必须在已有通道的前提下才可用;新组的 id / 拐点用 `channelGroups` 或 `group` 给。
696
+ - 只写 `channelId` / `groupId` / `groupIndex` 也能作为旧简写,但它们只有在两个节点之间已经有通道时才按并组解释。
697
+ - `channelProps` 只在新建通道时写进新通道;并进已有分组时不会改已有通道的字段,需要改通道请另调 `updateChannel`。
698
+
699
+ ```js
700
+ // 旧简写:两个对象字段等价
701
+ view.commitAddChannel({ type: 'joinGroup', groupId: 'corridor-1::2' })
702
+ view.commitAddChannel({ groupId: 'corridor-1::2' })
703
+
704
+ // 只指定通道,让组件按「新分组 → 能并的分组」挑
705
+ view.commitAddChannel({ channelId: 'corridor-1' })
706
+ ```
707
+
708
+ #### 连续新增、自动提交与事件
709
+
710
+ 第二参数只在一次提交里生效:
711
+
712
+ | 字段 | 类型 | 缺省 | 说明 |
713
+ | --- | --- | --- | --- |
714
+ | `auto` | `boolean` | `false` | 只标记本次事件是自动提交;不等于 `beginAddChannel({ autoCommit: true })`。`autoCommit` 由组件内部用 `commitAddChannel('auto', { auto: true })` 提交 |
715
+ | `continuous` | `boolean` | 跟草稿的 `continuous` | 显式 `false` 强制本次落地后结束草稿;`true` 只在草稿本来就是连续模式时有效 |
716
+ | `originalEvent` | `Event \| null` | `null` | 连续模式重开草稿时带回去,供下一次草稿回调使用 |
717
+
718
+ ```js
719
+ // 连续新增:本次落地后直接开下一段,并以上一段终点为下一段起点
720
+ view.beginAddChannel({ continuous: true, chain: true, kind: 'channel' })
721
+
722
+ view.updateOptions({
723
+ onChannelDraft: payload => {
724
+ if (payload.phase === 'ready' && !payload.blocked) {
725
+ view.commitAddChannel(undefined, { continuous: true, originalEvent: payload.originalEvent })
726
+ }
727
+ },
728
+ onChannelDraftClick: payload => {
729
+ if (payload.phase === 'commit') {
730
+ console.log('已落地', payload.lineIds, '下一段是否重开', payload.restarted)
731
+ }
732
+ }
733
+ })
734
+
735
+ // 本次之后收手;即使草稿配了 continuous: true,也不会再开下一段
736
+ view.commitAddChannel(undefined, { continuous: false })
737
+ ```
738
+
739
+ 提交成功时发一条 `onChannelDraft` / `channelDraft`(`phase: 'commit'`);失败时发 `phase: 'blocked'`,并且:非连续模式下草稿同样会结束,连续模式下会照常重开一段让用户继续试。事件载荷常用字段如下:
740
+
741
+ | 字段 | 含义 |
742
+ | --- | --- |
743
+ | `phase` | `'commit'` / `'blocked'` |
744
+ | `auto` / `autoCommit` | 本次是否自动提交 / 草稿是否开了自动提交 |
745
+ | `continuous` / `chain` / `restarted` | 提交后是否重开、是否继承上一段终点、是否真的重开了 |
746
+ | `kind` / `circuits` | 本次加通道还是独立线路、落地几条 |
747
+ | `from` / `to` / `plan` / `options` | 实际端点、方案表与全部方案 |
748
+ | `blocked` / `reason` | 是否失败与原因 |
749
+ | `resolution` | 实际选中的方案 |
750
+ | `line` / `lineId` / `lines` / `lineIds` | 新回线(`line` / `lineId` 是第一条) |
751
+ | `channelId` / `groupId` / `channelLineIds` | 新通道 / 分组与通道完整成员表(独立线路为 `null`) |
752
+ | `lineIndex` | 第一条新回线在 `lines` 里的下标;失败事件里按 `-1` 兜底 |
753
+ | `routing` / `path` | 本次落地用的走线方式与中间拐点 |
754
+
755
+ #### 返回值与错误处理
756
+
757
+ `GisViewChannelAddResult` 与 `addChannel` / `addLine` 共用:
758
+
759
+ | 字段 | 成功时 | 失败时 |
760
+ | --- | --- | --- |
761
+ | `ok` | `true` | `false` |
762
+ | `reason` | 无 | 说明原因(未 ready、找不到节点、方案不可用、端点相同…) |
763
+ | `plan` | 本次方案表 | 能算出方案表时一并带回,供界面重新选择 |
764
+ | `resolution` | 实际方案;独立线路是 `newLine` | 无 |
765
+ | `kind` / `circuits` | `'channel' / 'line'`、回数 | 无 |
766
+ | `line` / `lineId` / `lines` / `lineIds` | 新回线与 id(多回时 `line` 是第一条) | 无 |
767
+ | `lineIndex` | 第一条新回线的下标 | 无 |
768
+ | `channelId` / `groupId` | 新通道 / 分组;独立线路分别为 `null` / `null` | 无 |
769
+ | `channelLineIds` | 通道落地后的完整成员表;独立线路为 `null` | 无 |
770
+ | `channelIndex` | 新通道或并进通道的下标;独立线路为 `-1` | 通常不返回;事件载荷失败时按 `-1` 兜底 |
771
+
772
+ ```js
773
+ const result = view.commitAddChannel({ groupId: 'not-exist' })
774
+ if (!result.ok) {
775
+ console.warn(result.reason) // 例如:找不到通道 / 指定分组没有可用方案
776
+ console.table(result.plan?.options ?? []) // 每条方案为什么不可用
777
+ // 失败时草稿是否结束取决于 continuous:非连续模式收起,连续模式重开下一段
778
+ }
779
+ ```
780
+
781
+ 常见失败原因:
782
+
783
+ - **未 ready**:先完成两次点选,或 `beginAddChannel({ from })` 后补点终点。
784
+ - **功能关闭**:`features: { channelDraft: false }` 时提交直接拒绝。
785
+ - **节点找不到 / 端点相同**:`from` / `to` 覆盖成不存在的 id,或两端指到同一个节点。
786
+ - **方案不可用**:已有通道组数已满、目标分组余量不足,或者 `resolution` 指向不存在的通道 / 分组。
787
+
368
788
  **给已有通道加线路 / 删除线路**
369
789
 
370
790
  | 想做什么 | 方法 | 要点 |