gis-view-webgl2 0.1.2 → 0.1.3

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 (61) hide show
  1. package/README.md +12 -8
  2. package/dist/gis-view.css +1 -1
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +309 -59
  5. package/dist/index.js +1 -1
  6. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +117 -0
  7. package/docs/GisView.md +2 -2
  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 +107 -0
  9. package/docs/api/index.html +1 -1
  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/GisViewDemo.vue +64 -8809
  16. package/docs/demos/api-index.js +48 -41
  17. package/docs/demos/demos.js +33 -16
  18. package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +28 -0
  19. package/docs/demos/gis-view-demo/GisViewDemoDialogs.vue +82 -0
  20. package/docs/demos/gis-view-demo/GisViewDemoFooter.vue +16 -0
  21. package/docs/demos/gis-view-demo/GisViewDemoHeading.vue +16 -0
  22. package/docs/demos/gis-view-demo/GisViewDemoPanelStack.vue +71 -0
  23. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +219 -0
  24. package/docs/demos/gis-view-demo/demo.css +1230 -0
  25. package/docs/demos/gis-view-demo/demoContext.js +12 -0
  26. package/docs/demos/gis-view-demo/panels/DataPanels.vue +303 -0
  27. package/docs/demos/gis-view-demo/panels/OpsPanels.vue +174 -0
  28. package/docs/demos/gis-view-demo/panels/StylePanels.vue +561 -0
  29. package/docs/demos/gis-view-demo/panels/ViewPanels.vue +396 -0
  30. package/docs/demos/gis-view-demo/useGisViewDemo.js +6188 -0
  31. 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
  32. 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
  33. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +3 -2
  34. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +87 -14
  35. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +8 -8
  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 +40 -3
  37. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +26 -0
  38. 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
  39. 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
  40. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +13 -4
  41. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +3 -0
  42. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +35 -19
  43. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +5 -0
  44. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +12 -6
  45. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +39 -22
  46. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +52 -13
  47. 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
  48. 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 +5 -5
  49. package/docs/steps/README.md +17 -11
  50. package/docs/steps/selftest-actions.html +3 -5
  51. package/docs/steps/selftest-demo.html +27 -18
  52. package/docs/steps/selftest-demos.html +387 -27
  53. package/docs/steps/selftest-docs.html +4 -4
  54. package/docs/steps/selftest-effects.html +200 -0
  55. package/docs/steps/selftest-features.html +512 -11
  56. package/docs/steps/selftest-labels.html +74 -0
  57. package/docs/steps/selftest-perf.html +293 -20
  58. package/docs/steps/selftest-scale.html +52 -29
  59. package/docs/steps/step-10-full.js +1 -1
  60. package/docs/steps/step-12-events.js +1 -1
  61. package/package.json +1 -1
@@ -133,6 +133,11 @@
133
133
  Math.abs(lineEffectDistance(normalizeLineEffect({ period: 4, offset: 0.5 }, LINE_EFFECT_DEFAULTS), 200, 0, 0) - 100) < 1e-9 &&
134
134
  Math.abs(lineEffectDistance(normalizeLineEffect({ period: 4, reverse: true }, LINE_EFFECT_DEFAULTS), 200, 1, 0) - 150) < 1e-9 &&
135
135
  Math.abs(lineEffectDistance(normalizeLineEffect({ period: 4, delay: 1 }, LINE_EFFECT_DEFAULTS), 200, 1, 0) - 0) < 1e-9)
136
+ const xorEffect = normalizeLineEffect({ period: 4 }, LINE_EFFECT_DEFAULTS)
137
+ const xorAt = (forward, effectReverse) => lineEffectDistance(xorEffect, 200, 1, 0, (forward === false) !== effectReverse)
138
+ check('线路 forward 与 effect.reverse 异或:正正 / 反反同向,正反 / 反正反向',
139
+ [50, 150, 150, 50].every((expected, index) => Math.abs(xorAt(index < 2, index % 2 === 1) - expected) < 1e-9),
140
+ [true, false].flatMap(forward => [false, true].map(reverse => xorAt(forward, reverse))).join(','))
136
141
  const spaced = normalizeLineEffect({ period: 4, count: 4, spaced: true }, LINE_EFFECT_DEFAULTS)
137
142
  const gaps = [0, 1, 2, 3].map(slot => lineEffectDistance(spaced, 200, 0, slot)).sort((a, b) => a - b)
138
143
  check('count > 1 时沿弧长等分(spaced: true)',
@@ -196,6 +201,11 @@
196
201
  await sleep(Math.round(ms / step))
197
202
  }
198
203
  }
204
+ /** 画布在视口里才会连续跑动画:页面下面临时挂的样例先滚到视口中间,等观察器恢复渲染 */
205
+ const showHost = async (element) => {
206
+ element.scrollIntoView({ block: 'center', inline: 'nearest' })
207
+ await sleep(120)
208
+ }
199
209
 
200
210
  /* ---------------- 引擎:全局配置 + 逐要素覆盖 ---------------- */
201
211
  // 一条直角折线的线路(红点沿它跑)+ 三个节点:a 自己配了涟漪(半径 16),b / c 跟着全局那一份
@@ -298,6 +308,83 @@
298
308
  JSON.stringify({ line: stats.lineEffectCount, point: stats.pointEffectCount }))
299
309
  check('getOptions 与 updateOptions({ effects }) 一致(配置记进了 current)', view.getOptions().effects?.line?.period === 1, JSON.stringify(view.getOptions().effects?.line?.period))
300
310
 
311
+ /* ---------------- 引擎:线路 forward 反向装饰 / 箭头 / 动画 ---------------- */
312
+ const forwardHost = document.createElement('div')
313
+ forwardHost.style.cssText = 'width:480px;height:240px'
314
+ document.body.append(forwardHost)
315
+ const forwardView = createGisView(forwardHost, {
316
+ points: [],
317
+ lines: [{
318
+ id: 'forward-line',
319
+ path: [[0, 0], [160, 0]],
320
+ decorations: [{ position: 0.25, symbol: 'square', size: 12, color: '#00ff00', parallel: true }],
321
+ direction: { positions: [0.75], symbol: 'square', size: 12, color: '#ff0000' },
322
+ effect: { speed: 0.001, count: 1, offset: 0.25, parallel: true, size: 10, color: '#0000ff' }
323
+ }],
324
+ lineStyle: { color: '#111111', width: 1 },
325
+ effects: { line: { period: 1 }, point: false },
326
+ showGrid: false,
327
+ background: '#ffffff'
328
+ })
329
+ forwardView.fitView()
330
+ await showHost(forwardHost)
331
+ await advance(60, 2)
332
+ const forwardDecorations = () => {
333
+ const scene = forwardView.snapshotScene({ background: false })
334
+ return {
335
+ deco: scene.decorations.find(item => item.fill === 'rgba(0,255,0,1)'),
336
+ arrow: scene.decorations.find(item => item.fill === 'rgba(255,0,0,1)')
337
+ }
338
+ }
339
+ const forwardEffectSample = () => forwardView.getLineEffects().find(item => item.lineIndex === 0)
340
+ const forwardStart = forwardDecorations()
341
+ const forwardStartEffect = forwardEffectSample()
342
+ check('forward 默认 true:装饰在 25%、方向箭头在 75%,两者都顺着 from -> to',
343
+ Boolean(forwardStart.deco && forwardStart.arrow) && forwardStart.deco.x < forwardStart.arrow.x &&
344
+ Math.abs(forwardStart.deco.angle) < 0.01 && Math.abs(forwardStart.arrow.angle) < 0.01,
345
+ JSON.stringify({ deco: forwardStart.deco && [forwardStart.deco.x, forwardStart.deco.angle], arrow: forwardStart.arrow && [forwardStart.arrow.x, forwardStart.arrow.angle] }))
346
+ check('forward 默认 true:动点从 path[0] 起算(offset 25% 落在 40)',
347
+ Math.abs(forwardStartEffect?.x - 40) < 2 && Math.abs(forwardStartEffect?.angle) < 0.01,
348
+ JSON.stringify(forwardStartEffect))
349
+
350
+ forwardView.updateLine('forward-line', { forward: false })
351
+ await advance(100, 3)
352
+ const forwardReversed = forwardDecorations()
353
+ const forwardReversedEffect = forwardEffectSample()
354
+ check('updateLine({ forward:false }) 立刻把装饰与方向箭头镜像到另一端',
355
+ Boolean(forwardReversed.deco && forwardReversed.arrow) &&
356
+ Math.abs(forwardReversed.deco.x - forwardStart.arrow.x) < 0.6 &&
357
+ Math.abs(forwardReversed.arrow.x - forwardStart.deco.x) < 0.6 &&
358
+ Math.abs(Math.abs(forwardReversed.deco.angle) - Math.PI) < 0.02 &&
359
+ Math.abs(Math.abs(forwardReversed.arrow.angle) - Math.PI) < 0.02,
360
+ JSON.stringify({ deco: forwardReversed.deco && [forwardReversed.deco.x, forwardReversed.deco.angle], arrow: forwardReversed.arrow && [forwardReversed.arrow.x, forwardReversed.arrow.angle] }))
361
+ check('forward:false 立刻把动点翻到另一端、朝向也翻半圈',
362
+ Math.abs(forwardReversedEffect?.x - 120) < 2 && Math.abs(Math.abs(forwardReversedEffect?.angle) - Math.PI) < 0.02,
363
+ JSON.stringify(forwardReversedEffect))
364
+
365
+ forwardView.updateLine('forward-line', { direction: { positions: [0.75], symbol: 'square', size: 12, color: '#ff0000', reverse: true } })
366
+ await advance(60, 2)
367
+ const xorDirection = forwardDecorations()
368
+ check('forward:false 与 direction.reverse:true 异或:箭头回到 25% 并指回正向',
369
+ Boolean(xorDirection.deco && xorDirection.arrow) && xorDirection.arrow.x < xorDirection.deco.x &&
370
+ Math.abs(xorDirection.arrow.angle) < 0.01,
371
+ JSON.stringify({ deco: xorDirection.deco && [xorDirection.deco.x, xorDirection.deco.angle], arrow: xorDirection.arrow && [xorDirection.arrow.x, xorDirection.arrow.angle] }))
372
+
373
+ forwardView.updateLine('forward-line', { effect: { speed: 0.001, count: 1, offset: 0.25, parallel: true, size: 10, color: '#0000ff', reverse: true } })
374
+ await advance(60, 2)
375
+ const xorEffectSample = forwardEffectSample()
376
+ check('forward:false 与 effect.reverse:true 异或:动点回到 40、朝向翻回正向',
377
+ Math.abs(xorEffectSample?.x - 40) < 2 && Math.abs(xorEffectSample?.angle) < 0.01,
378
+ JSON.stringify(xorEffectSample))
379
+ forwardView.updateLine('forward-line', { forward: true })
380
+ await advance(60, 2)
381
+ const singleEffectReverse = forwardEffectSample()
382
+ check('只留 effect.reverse:true 时动点反向(forward 恢复 true 后落在 120)',
383
+ Math.abs(singleEffectReverse?.x - 120) < 2 && Math.abs(Math.abs(singleEffectReverse?.angle) - Math.PI) < 0.02,
384
+ JSON.stringify(singleEffectReverse))
385
+ forwardView.destroy()
386
+ forwardHost.remove()
387
+
301
388
  /* ---------------- 引擎:全局什么都不配,只在数据里逐条配 ---------------- */
302
389
  // 需求里的第二种写法:不动全局缺省,写在哪条上哪条就动(全局没配不代表关掉)
303
390
  const soloHost = document.createElement('div')
@@ -309,6 +396,7 @@
309
396
  showGrid: false
310
397
  })
311
398
  solo.fitView()
399
+ await showHost(soloHost)
312
400
  await advance(60, 3)
313
401
  const soloStats = solo.getStats()
314
402
  check('全局不配、只在数据里逐条配:那条线动、那个节点冒涟漪,另一条不动',
@@ -325,8 +413,58 @@
325
413
  solo.destroy()
326
414
  soloHost.remove()
327
415
 
416
+ /* ---------------- 引擎:动点缓存只跟动画线路走,路径就地换过总长要跟着换 ---------------- */
417
+ // 5000 条普通线 + 2 条动点线:前者不该进入每帧的动点循环;后者在 updatePoint / updateLine
418
+ // 就地换路径后必须立刻按新弧长采样(拖动端点与拐点走的也是这条路)。
419
+ const stressHost = document.createElement('div')
420
+ stressHost.style.cssText = 'width:320px;height:180px'
421
+ document.body.append(stressHost)
422
+ const stressLines = new Array(5000)
423
+ for (let index = 0; index < stressLines.length; index += 1) {
424
+ const x = index % 100
425
+ const y = Math.floor(index / 100)
426
+ stressLines[index] = { path: [[x, y], [x + 1, y]] }
427
+ }
428
+ stressLines.push({ id: 'fx-bound', from: 'sp0', to: 'sp1', effect: { period: 1, count: 1 } })
429
+ stressLines.push({ id: 'fx-path', path: [[0, 10], [80, 10], [80, 60]], effect: { period: 1, count: 1 } })
430
+ const stress = createGisView(stressHost, {
431
+ points: [{ id: 'sp0', x: 0, y: 0 }, { id: 'sp1', x: 100, y: 0 }],
432
+ lines: stressLines,
433
+ showGrid: false,
434
+ width: 320,
435
+ height: 180
436
+ })
437
+ stress.fitView()
438
+ await advance(80, 3)
439
+ const stressStats = stress.getStats()
440
+ const totalsOf = () => new Map(stress.getLineEffects().map(item => [item.lineIndex, item.total]))
441
+ const initialTotals = totalsOf()
442
+ check('动点清单只包含配了 effect 的线路(5000 条普通线不进每帧循环)',
443
+ stressStats.lineEffectCount === 2 && stress.getData().lines.length === 5002,
444
+ JSON.stringify({ effects: stressStats.lineEffectCount, lines: stress.getData().lines.length }))
445
+ check('动点总长按初始折线算(100 / 130)',
446
+ initialTotals.get(5000) === 100 && initialTotals.get(5001) === 130,
447
+ `${initialTotals.get(5000)} / ${initialTotals.get(5001)}`)
448
+ stress.updatePoint('sp1', { x: 0, y: 50 })
449
+ await advance(60, 2)
450
+ const endpointTotals = totalsOf()
451
+ check('端点就地移动后动点总长跟着新路径重算(100 → 50)',
452
+ endpointTotals.get(5000) === 50 && endpointTotals.get(5001) === 130,
453
+ `${endpointTotals.get(5000)} / ${endpointTotals.get(5001)}`)
454
+ stress.updateLine('fx-path', { path: [[0, 10], [100, 10]] })
455
+ await advance(60, 2)
456
+ const pathTotals = totalsOf()
457
+ const pathGlyph = stress.getLineEffects().find(item => item.lineIndex === 5001)
458
+ check('updateLine 换整条路径后动点按新总长采样(130 → 100,位置仍在新线上)',
459
+ pathTotals.get(5001) === 100 && Math.abs(pathGlyph?.y - 10) < 1e-6 && pathGlyph?.x >= -1e-6 && pathGlyph?.x <= 100 + 1e-6,
460
+ JSON.stringify({ total: pathTotals.get(5001), x: pathGlyph?.x, y: pathGlyph?.y }))
461
+ stress.destroy()
462
+ stressHost.remove()
463
+
328
464
  /* ---------------- 引擎:读像素证明动点真的画出来了 ---------------- */
329
465
  // 只留线路动点(红色):画面上红色的像素就只可能是它
466
+ // 前面滚动到过页面底部的临时画布;像素断言要在主画布自己的视口里读,先滚回来
467
+ await showHost(host)
330
468
  view.updateOptions({ effects: { line: { period: 1.5, count: 1, size: 14, color: '#ff0000' }, point: false } })
331
469
  const canvas = host.querySelector('canvas')
332
470
  const gl = canvas.getContext('webgl2')
@@ -360,7 +498,69 @@
360
498
  `${first.x.toFixed(1)},${first.y.toFixed(1)} -> ${second.x.toFixed(1)},${second.y.toFixed(1)}`)
361
499
  diag('像素读数', `画布 ${width}x${height},红点 ${first.count} 个像素 -> ${second.count} 个像素,质心 ${first.x.toFixed(1)},${first.y.toFixed(1)} -> ${second.x.toFixed(1)},${second.y.toFixed(1)}`)
362
500
 
501
+ /* ---------------- 引擎:动点拖尾是可见的渐隐带(像素级) ---------------- */
502
+ // 单独一条横线,只留红色动点:沿 x 轴数红色像素的跨度,单个图标约一个尺寸宽;
503
+ // 身后有 28 CSS 像素的拖尾时,可见尾段会明显超过图标本身(旧实现只有圆点)。
504
+ const trailHost = document.createElement('div')
505
+ trailHost.style.cssText = 'width:480px;height:200px'
506
+ document.body.append(trailHost)
507
+ const trailView = createGisView(trailHost, {
508
+ points: [],
509
+ lines: [{ id: 'trail-line', path: [[0, 0], [160, 0]], effect: true }],
510
+ lineStyle: { color: '#111111', width: 1 },
511
+ effects: { line: { period: 4, size: 10, color: '#ff0000', parallel: false }, point: false },
512
+ showGrid: false,
513
+ background: '#ffffff'
514
+ })
515
+ trailView.fitView()
516
+ await showHost(trailHost)
517
+ await advance(80, 3)
518
+ const trailCanvas = trailHost.querySelector('canvas')
519
+ const trailGl = trailCanvas.getContext('webgl2')
520
+ const trailWidth = trailGl.drawingBufferWidth
521
+ const trailHeight = trailGl.drawingBufferHeight
522
+ const trailPixels = new Uint8Array(trailWidth * trailHeight * 4)
523
+ const trailSpan = () => {
524
+ pump()
525
+ trailGl.readPixels(0, 0, trailWidth, trailHeight, trailGl.RGBA, trailGl.UNSIGNED_BYTE, trailPixels)
526
+ let minX = Number.POSITIVE_INFINITY
527
+ let maxX = Number.NEGATIVE_INFINITY
528
+ let count = 0
529
+ for (let y = 0; y < trailHeight; y += 1) {
530
+ for (let x = 0; x < trailWidth; x += 1) {
531
+ const offset = (y * trailWidth + x) * 4
532
+ const red = trailPixels[offset]
533
+ const green = trailPixels[offset + 1]
534
+ const blue = trailPixels[offset + 2]
535
+ // 白底上的红色拖尾从实心过渡到很淡的粉红,门槛压低一点才数得到尾段
536
+ if (red > 210 && red > green + 12 && red > blue + 12) {
537
+ minX = Math.min(minX, x)
538
+ maxX = Math.max(maxX, x)
539
+ count += 1
540
+ }
541
+ }
542
+ }
543
+ return count ? { span: maxX - minX + 1, count } : { span: 0, count: 0 }
544
+ }
545
+ let trailBest = { span: 0, count: 0 }
546
+ for (let index = 0; index < 10; index += 1) {
547
+ const next = trailSpan()
548
+ if (next.span > trailBest.span) {
549
+ trailBest = next
550
+ }
551
+ await sleep(80)
552
+ }
553
+ const trailRatio = trailView.getViewport().pixelRatio
554
+ // 最后一段已经淡到接近纸白,按可见的尾段量度:> 18 CSS 像素仍明显超过 10 px 的图标,
555
+ // 又不会把几乎看不见的尾尖也算进门槛,避免不同 GPU / 合成精度下偶发地卡在边界上。
556
+ check('线路动点:红点身后铺出渐隐拖尾(像素跨度明显超过单个图标)',
557
+ trailBest.count > 80 && trailBest.span > 18 * trailRatio,
558
+ `跨度 ${trailBest.span}px / ${trailBest.count} 个像素 / 期望 > ${(18 * trailRatio).toFixed(0)}px`)
559
+ trailView.destroy()
560
+ trailHost.remove()
561
+
363
562
  /* ---------------- 引擎:多动点 / 多圈与帧泵开销 ---------------- */
563
+ await showHost(host)
364
564
  view.updateOptions({ effects: { line: { period: 1, count: 8 }, point: { radius: 18, count: 2 } } })
365
565
  await advance(80, 3)
366
566
  const heavy = view.getStats()