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
@@ -192,8 +192,8 @@
192
192
  await settleFrames()
193
193
  const fillAuto = minimapFill()
194
194
  check('鹰眼图 extent: auto 把远离数据的 GeoJSON 一起框进来', fillAuto > 100 && fillAuto > fillData + 100, `data=${fillData} auto=${fillAuto}`)
195
- // 反过来:底图小到在合并范围里只剩一两个像素、而相机正好落在里面时,auto 以它为准 ——
196
- // 正在看的主体不该因为另一套坐标离得远就被压成一个点(数据还没映射到底图时常是这种情况)
195
+ // 反过来:底图小到在合并范围里只剩一两个像素、而相机正好落在里面时,默认仍按并集铺;
196
+ // 显式打开 extentFollowCamera 后,auto 才以正在看的那一份为准。
197
197
  const tinyHost = document.createElement('div')
198
198
  tinyHost.style.cssText = 'position:fixed;left:-9999px;top:0;width:420px;height:320px'
199
199
  document.body.appendChild(tinyHost)
@@ -210,13 +210,62 @@
210
210
  minimap: { width: 200, height: 160, extent: 'auto' }
211
211
  })
212
212
  await settleFrames()
213
- const fillSquashed = minimapFill(tinyHost)
214
- tinyView.zoomToBounds({ minX: 100, minY: 20, maxX: 101, maxY: 21 })
215
- await settleFrames()
216
- const fillFocused = minimapFill(tinyHost)
217
- check('鹰眼图 extent: auto 在相机落进小范围时以它为准', fillFocused > 200 && fillFocused > fillSquashed + 200, `squashed=${fillSquashed} focused=${fillFocused}`)
218
- // 这一轮:沿用主图规则取描边色(不再是白点)、小图尺寸跟随主视图地图倍数、框与框选矩形的样式
219
- const ruleHost = document.createElement('div')
213
+ const fillSquashed = minimapFill(tinyHost)
214
+ tinyView.zoomToBounds({ minX: 100, minY: 20, maxX: 101, maxY: 21 })
215
+ await settleFrames()
216
+ const fillDefault = minimapFill(tinyHost)
217
+ check('鹰眼图 extent: auto 默认不随相机收窄(外围内容消失后包围盒保持并集)',
218
+ (tinyView.getOptions().minimap.extentFollowCamera ?? false) === false && fillDefault > 0 && Math.abs(fillDefault - fillSquashed) < 40,
219
+ `squashed=${fillSquashed} default=${fillDefault}`)
220
+ tinyView.updateOptions({ minimap: { ...tinyView.getOptions().minimap, extentFollowCamera: true } })
221
+ await settleFrames()
222
+ const fillFocused = minimapFill(tinyHost)
223
+ check('鹰眼图 extent: 打开 extentFollowCamera 后,相机落进小范围时 auto 以它为准',
224
+ tinyView.getOptions().minimap.extentFollowCamera === true && fillFocused > 200 && fillFocused > fillSquashed + 200,
225
+ `squashed=${fillSquashed} focused=${fillFocused}`)
226
+ // extentFollowContent 默认关闭:筛选掉外围节点后,鹰眼图仍用首次铺好的范围;显式打开后才跟着当前可见内容缩。
227
+ const lockHost = document.createElement('div')
228
+ lockHost.style.cssText = 'position:fixed;left:-9999px;top:0;width:420px;height:320px'
229
+ document.body.appendChild(lockHost)
230
+ const lockExtents = []
231
+ const lockView = createGisView(lockHost, {
232
+ points: [
233
+ { id: 'near-a', x: 0, y: 0, zone: 'near' },
234
+ { id: 'near-b', x: 10, y: 0, zone: 'near' },
235
+ { id: 'far', x: 100, y: 40, zone: 'far' }
236
+ ],
237
+ lines: [],
238
+ background: false,
239
+ minimap: {
240
+ width: 200,
241
+ height: 160,
242
+ background: false,
243
+ extent: 'auto',
244
+ extentFollowContent: false,
245
+ draw: info => lockExtents.push({ ...info.extent })
246
+ }
247
+ })
248
+ await settleFrames()
249
+ const lockBefore = lockExtents.at(-1)
250
+ lockView.setFilter({ points: { zone: 'near' }, worker: false })
251
+ await settleFrames()
252
+ const lockAfter = lockExtents.at(-1)
253
+ const sameExtent = lockBefore && lockAfter &&
254
+ Math.abs(lockBefore.minX - lockAfter.minX) < 1e-6 && Math.abs(lockBefore.minY - lockAfter.minY) < 1e-6 &&
255
+ Math.abs(lockBefore.maxX - lockAfter.maxX) < 1e-6 && Math.abs(lockBefore.maxY - lockAfter.maxY) < 1e-6
256
+ check('鹰眼图 extentFollowContent 默认关闭:外围节点被筛掉后包围盒保持不变',
257
+ lockView.getOptions().minimap.extentFollowContent === false && lockBefore?.maxX === 100 && lockBefore?.maxY === 40 && sameExtent,
258
+ `before=${JSON.stringify(lockBefore)} after=${JSON.stringify(lockAfter)}`)
259
+ lockView.updateOptions({ minimap: { ...lockView.getOptions().minimap, extentFollowContent: true } })
260
+ await settleFrames()
261
+ const lockFollowed = lockExtents.at(-1)
262
+ check('鹰眼图 extentFollowContent 打开后:包围盒按当前可见内容重算',
263
+ lockView.getOptions().minimap.extentFollowContent === true && lockFollowed?.maxX === 10 && lockFollowed?.maxY === 0,
264
+ JSON.stringify(lockFollowed))
265
+ lockView.destroy()
266
+ lockHost.remove()
267
+ // 这一轮:沿用主图规则取描边色(不再是白点)、继承尺寸不受主视图地图倍数影响、框与框选矩形的样式
268
+ const ruleHost = document.createElement('div')
220
269
  ruleHost.style.cssText = 'position:fixed;left:-9999px;top:0;width:420px;height:320px'
221
270
  document.body.appendChild(ruleHost)
222
271
  const ruleView = createGisView(ruleHost, {
@@ -254,7 +303,11 @@
254
303
  ruleView.zoomTo(ruleView.getCamera().scale * 3, [210, 160])
255
304
  await settleFrames()
256
305
  const redZoomed = minimapRed()
257
- check('鹰眼图节点跟随主视图地图倍数放大', redZoomed > redBefore * 2, `${redBefore} → ${redZoomed}`)
306
+ check(
307
+ '鹰眼图沿用主图规则时主图放大不改变节点尺寸(pointMapZoomScale 也不参与继承)',
308
+ redBefore > 0 && Math.abs(redZoomed - redBefore) <= 2,
309
+ `${redBefore} → ${redZoomed}`
310
+ )
258
311
  const boxElement = ruleHost.querySelector('.gis-view__minimap-box')
259
312
  const boxComputed = getComputedStyle(boxElement)
260
313
  check(
@@ -319,9 +372,113 @@
319
372
  bgRedBefore > 0 && Math.abs(bgRedAfter - bgRedBefore) <= Math.max(bgRedBefore * 0.2, 2),
320
373
  `主图放大 3 倍前后小图红点像素 ${bgRedBefore} → ${bgRedAfter}`
321
374
  )
322
- bgView.destroy()
323
- bgHost.remove()
324
- // 这一轮:小图自身倍数的联动指数(pointZoomScale)真的生效 —— 放大鹰眼图 6 倍,节点半径按 6^指数 长
375
+ bgView.destroy()
376
+ bgHost.remove()
377
+ // 这一轮:沿用主图规则时线路线宽也只跟视口比例走 —— 主图放大不变,小图自身放大才按 lineZoomScale 变
378
+ const lineHost = document.createElement('div')
379
+ lineHost.style.cssText = 'position:fixed;left:-9999px;top:0;width:420px;height:320px'
380
+ document.body.appendChild(lineHost)
381
+ const lineView = createGisView(lineHost, {
382
+ points: [{ id: 'a', x: 0, y: 50 }, { id: 'b', x: 100, y: 50 }],
383
+ lines: [{ id: 'l', from: 'a', to: 'b' }],
384
+ background: false,
385
+ pointStyle: { size: 1, color: '#00aa00' },
386
+ lineStyle: { width: 8, color: '#ff3200', opacity: 1 },
387
+ minimap: {
388
+ width: 200,
389
+ height: 160,
390
+ background: false,
391
+ lineStyle: { color: '#ff3200', width: 1, opacity: 1 },
392
+ inheritRules: true,
393
+ pointMapZoomScale: 1,
394
+ lineMapZoomScale: 1,
395
+ lineZoomScale: 1,
396
+ maxZoom: 8
397
+ }
398
+ })
399
+ await settleFrames()
400
+ const lineBefore = minimapRedIn(lineHost)
401
+ lineView.zoomTo(lineView.getCamera().scale * 3, [210, 160])
402
+ await settleFrames()
403
+ const lineAfterMainZoom = minimapRedIn(lineHost)
404
+ check(
405
+ '鹰眼图沿用主图线宽时主图放大不改变线路宽度(lineMapZoomScale 也不参与继承)',
406
+ lineBefore > 20 && Math.abs(lineAfterMainZoom - lineBefore) <= 2,
407
+ `${lineBefore} → ${lineAfterMainZoom}`
408
+ )
409
+ lineView.updateOptions({ minimap: { ...lineView.getOptions().minimap, lineWidthScale: 2 } })
410
+ await settleFrames()
411
+ const lineWidthScaled = minimapRedIn(lineHost)
412
+ check(
413
+ '鹰眼图线路整体倍数 lineWidthScale 对继承线路生效',
414
+ lineWidthScaled > lineAfterMainZoom * 1.5,
415
+ `${lineAfterMainZoom} → ${lineWidthScaled}`
416
+ )
417
+ lineView.updateOptions({ minimap: { ...lineView.getOptions().minimap, lineWidthScale: 1 } })
418
+ await settleFrames()
419
+ lineView.setMinimapZoom(2)
420
+ await settleFrames()
421
+ const lineAfterMiniZoom = minimapRedIn(lineHost)
422
+ check(
423
+ '鹰眼图自身放大时沿用主图的线路按 lineZoomScale 变粗',
424
+ lineAfterMiniZoom > lineAfterMainZoom * 1.6,
425
+ `${lineAfterMainZoom} → ${lineAfterMiniZoom}`
426
+ )
427
+ lineView.destroy()
428
+ lineHost.remove()
429
+ // 沿用主图的节点:主图放大不变,小图自身放大时按 pointZoomScale 变大(pointMapZoomScale 不参与继承)
430
+ const inheritedPointHost = document.createElement('div')
431
+ inheritedPointHost.style.cssText = 'position:fixed;left:-9999px;top:0;width:420px;height:320px'
432
+ document.body.appendChild(inheritedPointHost)
433
+ const inheritedPointView = createGisView(inheritedPointHost, {
434
+ points: [{ id: 'a', x: 45, y: 50 }, { id: 'b', x: 55, y: 50 }],
435
+ lines: [{ id: 'l', from: 'a', to: 'b' }],
436
+ background: false,
437
+ pointStyle: { size: 10, color: '#ff3200' },
438
+ lineStyle: { width: 1.5, color: '#00aa00' },
439
+ minimap: {
440
+ width: 200,
441
+ height: 160,
442
+ background: false,
443
+ extent: { minX: 0, minY: 0, maxX: 100, maxY: 100 },
444
+ inheritRules: true,
445
+ pointZoomScale: 1,
446
+ pointMapZoomScale: 1,
447
+ zoomable: true,
448
+ maxZoom: 8
449
+ }
450
+ })
451
+ await settleFrames()
452
+ const inheritedPointBefore = minimapRedIn(inheritedPointHost)
453
+ inheritedPointView.zoomTo(inheritedPointView.getCamera().scale * 3, [210, 160])
454
+ await settleFrames()
455
+ const inheritedPointAfterMainZoom = minimapRedIn(inheritedPointHost)
456
+ check(
457
+ '鹰眼图沿用主图节点时主图放大不改变节点大小(pointMapZoomScale 也不参与继承)',
458
+ inheritedPointBefore > 10 && Math.abs(inheritedPointAfterMainZoom - inheritedPointBefore) <= 2,
459
+ `${inheritedPointBefore} → ${inheritedPointAfterMainZoom}`
460
+ )
461
+ inheritedPointView.updateOptions({ minimap: { ...inheritedPointView.getOptions().minimap, pointSizeScale: 2 } })
462
+ await settleFrames()
463
+ const inheritedPointSizeScaled = minimapRedIn(inheritedPointHost)
464
+ check(
465
+ '鹰眼图节点整体倍数 pointSizeScale 对继承节点生效',
466
+ inheritedPointSizeScaled > inheritedPointAfterMainZoom * 2,
467
+ `${inheritedPointAfterMainZoom} → ${inheritedPointSizeScaled}`
468
+ )
469
+ inheritedPointView.updateOptions({ minimap: { ...inheritedPointView.getOptions().minimap, pointSizeScale: 1 } })
470
+ await settleFrames()
471
+ inheritedPointView.setMinimapZoom(2)
472
+ await settleFrames()
473
+ const inheritedPointAfterMiniZoom = minimapRedIn(inheritedPointHost)
474
+ check(
475
+ '鹰眼图自身放大时沿用主图的节点按 pointZoomScale 变大',
476
+ inheritedPointAfterMiniZoom > inheritedPointAfterMainZoom * 2,
477
+ `${inheritedPointAfterMainZoom} → ${inheritedPointAfterMiniZoom}`
478
+ )
479
+ inheritedPointView.destroy()
480
+ inheritedPointHost.remove()
481
+ // 这一轮:小图自身倍数的联动指数(pointZoomScale)真的生效 —— 放大鹰眼图 6 倍,节点半径按 6^指数 长
325
482
  const zoomHost = document.createElement('div')
326
483
  zoomHost.style.cssText = 'position:fixed;left:-9999px;top:0;width:420px;height:320px'
327
484
  document.body.appendChild(zoomHost)
@@ -458,6 +615,193 @@
458
615
  )
459
616
  extraView.destroy()
460
617
  extraHost.remove()
618
+ // 本轮鹰眼图五项:回线居中、动态位移、backgroundStyle、主图尺寸 / 图标、外部挂载
619
+ const circuitHost = document.createElement('div')
620
+ circuitHost.style.cssText = 'position:fixed;left:-9999px;top:0;width:420px;height:320px'
621
+ document.body.appendChild(circuitHost)
622
+ let circuitLines = []
623
+ const circuitView = createGisView(circuitHost, {
624
+ points: [{ id: 'ca', x: 0, y: 0 }, { id: 'cb', x: 100, y: 0 }],
625
+ lines: [
626
+ { id: 'cl1', from: 'ca', to: 'cb' },
627
+ { id: 'cl2', from: 'ca', to: 'cb' }
628
+ ],
629
+ channels: [{ id: 'cch', from: 'ca', to: 'cb', lineIds: ['cl1', 'cl2'] }],
630
+ background: false,
631
+ minimap: {
632
+ width: 200,
633
+ height: 150,
634
+ background: false,
635
+ channelCircuits: true,
636
+ boxStyle: { fillOpacity: 0, strokeWidth: 0 },
637
+ draw: info => { circuitLines = info.data.lines }
638
+ }
639
+ })
640
+ await settleFrames()
641
+ const allCircuitPaths = circuitLines.map(line => line.path)
642
+ circuitLines = []
643
+ circuitView.updateOptions({ minimap: { ...circuitView.getOptions().minimap, channelCircuits: false } })
644
+ await settleFrames()
645
+ const representativePath = circuitLines[0]?.path ?? []
646
+ const expectedCenter = allCircuitPaths.length === 2
647
+ ? allCircuitPaths[0].map((coordinate, index) => [
648
+ (coordinate[0] + allCircuitPaths[1][index][0]) / 2,
649
+ (coordinate[1] + allCircuitPaths[1][index][1]) / 2
650
+ ])
651
+ : []
652
+ const centeredRepresentative = representativePath.length === expectedCenter.length && representativePath.every((coordinate, index) =>
653
+ Math.abs(coordinate[0] - expectedCenter[index][0]) < 1e-6 && Math.abs(coordinate[1] - expectedCenter[index][1]) < 1e-6
654
+ )
655
+ check(
656
+ '鹰眼图关闭回线后双回线取居中代表线',
657
+ allCircuitPaths.length === 2 && circuitLines.length === 1 && centeredRepresentative,
658
+ `原回线 ${allCircuitPaths.length} 条,代表线 ${circuitLines.length} 条,路径 ${JSON.stringify(representativePath)}`
659
+ )
660
+ circuitView.destroy()
661
+ circuitHost.remove()
662
+ const styleHost = document.createElement('div')
663
+ styleHost.style.cssText = 'position:fixed;left:-9999px;top:0;width:360px;height:260px'
664
+ document.body.appendChild(styleHost)
665
+ const styleView = createGisView(styleHost, {
666
+ points: [],
667
+ lines: [],
668
+ background: { color: '#ffffff' },
669
+ minimap: {
670
+ width: 140,
671
+ height: 90,
672
+ background: 'inherit',
673
+ backgroundStyle: 'rgb(20, 40, 60)',
674
+ boxStyle: { fillOpacity: 0, strokeWidth: 0 }
675
+ }
676
+ })
677
+ await settleFrames()
678
+ const styledRoot = styleView.getMinimapElement()
679
+ const styledCanvas = styledRoot?.querySelector('.gis-view__minimap-canvas')
680
+ const styledPixels = styledCanvas?.getContext('2d').getImageData(0, 0, styledCanvas.width, styledCanvas.height).data ?? []
681
+ let opaqueBackgroundPixels = 0
682
+ for (let index = 3; index < styledPixels.length; index += 4) {
683
+ if (styledPixels[index] > 0) {
684
+ opaqueBackgroundPixels += 1
685
+ }
686
+ }
687
+ const styledColor = styledRoot ? getComputedStyle(styledRoot).backgroundColor : ''
688
+ check(
689
+ '鹰眼图显式 backgroundStyle 接管容器底色且画布让出底色',
690
+ styledColor === 'rgb(20, 40, 60)' && opaqueBackgroundPixels === 0,
691
+ `容器=${styledColor},画布不透明像素 ${opaqueBackgroundPixels}`
692
+ )
693
+ styleView.destroy()
694
+ styleHost.remove()
695
+ const minimapErrors = []
696
+ const captureMinimapError = event => minimapErrors.push(String(event.error?.stack ?? event.message))
697
+ window.addEventListener('error', captureMinimapError)
698
+ const pointHost = document.createElement('div')
699
+ pointHost.style.cssText = 'position:fixed;left:-9999px;top:0;width:360px;height:260px'
700
+ document.body.appendChild(pointHost)
701
+ const pointView = createGisView(pointHost, {
702
+ points: [
703
+ { id: 'square-node', x: 0, y: 0 },
704
+ { id: 'circle-node', x: 100, y: 0 },
705
+ { id: 'icon-node', x: 200, y: 0 }
706
+ ],
707
+ lines: [],
708
+ pointRules: [
709
+ { name: '小图方点', when: { id: 'square-node' }, style: { size: 64, symbol: 'square', color: '#0088ff', strokeColor: '#0088ff' } },
710
+ { name: '小图圆点', when: { id: 'circle-node' }, style: { size: 64, symbol: 'circle', color: '#00cc66', strokeColor: '#00cc66' } },
711
+ { name: '小图图标', when: { id: 'icon-node' }, style: { size: 64, symbol: { type: 'svg', path: 'M0 0H20V20H0Z', viewBox: [0, 0, 20, 20], fill: '#ff00ff' }, color: '#0088ff', strokeColor: '#0088ff' } }
712
+ ],
713
+ background: false,
714
+ minimap: {
715
+ width: 220,
716
+ height: 120,
717
+ background: false,
718
+ extent: { minX: -20, minY: -20, maxX: 220, maxY: 20 },
719
+ boxStyle: { fillOpacity: 0, strokeWidth: 0 }
720
+ }
721
+ })
722
+ let pointInk = { blue: 0, green: 0, magenta: 0 }
723
+ for (let attempt = 0; attempt < 15; attempt += 1) {
724
+ await new Promise(resolve => setTimeout(resolve, 60))
725
+ const pointCanvas = pointView.getMinimapElement()?.querySelector('.gis-view__minimap-canvas')
726
+ const pointPixels = pointCanvas?.getContext('2d').getImageData(0, 0, pointCanvas.width, pointCanvas.height).data ?? []
727
+ pointInk = { blue: 0, green: 0, magenta: 0 }
728
+ for (let index = 0; index < pointPixels.length; index += 4) {
729
+ const red = pointPixels[index]
730
+ const green = pointPixels[index + 1]
731
+ const blue = pointPixels[index + 2]
732
+ if (red < 60 && green > 80 && green < 190 && blue > 200) {
733
+ pointInk.blue += 1
734
+ }
735
+ if (red < 60 && green > 150 && blue < 140) {
736
+ pointInk.green += 1
737
+ }
738
+ if (red > 220 && green < 50 && blue > 220) {
739
+ pointInk.magenta += 1
740
+ }
741
+ }
742
+ if (pointInk.magenta > 500 && pointInk.blue > 500 && pointInk.green > 500) {
743
+ break
744
+ }
745
+ }
746
+ const circleSquareRatio = pointInk.blue ? pointInk.green / pointInk.blue : 0
747
+ check(
748
+ '鹰眼图沿用主图规则后尺寸与 SVG 图标一致',
749
+ pointInk.blue > 500 && pointInk.green > 500 && pointInk.magenta > 500 && circleSquareRatio > 0.85 && circleSquareRatio < 1.15 && minimapErrors.length === 0,
750
+ '方点 ' + pointInk.blue + 'px,圆点 ' + pointInk.green + 'px(比值 ' + circleSquareRatio.toFixed(2) + '),图标 ' + pointInk.magenta + 'px,错误 ' + (minimapErrors[0] ?? '无')
751
+ )
752
+ pointView.destroy()
753
+ pointHost.remove()
754
+ window.removeEventListener('error', captureMinimapError)
755
+ const externalHostId = 'minimap-ext-' + Math.random().toString(36).slice(2)
756
+ const alternateHostId = 'minimap-alt-' + Math.random().toString(36).slice(2)
757
+ const externalHost = document.createElement('div')
758
+ externalHost.id = externalHostId
759
+ externalHost.style.cssText = 'position:fixed;left:-9999px;top:340px;width:280px;height:200px;background:#eef4fb'
760
+ const alternateHost = document.createElement('div')
761
+ alternateHost.id = alternateHostId
762
+ alternateHost.style.cssText = 'position:fixed;left:-9999px;top:560px;width:280px;height:200px;background:#f7eefb'
763
+ const externalViewHost = document.createElement('div')
764
+ externalViewHost.style.cssText = 'position:fixed;left:-9999px;top:0;width:360px;height:260px'
765
+ document.body.append(externalHost, alternateHost, externalViewHost)
766
+ const externalView = createGisView(externalViewHost, {
767
+ points: [{ id: 'ext-a', x: 0, y: 0 }, { id: 'ext-b', x: 30, y: 20 }],
768
+ lines: [{ id: 'ext-l', from: 'ext-a', to: 'ext-b' }],
769
+ background: false,
770
+ minimap: {
771
+ width: 140,
772
+ height: 90,
773
+ margin: 10,
774
+ background: false,
775
+ container: externalHost,
776
+ boxStyle: { fillOpacity: 0, strokeWidth: 0 }
777
+ }
778
+ })
779
+ await settleFrames()
780
+ const externalRoot = externalView.getMinimapElement()
781
+ const beforeMoveRect = externalRoot?.getBoundingClientRect()
782
+ externalView.updateOptions({ minimap: { ...externalView.getOptions().minimap, offset: [30, 20] } })
783
+ await settleFrames()
784
+ const afterMoveRect = externalRoot?.getBoundingClientRect()
785
+ const moved = Boolean(beforeMoveRect && afterMoveRect &&
786
+ Math.abs(afterMoveRect.left - beforeMoveRect.left - 30) < 0.75 &&
787
+ Math.abs(afterMoveRect.top - beforeMoveRect.top - 20) < 0.75)
788
+ check(
789
+ '鹰眼图能挂到外部 div,并用 offset 动态移动整张图',
790
+ externalRoot?.parentElement === externalHost && externalViewHost.querySelector('.gis-view__minimap') === null && moved,
791
+ '父元素 ' + (externalRoot?.parentElement?.id ?? 'null') + ',位移 ' + (beforeMoveRect ? Math.round(afterMoveRect.left - beforeMoveRect.left) + ',' + Math.round(afterMoveRect.top - beforeMoveRect.top) : 'null')
792
+ )
793
+ externalView.updateOptions({ minimap: { ...externalView.getOptions().minimap, container: '#' + alternateHostId } })
794
+ await settleFrames()
795
+ const movedHost = externalView.getMinimapElement() === externalRoot && externalRoot?.parentElement === alternateHost
796
+ externalView.destroy()
797
+ check(
798
+ '鹰眼图换外部容器复用同一根节点,destroy 后一并清理',
799
+ movedHost && !externalRoot?.isConnected && !alternateHost.querySelector('.gis-view__minimap'),
800
+ '换容器 ' + movedHost + ',销毁后 connected=' + (externalRoot?.isConnected ?? false)
801
+ )
802
+ externalHost.remove()
803
+ alternateHost.remove()
804
+ externalViewHost.remove()
461
805
  // 宿主缩放(CSS zoom / 祖先的 transform: scale):引擎的 CSS 像素按布局像素算、画布备份仍是设备像素、
462
806
  // DOM 标签与指针换算同除显示比例;三样对不上时,标签会飞出节点、指针也点不中
463
807
  const plainScaleHost = document.createElement('div')