@bitalltech-maplibre/core 1.0.2 → 1.0.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 (28) hide show
  1. package/README.md +102 -0
  2. package/dist/index.cjs.js +1 -1
  3. package/dist/index.esm.js +1566 -107
  4. package/dist/index.umd.js +1 -1
  5. package/dist/types/effects/electronic-fence-common.d.ts +76 -0
  6. package/dist/types/effects/electronic-fence-types.d.ts +131 -0
  7. package/dist/types/effects/index.d.ts +3 -0
  8. package/dist/types/effects/low-altitude/breach-alert.d.ts +61 -0
  9. package/dist/types/effects/low-altitude/breathing-circle.d.ts +5 -0
  10. package/dist/types/effects/low-altitude/coordinated-countermeasure.d.ts +5 -0
  11. package/dist/types/effects/low-altitude/countermeasure-beam.d.ts +5 -0
  12. package/dist/types/effects/low-altitude/defence-circle.d.ts +5 -0
  13. package/dist/types/effects/low-altitude/directional-pulse.d.ts +6 -0
  14. package/dist/types/effects/low-altitude/navigation-spoofing.d.ts +6 -0
  15. package/dist/types/effects/low-altitude/pulse-marker.d.ts +5 -0
  16. package/dist/types/effects/low-altitude/radar-sweep.d.ts +5 -0
  17. package/dist/types/effects/low-altitude/ring-pulse-marker.d.ts +5 -0
  18. package/dist/types/effects/low-altitude/sector-scan.d.ts +5 -0
  19. package/dist/types/effects/low-altitude/shared.d.ts +67 -0
  20. package/dist/types/effects/low-altitude/target-lock.d.ts +4 -0
  21. package/dist/types/effects/low-altitude/types.d.ts +333 -0
  22. package/dist/types/effects/low-altitude/visual.d.ts +15 -0
  23. package/dist/types/effects/low-altitude.d.ts +15 -441
  24. package/dist/types/effects/standard-electronic-fence.d.ts +7 -0
  25. package/dist/types/effects/webgl-electronic-fence.d.ts +7 -0
  26. package/dist/types/index.d.ts +2 -2
  27. package/dist/types/map/create-map.d.ts +2 -1
  28. package/package.json +1 -1
package/dist/index.esm.js CHANGED
@@ -320,7 +320,7 @@ const setLayersVisibility = (map, layerIds, visible) => {
320
320
  }
321
321
  });
322
322
  };
323
- const isStyleNotReadyError = (error) => error instanceof Error && error.message === "Style is not done loading.";
323
+ const isStyleNotReadyError$1 = (error) => error instanceof Error && error.message === "Style is not done loading.";
324
324
  const createManagedEffect = (map, initialOptions, renderEffect) => {
325
325
  const effectId = initialOptions.id;
326
326
  const sourceId = `${effectId}-source`;
@@ -414,7 +414,7 @@ const createManagedEffect = (map, initialOptions, renderEffect) => {
414
414
  renderNow();
415
415
  stopWaitingForStyle();
416
416
  } catch (error) {
417
- if (!isStyleNotReadyError(error)) {
417
+ if (!isStyleNotReadyError$1(error)) {
418
418
  throw error;
419
419
  }
420
420
  if (!waitingForStyle) {
@@ -549,6 +549,87 @@ const createPulseMarkerRender = (options) => {
549
549
  }
550
550
  };
551
551
  };
552
+ const addPulseMarker = (map, options) => createManagedEffect(map, options, createPulseMarkerRender);
553
+ const createBreathingCircleData = (options, phase = 0) => {
554
+ const baseRadius = Math.max(1, options.radius ?? 90);
555
+ const minRadius = Math.max(1, options.minRadius ?? baseRadius * 0.78);
556
+ const maxRadius = Math.max(
557
+ minRadius,
558
+ options.maxRadius ?? baseRadius * 1.22
559
+ );
560
+ const progress = (1 - Math.cos(phase * Math.PI * 2)) / 2;
561
+ const currentRadius = minRadius + (maxRadius - minRadius) * progress;
562
+ const fillOpacity = options.fillOpacity ?? 0.28;
563
+ const minFillOpacity = options.minFillOpacity ?? fillOpacity * 0.46;
564
+ const strokeOpacity = options.strokeOpacity ?? 0.82;
565
+ const minStrokeOpacity = options.minStrokeOpacity ?? strokeOpacity * 0.38;
566
+ const inverseProgress = 1 - progress;
567
+ const features = [
568
+ createFeature(createCirclePolygon(options.center, currentRadius), {
569
+ kind: "breathing-fill",
570
+ opacity: minFillOpacity + (fillOpacity - minFillOpacity) * inverseProgress
571
+ })
572
+ ];
573
+ if ((options.strokeWidth ?? 2) > 0) {
574
+ features.push(
575
+ createFeature(createCircleLine(options.center, currentRadius), {
576
+ kind: "breathing-stroke",
577
+ opacity: minStrokeOpacity + (strokeOpacity - minStrokeOpacity) * inverseProgress
578
+ })
579
+ );
580
+ }
581
+ return createFeatureCollection(features);
582
+ };
583
+ const createBreathingCircleRender = (options) => {
584
+ const color = options.color || "#1677ff";
585
+ const fillColor = options.fillColor || color;
586
+ const strokeColor = options.strokeColor || color;
587
+ const strokeWidth = Math.max(0, options.strokeWidth ?? 2);
588
+ return {
589
+ data: createBreathingCircleData(options),
590
+ layers: [
591
+ {
592
+ id: `${options.id}-breathing-fill`,
593
+ type: "fill",
594
+ filter: ["==", ["get", "kind"], "breathing-fill"],
595
+ paint: {
596
+ "fill-color": fillColor,
597
+ "fill-opacity": ["coalesce", ["get", "opacity"], options.fillOpacity ?? 0.28]
598
+ }
599
+ },
600
+ {
601
+ id: `${options.id}-breathing-stroke`,
602
+ type: "line",
603
+ filter: ["==", ["get", "kind"], "breathing-stroke"],
604
+ layout: {
605
+ "line-cap": "round",
606
+ "line-join": "round"
607
+ },
608
+ paint: {
609
+ "line-color": strokeColor,
610
+ "line-opacity": ["coalesce", ["get", "opacity"], options.strokeOpacity ?? 0.82],
611
+ "line-width": strokeWidth
612
+ }
613
+ }
614
+ ],
615
+ startAnimation({ getOptions, setData }) {
616
+ const duration = Math.max(400, getOptions().duration ?? 1800);
617
+ const startTime = performance.now();
618
+ let frameId = 0;
619
+ const tick = (timestamp) => {
620
+ const currentOptions = getOptions();
621
+ const phase = (timestamp - startTime) % duration / duration;
622
+ setData(createBreathingCircleData(currentOptions, phase));
623
+ frameId = requestAnimationFrame(tick);
624
+ };
625
+ frameId = requestAnimationFrame(tick);
626
+ return () => {
627
+ cancelAnimationFrame(frameId);
628
+ };
629
+ }
630
+ };
631
+ };
632
+ const addBreathingCircle = (map, options) => createManagedEffect(map, options, createBreathingCircleRender);
552
633
  const createRingPulseEffectData = (options, phase = 0) => {
553
634
  const baseRadius = Math.max(16, options.radius ?? 110);
554
635
  const pulseScale = Math.max(0, options.pulseScale ?? 0.16);
@@ -638,86 +719,8 @@ const createRingPulseMarkerRender = (options) => {
638
719
  }
639
720
  };
640
721
  };
641
- const createBreathingCircleData = (options, phase = 0) => {
642
- const baseRadius = Math.max(1, options.radius ?? 90);
643
- const minRadius = Math.max(1, options.minRadius ?? baseRadius * 0.78);
644
- const maxRadius = Math.max(
645
- minRadius,
646
- options.maxRadius ?? baseRadius * 1.22
647
- );
648
- const progress = (1 - Math.cos(phase * Math.PI * 2)) / 2;
649
- const currentRadius = minRadius + (maxRadius - minRadius) * progress;
650
- const fillOpacity = options.fillOpacity ?? 0.28;
651
- const minFillOpacity = options.minFillOpacity ?? fillOpacity * 0.46;
652
- const strokeOpacity = options.strokeOpacity ?? 0.82;
653
- const minStrokeOpacity = options.minStrokeOpacity ?? strokeOpacity * 0.38;
654
- const inverseProgress = 1 - progress;
655
- const features = [
656
- createFeature(createCirclePolygon(options.center, currentRadius), {
657
- kind: "breathing-fill",
658
- opacity: minFillOpacity + (fillOpacity - minFillOpacity) * inverseProgress
659
- })
660
- ];
661
- if ((options.strokeWidth ?? 2) > 0) {
662
- features.push(
663
- createFeature(createCircleLine(options.center, currentRadius), {
664
- kind: "breathing-stroke",
665
- opacity: minStrokeOpacity + (strokeOpacity - minStrokeOpacity) * inverseProgress
666
- })
667
- );
668
- }
669
- return createFeatureCollection(features);
670
- };
671
- const createBreathingCircleRender = (options) => {
672
- const color = options.color || "#1677ff";
673
- const fillColor = options.fillColor || color;
674
- const strokeColor = options.strokeColor || color;
675
- const strokeWidth = Math.max(0, options.strokeWidth ?? 2);
676
- return {
677
- data: createBreathingCircleData(options),
678
- layers: [
679
- {
680
- id: `${options.id}-breathing-fill`,
681
- type: "fill",
682
- filter: ["==", ["get", "kind"], "breathing-fill"],
683
- paint: {
684
- "fill-color": fillColor,
685
- "fill-opacity": ["coalesce", ["get", "opacity"], options.fillOpacity ?? 0.28]
686
- }
687
- },
688
- {
689
- id: `${options.id}-breathing-stroke`,
690
- type: "line",
691
- filter: ["==", ["get", "kind"], "breathing-stroke"],
692
- layout: {
693
- "line-cap": "round",
694
- "line-join": "round"
695
- },
696
- paint: {
697
- "line-color": strokeColor,
698
- "line-opacity": ["coalesce", ["get", "opacity"], options.strokeOpacity ?? 0.82],
699
- "line-width": strokeWidth
700
- }
701
- }
702
- ],
703
- startAnimation({ getOptions, setData }) {
704
- const duration = Math.max(400, getOptions().duration ?? 1800);
705
- const startTime = performance.now();
706
- let frameId = 0;
707
- const tick = (timestamp) => {
708
- const currentOptions = getOptions();
709
- const phase = (timestamp - startTime) % duration / duration;
710
- setData(createBreathingCircleData(currentOptions, phase));
711
- frameId = requestAnimationFrame(tick);
712
- };
713
- frameId = requestAnimationFrame(tick);
714
- return () => {
715
- cancelAnimationFrame(frameId);
716
- };
717
- }
718
- };
719
- };
720
- const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
722
+ const addRingPulseMarker = (map, options) => createManagedEffect(map, options, createRingPulseMarkerRender);
723
+ const clamp$1 = (value, min, max) => Math.min(max, Math.max(min, value));
721
724
  const parseColor = (color) => {
722
725
  const value = color.trim();
723
726
  const hex = value.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
@@ -735,9 +738,9 @@ const parseColor = (color) => {
735
738
  );
736
739
  if (rgb) {
737
740
  return [
738
- clamp(Number(rgb[1]), 0, 255),
739
- clamp(Number(rgb[2]), 0, 255),
740
- clamp(Number(rgb[3]), 0, 255)
741
+ clamp$1(Number(rgb[1]), 0, 255),
742
+ clamp$1(Number(rgb[2]), 0, 255),
743
+ clamp$1(Number(rgb[3]), 0, 255)
741
744
  ];
742
745
  }
743
746
  return void 0;
@@ -753,7 +756,7 @@ const toRgbaColor = (color, opacity) => {
753
756
  if (!rgb) {
754
757
  return color;
755
758
  }
756
- return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${clamp(opacity, 0, 1)})`;
759
+ return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${clamp$1(opacity, 0, 1)})`;
757
760
  };
758
761
  const easeOutQuad = (t) => t * (2 - t);
759
762
  const lerpRgb = (a, b, t) => [
@@ -945,6 +948,7 @@ const createSectorScanRender = (options) => {
945
948
  } : void 0
946
949
  };
947
950
  };
951
+ const addSectorScan = (map, options) => createManagedEffect(map, options, createSectorScanRender);
948
952
  const createDirectionalPulseData = (options, leadingDistance) => {
949
953
  const radius = Math.max(40, options.radius);
950
954
  const direction = options.direction ?? 270;
@@ -1023,6 +1027,7 @@ const createDirectionalPulseRender = (options) => ({
1023
1027
  };
1024
1028
  } : void 0
1025
1029
  });
1030
+ const addDirectionalPulse = (map, options) => createManagedEffect(map, options, createDirectionalPulseRender);
1026
1031
  const hasRadarSweepGradient = (options) => {
1027
1032
  var _a;
1028
1033
  const colors = (_a = options.gradient) == null ? void 0 : _a.colors;
@@ -1296,6 +1301,7 @@ const createRadarSweepRender = (options) => {
1296
1301
  }
1297
1302
  };
1298
1303
  };
1304
+ const addRadarSweep = (map, options) => createManagedEffect(map, options, createRadarSweepRender);
1299
1305
  const createDefenceCircleCanvas = () => {
1300
1306
  const canvas = document.createElement("canvas");
1301
1307
  canvas.width = 512;
@@ -1396,6 +1402,7 @@ const createDefenceCircleRender = (options) => {
1396
1402
  ]
1397
1403
  };
1398
1404
  };
1405
+ const addDefenceCircle = (map, options) => createManagedEffect(map, options, createDefenceCircleRender);
1399
1406
  const createCountermeasureBeamData = (options, elapsed = 0) => {
1400
1407
  const sourceWidth = Math.max(1, options.sourceWidth ?? 220);
1401
1408
  const targetWidth = Math.max(1, options.targetWidth ?? 45);
@@ -1545,6 +1552,7 @@ const createCountermeasureBeamRender = (options) => {
1545
1552
  }
1546
1553
  };
1547
1554
  };
1555
+ const addCountermeasureBeam = (map, options) => createManagedEffect(map, options, createCountermeasureBeamRender);
1548
1556
  const getCoordinationColor = (type) => {
1549
1557
  if (type === "detection") return "#38bdf8";
1550
1558
  if (type === "command") return "#22d3ee";
@@ -1707,6 +1715,7 @@ const createCoordinatedCountermeasureRender = (options) => {
1707
1715
  }
1708
1716
  };
1709
1717
  };
1718
+ const addCoordinatedCountermeasure = (map, options) => createManagedEffect(map, options, createCoordinatedCountermeasureRender);
1710
1719
  const createNavigationSpoofingData = (options, elapsed = 0) => {
1711
1720
  const features = [
1712
1721
  createFeature(
@@ -1857,15 +1866,6 @@ const createNavigationSpoofingRender = (options) => {
1857
1866
  }
1858
1867
  };
1859
1868
  };
1860
- const addPulseMarker = (map, options) => createManagedEffect(map, options, createPulseMarkerRender);
1861
- const addBreathingCircle = (map, options) => createManagedEffect(map, options, createBreathingCircleRender);
1862
- const addRingPulseMarker = (map, options) => createManagedEffect(map, options, createRingPulseMarkerRender);
1863
- const addSectorScan = (map, options) => createManagedEffect(map, options, createSectorScanRender);
1864
- const addDirectionalPulse = (map, options) => createManagedEffect(map, options, createDirectionalPulseRender);
1865
- const addRadarSweep = (map, options) => createManagedEffect(map, options, createRadarSweepRender);
1866
- const addDefenceCircle = (map, options) => createManagedEffect(map, options, createDefenceCircleRender);
1867
- const addCountermeasureBeam = (map, options) => createManagedEffect(map, options, createCountermeasureBeamRender);
1868
- const addCoordinatedCountermeasure = (map, options) => createManagedEffect(map, options, createCoordinatedCountermeasureRender);
1869
1869
  const addNavigationSpoofing = (map, options) => createManagedEffect(map, options, createNavigationSpoofingRender);
1870
1870
  const addTargetLock = (map, initialOptions) => {
1871
1871
  const effectId = initialOptions.id;
@@ -2330,7 +2330,7 @@ const addBreachAlert = (map, initialOptions) => {
2330
2330
  waitingForStyle = false;
2331
2331
  map.off("styledata", handleStyleData);
2332
2332
  } catch (error) {
2333
- if (!isStyleNotReadyError(error)) {
2333
+ if (!isStyleNotReadyError$1(error)) {
2334
2334
  throw error;
2335
2335
  }
2336
2336
  if (!waitingForStyle) {
@@ -2421,22 +2421,1479 @@ const addBreachAlert = (map, initialOptions) => {
2421
2421
  }
2422
2422
  };
2423
2423
  };
2424
- const resolveStyle = (style, tdtToken) => {
2425
- if (!style) {
2426
- return getBaseMapStyle("openfreemap-liberty");
2424
+ const FENCE_ID_PROPERTY = "__electronic_fence_id";
2425
+ const clone = (value) => JSON.parse(JSON.stringify(value));
2426
+ const isSamePosition = (a, b) => a[0] === b[0] && a[1] === b[1];
2427
+ const normalizeRing = (ring) => {
2428
+ const coordinates = ring.map((position) => {
2429
+ const lng = Number(position[0]);
2430
+ const lat = Number(position[1]);
2431
+ if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
2432
+ throw new Error("电子围栏坐标必须是有限数字。");
2433
+ }
2434
+ return [lng, lat];
2435
+ });
2436
+ if (coordinates.length < 3) {
2437
+ throw new Error("电子围栏的每个环至少需要 3 个不同坐标。");
2427
2438
  }
2428
- if (typeof style === "string" && isBaseMapType(style)) {
2429
- return getBaseMapStyle(style, { token: tdtToken });
2439
+ if (!isSamePosition(coordinates[0], coordinates[coordinates.length - 1])) {
2440
+ coordinates.push([...coordinates[0]]);
2430
2441
  }
2431
- return style;
2442
+ if (coordinates.length < 4) {
2443
+ throw new Error("电子围栏 Polygon 环无效。");
2444
+ }
2445
+ return coordinates;
2432
2446
  };
2433
- const createMap = (options) => {
2434
- const { style, tdtToken, ...mapOptions } = options;
2435
- return new maplibregl.Map({
2436
- ...mapOptions,
2437
- style: resolveStyle(style, tdtToken)
2447
+ const normalizeFeature = (feature) => {
2448
+ if (typeof feature.id !== "string" || !feature.id.trim()) {
2449
+ throw new Error("电子围栏必须提供非空字符串 id。");
2450
+ }
2451
+ if (!feature.geometry || feature.geometry.type !== "Polygon") {
2452
+ throw new Error(`电子围栏 ${feature.id} 仅支持 Polygon geometry。`);
2453
+ }
2454
+ const properties = clone(feature.properties || {});
2455
+ return {
2456
+ type: "Feature",
2457
+ id: feature.id,
2458
+ geometry: {
2459
+ type: "Polygon",
2460
+ coordinates: feature.geometry.coordinates.map(normalizeRing)
2461
+ },
2462
+ properties
2463
+ };
2464
+ };
2465
+ const mergeFeaturePatch = (feature, patch) => {
2466
+ const currentProperties = feature.properties || {};
2467
+ const nextProperties = patch.properties || {};
2468
+ const nextStyle = nextProperties.style ? {
2469
+ ...currentProperties.style || {},
2470
+ ...nextProperties.style
2471
+ } : currentProperties.style;
2472
+ return normalizeFeature({
2473
+ ...clone(feature),
2474
+ geometry: patch.geometry ? clone(patch.geometry) : clone(feature.geometry),
2475
+ properties: {
2476
+ ...clone(currentProperties),
2477
+ ...clone(nextProperties),
2478
+ ...nextStyle ? { style: nextStyle } : {}
2479
+ }
2438
2480
  });
2439
2481
  };
2482
+ class ElectronicFenceStore {
2483
+ constructor() {
2484
+ this.features = /* @__PURE__ */ new Map();
2485
+ this.states = /* @__PURE__ */ new Map();
2486
+ this.activeIds = /* @__PURE__ */ new Set();
2487
+ this.hiddenIds = /* @__PURE__ */ new Set();
2488
+ }
2489
+ setData(data) {
2490
+ const nextFeatures = /* @__PURE__ */ new Map();
2491
+ data.features.forEach((feature) => {
2492
+ const normalized = normalizeFeature(
2493
+ feature
2494
+ );
2495
+ if (nextFeatures.has(normalized.id)) {
2496
+ throw new Error(`电子围栏 id 重复:${normalized.id}`);
2497
+ }
2498
+ nextFeatures.set(normalized.id, normalized);
2499
+ });
2500
+ this.features = nextFeatures;
2501
+ this.states.clear();
2502
+ this.activeIds.clear();
2503
+ this.hiddenIds.clear();
2504
+ }
2505
+ add(feature) {
2506
+ const normalized = normalizeFeature(feature);
2507
+ if (this.features.has(normalized.id)) {
2508
+ throw new Error(`电子围栏 id 已存在:${normalized.id}`);
2509
+ }
2510
+ this.features.set(normalized.id, normalized);
2511
+ return normalized.id;
2512
+ }
2513
+ update(id, patch) {
2514
+ const feature = this.requireFeature(id);
2515
+ this.features.set(id, mergeFeaturePatch(feature, patch));
2516
+ }
2517
+ remove(id) {
2518
+ this.requireFeature(id);
2519
+ this.features.delete(id);
2520
+ this.states.delete(id);
2521
+ this.activeIds.delete(id);
2522
+ this.hiddenIds.delete(id);
2523
+ }
2524
+ clear() {
2525
+ this.features.clear();
2526
+ this.states.clear();
2527
+ this.activeIds.clear();
2528
+ this.hiddenIds.clear();
2529
+ }
2530
+ setActive(id, active) {
2531
+ this.requireFeature(id);
2532
+ if (active) {
2533
+ this.activeIds.add(id);
2534
+ } else {
2535
+ this.activeIds.delete(id);
2536
+ }
2537
+ }
2538
+ setState(id, state) {
2539
+ this.requireFeature(id);
2540
+ if (state !== "normal" && state !== "warning" && state !== "alarm" && state !== "disabled") {
2541
+ throw new Error(`电子围栏 ${id} 的渲染状态无效。`);
2542
+ }
2543
+ if (state === "normal") {
2544
+ this.states.delete(id);
2545
+ } else {
2546
+ this.states.set(id, state);
2547
+ }
2548
+ }
2549
+ setVisible(id, visible) {
2550
+ this.requireFeature(id);
2551
+ if (visible) {
2552
+ this.hiddenIds.delete(id);
2553
+ } else {
2554
+ this.hiddenIds.add(id);
2555
+ }
2556
+ }
2557
+ isActive(id) {
2558
+ return this.activeIds.has(id);
2559
+ }
2560
+ getState(id) {
2561
+ this.requireFeature(id);
2562
+ return this.states.get(id) || "normal";
2563
+ }
2564
+ get(id) {
2565
+ const feature = this.features.get(id);
2566
+ return feature ? clone(feature) : void 0;
2567
+ }
2568
+ values() {
2569
+ return Array.from(this.features.values());
2570
+ }
2571
+ visibleValues() {
2572
+ return this.values().filter((feature) => !this.hiddenIds.has(feature.id));
2573
+ }
2574
+ requireFeature(id) {
2575
+ const feature = this.features.get(id);
2576
+ if (!feature) {
2577
+ throw new Error(`电子围栏不存在:${id}`);
2578
+ }
2579
+ return feature;
2580
+ }
2581
+ }
2582
+ const resolveFiniteNumber = (value, fallback, name, positive = false) => {
2583
+ const resolved = value === void 0 ? fallback : Number(value);
2584
+ if (!Number.isFinite(resolved) || positive && resolved <= 0) {
2585
+ throw new Error(`${name} 必须是${positive ? "大于 0 的" : "有限"}数字。`);
2586
+ }
2587
+ return resolved;
2588
+ };
2589
+ const validateFenceDefaults = (id, defaults) => {
2590
+ if (!id.trim()) {
2591
+ throw new Error("电子围栏插件 id 不能为空。");
2592
+ }
2593
+ resolveFiniteNumber(defaults.baseHeight, 0, "defaults.baseHeight");
2594
+ resolveFiniteNumber(defaults.height, 0, "defaults.height", true);
2595
+ resolveFiniteNumber(defaults.thickness, 1, "defaults.thickness", true);
2596
+ };
2597
+ const resolveFenceDimensions = (defaults) => ({
2598
+ baseHeight: resolveFiniteNumber(
2599
+ defaults.baseHeight,
2600
+ 0,
2601
+ "defaults.baseHeight"
2602
+ ),
2603
+ height: resolveFiniteNumber(
2604
+ defaults.height,
2605
+ 0,
2606
+ "defaults.height",
2607
+ true
2608
+ ),
2609
+ thickness: resolveFiniteNumber(
2610
+ defaults.thickness,
2611
+ 1,
2612
+ "defaults.thickness",
2613
+ true
2614
+ )
2615
+ });
2616
+ const resolveFenceVisualState = (status, active) => {
2617
+ if (status !== "normal") {
2618
+ return status;
2619
+ }
2620
+ return active ? "active" : "normal";
2621
+ };
2622
+ const resolveFenceStyle = (options) => {
2623
+ var _a, _b;
2624
+ const normal = {
2625
+ ...options.defaults.normal,
2626
+ ...((_a = options.overrides) == null ? void 0 : _a.normal) || {}
2627
+ };
2628
+ const stateStyle = options.state === "normal" ? {} : {
2629
+ ...options.defaults[options.state],
2630
+ ...((_b = options.overrides) == null ? void 0 : _b[options.state]) || {}
2631
+ };
2632
+ return {
2633
+ ...normal,
2634
+ ...stateStyle
2635
+ };
2636
+ };
2637
+ const createFenceSegments = (geometry, thickness) => {
2638
+ const halfThickness = thickness / 2;
2639
+ const segments = [];
2640
+ geometry.coordinates.forEach((ring) => {
2641
+ const rawSegments = [];
2642
+ for (let index = 0; index < ring.length - 1; index += 1) {
2643
+ const start = [ring[index][0], ring[index][1]];
2644
+ const end = [ring[index + 1][0], ring[index + 1][1]];
2645
+ const length = getDistance(start, end);
2646
+ if (length < 0.01) {
2647
+ continue;
2648
+ }
2649
+ rawSegments.push({
2650
+ start,
2651
+ end,
2652
+ length,
2653
+ bearing: getBearing(start, end)
2654
+ });
2655
+ }
2656
+ const totalLength = rawSegments.reduce(
2657
+ (sum, segment) => sum + segment.length,
2658
+ 0
2659
+ );
2660
+ let travelled = 0;
2661
+ rawSegments.forEach((segment) => {
2662
+ const extendedStart = getDestination(
2663
+ segment.start,
2664
+ halfThickness,
2665
+ segment.bearing + 180
2666
+ );
2667
+ const extendedEnd = getDestination(
2668
+ segment.end,
2669
+ halfThickness,
2670
+ segment.bearing
2671
+ );
2672
+ segments.push({
2673
+ startLeft: getDestination(
2674
+ extendedStart,
2675
+ halfThickness,
2676
+ segment.bearing - 90
2677
+ ),
2678
+ startRight: getDestination(
2679
+ extendedStart,
2680
+ halfThickness,
2681
+ segment.bearing + 90
2682
+ ),
2683
+ endLeft: getDestination(
2684
+ extendedEnd,
2685
+ halfThickness,
2686
+ segment.bearing - 90
2687
+ ),
2688
+ endRight: getDestination(
2689
+ extendedEnd,
2690
+ halfThickness,
2691
+ segment.bearing + 90
2692
+ ),
2693
+ startProgress: totalLength ? travelled / totalLength : 0,
2694
+ endProgress: totalLength ? (travelled + segment.length) / totalLength : 1
2695
+ });
2696
+ travelled += segment.length;
2697
+ });
2698
+ });
2699
+ return segments;
2700
+ };
2701
+ const createFenceWallMultiPolygon = (segments) => ({
2702
+ type: "MultiPolygon",
2703
+ coordinates: segments.map((segment) => [
2704
+ [
2705
+ segment.startLeft,
2706
+ segment.endLeft,
2707
+ segment.endRight,
2708
+ segment.startRight,
2709
+ segment.startLeft
2710
+ ]
2711
+ ])
2712
+ });
2713
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
2714
+ const parseFenceColor = (value) => {
2715
+ const color = value.trim().toLowerCase();
2716
+ if (color.startsWith("#")) {
2717
+ const hex = color.slice(1);
2718
+ const expanded = hex.length === 3 || hex.length === 4 ? hex.split("").map((character) => character + character).join("") : hex;
2719
+ if (expanded.length === 6 || expanded.length === 8) {
2720
+ const number = Number.parseInt(expanded, 16);
2721
+ if (Number.isFinite(number)) {
2722
+ return [
2723
+ (number >> (expanded.length === 8 ? 24 : 16) & 255) / 255,
2724
+ (number >> (expanded.length === 8 ? 16 : 8) & 255) / 255,
2725
+ (number >> (expanded.length === 8 ? 8 : 0) & 255) / 255,
2726
+ expanded.length === 8 ? (number & 255) / 255 : 1
2727
+ ];
2728
+ }
2729
+ }
2730
+ }
2731
+ const rgbMatch = color.match(/^rgba?\(([^)]+)\)$/);
2732
+ if (rgbMatch) {
2733
+ const parts = rgbMatch[1].split(",").map((part) => Number(part.trim()));
2734
+ if (parts.length >= 3 && parts.every((part) => Number.isFinite(part))) {
2735
+ return [
2736
+ clamp(parts[0] / 255, 0, 1),
2737
+ clamp(parts[1] / 255, 0, 1),
2738
+ clamp(parts[2] / 255, 0, 1),
2739
+ clamp(parts[3] ?? 1, 0, 1)
2740
+ ];
2741
+ }
2742
+ }
2743
+ throw new Error(`暂不支持的围栏颜色格式:${value}`);
2744
+ };
2745
+ const toFenceRgbaCss = (value, opacity = 1) => {
2746
+ const [red, green, blue, alpha] = parseFenceColor(value);
2747
+ return `rgba(${Math.round(red * 255)}, ${Math.round(green * 255)}, ${Math.round(
2748
+ blue * 255
2749
+ )}, ${clamp(alpha * opacity, 0, 1)})`;
2750
+ };
2751
+ const clampFenceNumber = clamp;
2752
+ const createFenceInteractionData = (store) => ({
2753
+ type: "FeatureCollection",
2754
+ features: store.visibleValues().map((feature) => ({
2755
+ type: "Feature",
2756
+ id: `${feature.id}:interaction`,
2757
+ geometry: clone(feature.geometry),
2758
+ properties: {
2759
+ [FENCE_ID_PROPERTY]: feature.id
2760
+ }
2761
+ }))
2762
+ });
2763
+ const bindFenceInteractions = (options) => {
2764
+ let hoveredId;
2765
+ const getHitId = (event) => {
2766
+ var _a;
2767
+ if (!options.map.getLayer(options.layerId)) {
2768
+ return void 0;
2769
+ }
2770
+ const feature = options.map.queryRenderedFeatures(event.point, {
2771
+ layers: [options.layerId]
2772
+ })[0];
2773
+ const id = (_a = feature == null ? void 0 : feature.properties) == null ? void 0 : _a[FENCE_ID_PROPERTY];
2774
+ return typeof id === "string" ? id : void 0;
2775
+ };
2776
+ const fire = (type, id, event) => {
2777
+ const feature = options.store.get(id);
2778
+ if (!feature) {
2779
+ return;
2780
+ }
2781
+ options.map.fire(`fence.${type}`, {
2782
+ instanceId: options.instanceId,
2783
+ renderer: options.renderer,
2784
+ id,
2785
+ feature,
2786
+ originalEvent: event
2787
+ });
2788
+ };
2789
+ const handleClick = (event) => {
2790
+ const id = getHitId(event);
2791
+ if (id) {
2792
+ fire("click", id, event);
2793
+ }
2794
+ };
2795
+ const handleMouseMove = (event) => {
2796
+ const nextId = getHitId(event);
2797
+ if (nextId === hoveredId) {
2798
+ return;
2799
+ }
2800
+ if (hoveredId) {
2801
+ fire("mouseleave", hoveredId, event);
2802
+ }
2803
+ hoveredId = nextId;
2804
+ if (hoveredId) {
2805
+ fire("mouseenter", hoveredId, event);
2806
+ }
2807
+ };
2808
+ const handleMouseOut = (event) => {
2809
+ if (!hoveredId) {
2810
+ return;
2811
+ }
2812
+ fire("mouseleave", hoveredId, event);
2813
+ hoveredId = void 0;
2814
+ };
2815
+ options.map.on("click", handleClick);
2816
+ options.map.on("mousemove", handleMouseMove);
2817
+ options.map.on("mouseout", handleMouseOut);
2818
+ return () => {
2819
+ options.map.off("click", handleClick);
2820
+ options.map.off("mousemove", handleMouseMove);
2821
+ options.map.off("mouseout", handleMouseOut);
2822
+ };
2823
+ };
2824
+ const isStyleNotReadyError = (error) => error instanceof Error && error.message === "Style is not done loading.";
2825
+ const setFenceLayerVisibility = (map, layerIds, visible) => {
2826
+ layerIds.forEach((layerId) => {
2827
+ if (map.getLayer(layerId)) {
2828
+ map.setLayoutProperty(layerId, "visibility", visible ? "visible" : "none");
2829
+ }
2830
+ });
2831
+ };
2832
+ const createElectronicFenceController = (options) => {
2833
+ let destroyed = false;
2834
+ const assertAlive = () => {
2835
+ if (destroyed) {
2836
+ throw new Error(`电子围栏插件 ${options.id} 已销毁。`);
2837
+ }
2838
+ };
2839
+ return {
2840
+ id: options.id,
2841
+ renderer: options.renderer,
2842
+ setData(data) {
2843
+ assertAlive();
2844
+ options.store.setData(data);
2845
+ options.refresh();
2846
+ },
2847
+ add(feature) {
2848
+ assertAlive();
2849
+ const id = options.store.add(feature);
2850
+ options.refresh();
2851
+ return id;
2852
+ },
2853
+ update(id, patch) {
2854
+ assertAlive();
2855
+ options.store.update(id, patch);
2856
+ options.refresh();
2857
+ },
2858
+ removeFeature(id) {
2859
+ assertAlive();
2860
+ options.store.remove(id);
2861
+ options.refresh();
2862
+ },
2863
+ clear() {
2864
+ assertAlive();
2865
+ options.store.clear();
2866
+ options.refresh();
2867
+ },
2868
+ setFeatureState(id, state) {
2869
+ assertAlive();
2870
+ options.store.setState(id, state);
2871
+ options.refresh();
2872
+ },
2873
+ setFeatureActive(id, active) {
2874
+ assertAlive();
2875
+ options.store.setActive(id, active);
2876
+ options.refresh();
2877
+ },
2878
+ setFeatureVisible(id, visible) {
2879
+ assertAlive();
2880
+ options.store.setVisible(id, visible);
2881
+ options.refresh();
2882
+ },
2883
+ show() {
2884
+ assertAlive();
2885
+ options.setPluginVisible(true);
2886
+ },
2887
+ hide() {
2888
+ assertAlive();
2889
+ options.setPluginVisible(false);
2890
+ },
2891
+ destroy() {
2892
+ if (destroyed) {
2893
+ return;
2894
+ }
2895
+ destroyed = true;
2896
+ options.destroy();
2897
+ }
2898
+ };
2899
+ };
2900
+ const KIND_PROPERTY = "__electronic_fence_kind";
2901
+ const COLOR_PROPERTY = "__electronic_fence_color";
2902
+ const OUTLINE_COLOR_PROPERTY = "__electronic_fence_outline_color";
2903
+ const OUTLINE_WIDTH_PROPERTY = "__electronic_fence_outline_width";
2904
+ const BASE_PROPERTY = "__electronic_fence_base";
2905
+ const TOP_PROPERTY = "__electronic_fence_top";
2906
+ const DEFAULT_STYLES$1 = {
2907
+ normal: {
2908
+ color: "#d8b4fe",
2909
+ opacity: 0.28,
2910
+ outlineColor: "#f5e9ff",
2911
+ outlineWidth: 1.5
2912
+ },
2913
+ active: {
2914
+ color: "#c084fc",
2915
+ opacity: 0.42,
2916
+ outlineColor: "#ffffff",
2917
+ outlineWidth: 2.5
2918
+ },
2919
+ warning: {
2920
+ color: "#f59e0b",
2921
+ opacity: 0.38,
2922
+ outlineColor: "#fde68a",
2923
+ outlineWidth: 2
2924
+ },
2925
+ alarm: {
2926
+ color: "#ef4444",
2927
+ opacity: 0.48,
2928
+ outlineColor: "#ffffff",
2929
+ outlineWidth: 2.5
2930
+ },
2931
+ disabled: {
2932
+ color: "#94a3b8",
2933
+ opacity: 0.16,
2934
+ outlineColor: "#cbd5e1",
2935
+ outlineWidth: 1
2936
+ }
2937
+ };
2938
+ const buildStandardFenceData = (store, options) => {
2939
+ const features = [];
2940
+ const dimensions = resolveFenceDimensions(options.defaults);
2941
+ store.visibleValues().forEach((feature) => {
2942
+ const state = resolveFenceVisualState(
2943
+ store.getState(feature.id),
2944
+ store.isActive(feature.id)
2945
+ );
2946
+ const style = resolveFenceStyle({
2947
+ defaults: DEFAULT_STYLES$1,
2948
+ overrides: options.styles,
2949
+ state
2950
+ });
2951
+ const segments = createFenceSegments(feature.geometry, dimensions.thickness);
2952
+ const commonProperties = {
2953
+ [FENCE_ID_PROPERTY]: feature.id,
2954
+ [COLOR_PROPERTY]: toFenceRgbaCss(
2955
+ style.color,
2956
+ clampFenceNumber(style.opacity, 0, 1)
2957
+ ),
2958
+ [OUTLINE_COLOR_PROPERTY]: toFenceRgbaCss(style.outlineColor),
2959
+ [OUTLINE_WIDTH_PROPERTY]: Math.max(0, style.outlineWidth),
2960
+ [BASE_PROPERTY]: dimensions.baseHeight,
2961
+ [TOP_PROPERTY]: dimensions.baseHeight + dimensions.height
2962
+ };
2963
+ if (segments.length) {
2964
+ features.push({
2965
+ type: "Feature",
2966
+ id: `${feature.id}:wall`,
2967
+ geometry: createFenceWallMultiPolygon(segments),
2968
+ properties: {
2969
+ ...commonProperties,
2970
+ [KIND_PROPERTY]: "wall"
2971
+ }
2972
+ });
2973
+ }
2974
+ features.push({
2975
+ type: "Feature",
2976
+ id: `${feature.id}:boundary`,
2977
+ geometry: feature.geometry,
2978
+ properties: {
2979
+ ...commonProperties,
2980
+ [KIND_PROPERTY]: "boundary"
2981
+ }
2982
+ });
2983
+ });
2984
+ return {
2985
+ type: "FeatureCollection",
2986
+ features
2987
+ };
2988
+ };
2989
+ const addStandardElectronicFenceLayer = (map, options) => {
2990
+ validateFenceDefaults(options.id, options.defaults);
2991
+ const store = new ElectronicFenceStore();
2992
+ const sourceId = `${options.id}-standard-source`;
2993
+ const extrusionLayerId = `${options.id}-standard-extrusion`;
2994
+ const outlineLayerId = `${options.id}-standard-outline`;
2995
+ const interactionLayerId = `${options.id}-standard-interaction`;
2996
+ const layerIds = [
2997
+ extrusionLayerId,
2998
+ outlineLayerId,
2999
+ ...options.interactive === false ? [] : [interactionLayerId]
3000
+ ];
3001
+ let data = buildStandardFenceData(store, options);
3002
+ let visible = options.visible !== false;
3003
+ let destroyed = false;
3004
+ const ensureLayers = () => {
3005
+ if (destroyed) {
3006
+ return;
3007
+ }
3008
+ try {
3009
+ if (!map.getSource(sourceId)) {
3010
+ map.addSource(sourceId, {
3011
+ type: "geojson",
3012
+ data
3013
+ });
3014
+ }
3015
+ if (!map.getLayer(extrusionLayerId)) {
3016
+ map.addLayer(
3017
+ {
3018
+ id: extrusionLayerId,
3019
+ type: "fill-extrusion",
3020
+ source: sourceId,
3021
+ filter: ["==", ["get", KIND_PROPERTY], "wall"],
3022
+ paint: {
3023
+ "fill-extrusion-color": ["get", COLOR_PROPERTY],
3024
+ "fill-extrusion-opacity": 1,
3025
+ "fill-extrusion-base": ["get", BASE_PROPERTY],
3026
+ "fill-extrusion-height": ["get", TOP_PROPERTY],
3027
+ "fill-extrusion-vertical-gradient": true
3028
+ }
3029
+ },
3030
+ options.beforeId
3031
+ );
3032
+ }
3033
+ if (!map.getLayer(outlineLayerId)) {
3034
+ map.addLayer(
3035
+ {
3036
+ id: outlineLayerId,
3037
+ type: "line",
3038
+ source: sourceId,
3039
+ filter: ["==", ["get", KIND_PROPERTY], "boundary"],
3040
+ paint: {
3041
+ "line-color": ["get", OUTLINE_COLOR_PROPERTY],
3042
+ "line-width": ["get", OUTLINE_WIDTH_PROPERTY],
3043
+ "line-opacity": 1
3044
+ }
3045
+ },
3046
+ options.beforeId
3047
+ );
3048
+ }
3049
+ if (options.interactive !== false && !map.getLayer(interactionLayerId)) {
3050
+ map.addLayer(
3051
+ {
3052
+ id: interactionLayerId,
3053
+ type: "fill",
3054
+ source: sourceId,
3055
+ filter: ["==", ["get", KIND_PROPERTY], "boundary"],
3056
+ paint: {
3057
+ "fill-color": "#000000",
3058
+ "fill-opacity": 0
3059
+ }
3060
+ },
3061
+ options.beforeId
3062
+ );
3063
+ }
3064
+ setFenceLayerVisibility(map, layerIds, visible);
3065
+ } catch (error) {
3066
+ if (!isStyleNotReadyError(error)) {
3067
+ throw error;
3068
+ }
3069
+ }
3070
+ };
3071
+ const refresh = () => {
3072
+ data = buildStandardFenceData(store, options);
3073
+ const source = map.getSource(sourceId);
3074
+ if (source) {
3075
+ source.setData(data);
3076
+ } else {
3077
+ ensureLayers();
3078
+ }
3079
+ };
3080
+ const handleStyleData = () => {
3081
+ ensureLayers();
3082
+ };
3083
+ map.on("styledata", handleStyleData);
3084
+ ensureLayers();
3085
+ const unbindInteractions = options.interactive === false ? () => void 0 : bindFenceInteractions({
3086
+ map,
3087
+ instanceId: options.id,
3088
+ renderer: "standard",
3089
+ layerId: interactionLayerId,
3090
+ store
3091
+ });
3092
+ return createElectronicFenceController({
3093
+ id: options.id,
3094
+ renderer: "standard",
3095
+ store,
3096
+ refresh,
3097
+ setPluginVisible(nextVisible) {
3098
+ visible = nextVisible;
3099
+ ensureLayers();
3100
+ setFenceLayerVisibility(map, layerIds, visible);
3101
+ },
3102
+ destroy() {
3103
+ destroyed = true;
3104
+ map.off("styledata", handleStyleData);
3105
+ unbindInteractions();
3106
+ [...layerIds].reverse().forEach((layerId) => {
3107
+ if (map.getLayer(layerId)) {
3108
+ map.removeLayer(layerId);
3109
+ }
3110
+ });
3111
+ if (map.getSource(sourceId)) {
3112
+ map.removeSource(sourceId);
3113
+ }
3114
+ }
3115
+ });
3116
+ };
3117
+ const FLOATS_PER_VERTEX = 19;
3118
+ const GLOW_LAYERS = [
3119
+ { thicknessScale: 0.08, opacity: 1 },
3120
+ { thicknessScale: 0.22, opacity: 0.56 },
3121
+ { thicknessScale: 0.45, opacity: 0.28 },
3122
+ { thicknessScale: 0.8, opacity: 0.12 },
3123
+ { thicknessScale: 1.3, opacity: 0.045 }
3124
+ ];
3125
+ const DEFAULT_STYLES = {
3126
+ normal: {
3127
+ color: "#d8b4fe",
3128
+ opacity: 0.2,
3129
+ outlineColor: "#f5e9ff",
3130
+ outlineWidth: 1.5,
3131
+ glowStrength: 1,
3132
+ flowColor: "#ffffff",
3133
+ flowCount: 3,
3134
+ flowLineStyle: "dashed",
3135
+ flowWidth: 0.18
3136
+ },
3137
+ active: {
3138
+ color: "#e9d5ff",
3139
+ opacity: 0.28,
3140
+ outlineColor: "#ffffff",
3141
+ outlineWidth: 2.5,
3142
+ glowStrength: 1.25,
3143
+ flowColor: "#ffffff",
3144
+ flowCount: 3,
3145
+ flowLineStyle: "dashed",
3146
+ flowWidth: 0.2
3147
+ },
3148
+ warning: {
3149
+ color: "#f59e0b",
3150
+ opacity: 0.25,
3151
+ outlineColor: "#fde68a",
3152
+ outlineWidth: 2,
3153
+ glowStrength: 1.1,
3154
+ flowColor: "#ffffff",
3155
+ flowCount: 4,
3156
+ flowLineStyle: "dashed",
3157
+ flowWidth: 0.18
3158
+ },
3159
+ alarm: {
3160
+ color: "#ef4444",
3161
+ opacity: 0.3,
3162
+ outlineColor: "#ffffff",
3163
+ outlineWidth: 2.5,
3164
+ glowStrength: 1.35,
3165
+ flowColor: "#ffffff",
3166
+ flowCount: 4,
3167
+ flowLineStyle: "dashed",
3168
+ flowWidth: 0.2
3169
+ },
3170
+ disabled: {
3171
+ color: "#94a3b8",
3172
+ opacity: 0.12,
3173
+ outlineColor: "#cbd5e1",
3174
+ outlineWidth: 1,
3175
+ glowStrength: 0,
3176
+ flowColor: "#ffffff",
3177
+ flowCount: 0,
3178
+ flowLineStyle: "dashed",
3179
+ flowWidth: 0
3180
+ }
3181
+ };
3182
+ const resolveAnimationOptions = (options) => ({
3183
+ enabled: (options == null ? void 0 : options.enabled) ?? true,
3184
+ fps: Math.round(clampFenceNumber((options == null ? void 0 : options.fps) ?? 30, 1, 60)),
3185
+ speed: Math.max(0, (options == null ? void 0 : options.speed) ?? 0.35),
3186
+ scope: (options == null ? void 0 : options.scope) ?? "all"
3187
+ });
3188
+ const toMercator = (lngLat, altitude) => {
3189
+ const coordinate = maplibregl.MercatorCoordinate.fromLngLat(lngLat, altitude);
3190
+ return [coordinate.x, coordinate.y, coordinate.z];
3191
+ };
3192
+ const subtract = (a, b) => [
3193
+ a[0] - b[0],
3194
+ a[1] - b[1],
3195
+ a[2] - b[2]
3196
+ ];
3197
+ const normalize = (vector) => {
3198
+ const length = Math.hypot(vector[0], vector[1], vector[2]) || 1;
3199
+ return [vector[0] / length, vector[1] / length, vector[2] / length];
3200
+ };
3201
+ const getNormal = (a, b, c) => {
3202
+ const ab = subtract(b, a);
3203
+ const ac = subtract(c, a);
3204
+ return normalize([
3205
+ ab[1] * ac[2] - ab[2] * ac[1],
3206
+ ab[2] * ac[0] - ab[0] * ac[2],
3207
+ ab[0] * ac[1] - ab[1] * ac[0]
3208
+ ]);
3209
+ };
3210
+ const pushVertex = (target, position, normal, heightRatio, pathProgress, style) => {
3211
+ target.push(
3212
+ ...position,
3213
+ ...normal,
3214
+ heightRatio,
3215
+ pathProgress,
3216
+ ...style.color,
3217
+ style.flowColor[0],
3218
+ style.flowColor[1],
3219
+ style.flowColor[2],
3220
+ style.glowStrength,
3221
+ style.flowWidth,
3222
+ style.flowCount,
3223
+ style.flowLineStyle
3224
+ );
3225
+ };
3226
+ const pushQuad = (target, positions, heightRatios, pathProgresses, style) => {
3227
+ const normal = getNormal(positions[0], positions[1], positions[2]);
3228
+ const indices = [0, 1, 2, 0, 2, 3];
3229
+ indices.forEach((index) => {
3230
+ pushVertex(
3231
+ target,
3232
+ positions[index],
3233
+ normal,
3234
+ heightRatios[index],
3235
+ pathProgresses[index],
3236
+ style
3237
+ );
3238
+ });
3239
+ };
3240
+ const appendSegmentPrism = (target, segment, baseHeight, height, style) => {
3241
+ const topHeight = baseHeight + height;
3242
+ const startLeftBottom = toMercator(segment.startLeft, baseHeight);
3243
+ const startRightBottom = toMercator(segment.startRight, baseHeight);
3244
+ const endLeftBottom = toMercator(segment.endLeft, baseHeight);
3245
+ const endRightBottom = toMercator(segment.endRight, baseHeight);
3246
+ const startLeftTop = toMercator(segment.startLeft, topHeight);
3247
+ const startRightTop = toMercator(segment.startRight, topHeight);
3248
+ const endLeftTop = toMercator(segment.endLeft, topHeight);
3249
+ const endRightTop = toMercator(segment.endRight, topHeight);
3250
+ pushQuad(
3251
+ target,
3252
+ [startLeftTop, endLeftTop, endRightTop, startRightTop],
3253
+ [1, 1, 1, 1],
3254
+ [
3255
+ segment.startProgress,
3256
+ segment.endProgress,
3257
+ segment.endProgress,
3258
+ segment.startProgress
3259
+ ],
3260
+ style
3261
+ );
3262
+ pushQuad(
3263
+ target,
3264
+ [startLeftBottom, endLeftBottom, endLeftTop, startLeftTop],
3265
+ [0, 0, 1, 1],
3266
+ [
3267
+ segment.startProgress,
3268
+ segment.endProgress,
3269
+ segment.endProgress,
3270
+ segment.startProgress
3271
+ ],
3272
+ style
3273
+ );
3274
+ pushQuad(
3275
+ target,
3276
+ [endRightBottom, startRightBottom, startRightTop, endRightTop],
3277
+ [0, 0, 1, 1],
3278
+ [
3279
+ segment.endProgress,
3280
+ segment.startProgress,
3281
+ segment.startProgress,
3282
+ segment.endProgress
3283
+ ],
3284
+ style
3285
+ );
3286
+ };
3287
+ const isHighlighted = (state) => state === "active" || state === "warning" || state === "alarm";
3288
+ const createVertexStyle = (style, flowEnabled) => {
3289
+ const color = parseFenceColor(style.color);
3290
+ const flowColor = parseFenceColor(style.flowColor);
3291
+ const flowCount = flowEnabled && style.flowCount > 0 ? Math.round(clampFenceNumber(style.flowCount, 1, 8)) : 0;
3292
+ return {
3293
+ color: [
3294
+ color[0],
3295
+ color[1],
3296
+ color[2],
3297
+ clampFenceNumber(color[3] * style.opacity, 0, 1)
3298
+ ],
3299
+ flowColor,
3300
+ glowStrength: clampFenceNumber(style.glowStrength, 0, 4),
3301
+ flowCount,
3302
+ flowLineStyle: style.flowLineStyle === "solid" ? 0 : 1,
3303
+ flowWidth: flowCount > 0 ? clampFenceNumber(style.flowWidth, 0.01, 0.49) : 0
3304
+ };
3305
+ };
3306
+ const buildWebGLFenceGeometry = (store, options, animation) => {
3307
+ const glowVertexLayers = GLOW_LAYERS.map(() => []);
3308
+ const bodyVertices = [];
3309
+ const dimensions = resolveFenceDimensions(options.defaults);
3310
+ let animated = false;
3311
+ store.visibleValues().forEach((feature) => {
3312
+ const active = store.isActive(feature.id);
3313
+ const state = resolveFenceVisualState(store.getState(feature.id), active);
3314
+ const style = resolveFenceStyle({
3315
+ defaults: DEFAULT_STYLES,
3316
+ overrides: options.styles,
3317
+ state
3318
+ });
3319
+ const flowEnabled = animation.enabled && (animation.scope === "all" || isHighlighted(state));
3320
+ const vertexStyle = createVertexStyle(style, flowEnabled);
3321
+ const bodySegments = createFenceSegments(
3322
+ feature.geometry,
3323
+ dimensions.thickness
3324
+ );
3325
+ bodySegments.forEach((segment) => {
3326
+ appendSegmentPrism(
3327
+ bodyVertices,
3328
+ segment,
3329
+ dimensions.baseHeight,
3330
+ dimensions.height,
3331
+ vertexStyle
3332
+ );
3333
+ });
3334
+ if (vertexStyle.glowStrength > 0) {
3335
+ GLOW_LAYERS.forEach((glowLayer, index) => {
3336
+ const glowThickness = dimensions.thickness * (1 + Math.min(vertexStyle.glowStrength, 2) * glowLayer.thicknessScale);
3337
+ const glowSegments = createFenceSegments(
3338
+ feature.geometry,
3339
+ glowThickness
3340
+ );
3341
+ glowSegments.forEach((segment) => {
3342
+ appendSegmentPrism(
3343
+ glowVertexLayers[index],
3344
+ segment,
3345
+ dimensions.baseHeight,
3346
+ dimensions.height,
3347
+ vertexStyle
3348
+ );
3349
+ });
3350
+ });
3351
+ }
3352
+ animated || (animated = vertexStyle.flowCount > 0 && vertexStyle.flowWidth > 0);
3353
+ });
3354
+ const glowFloatCount = glowVertexLayers.reduce(
3355
+ (sum, layerVertices) => sum + layerVertices.length,
3356
+ 0
3357
+ );
3358
+ const vertices = new Float32Array(glowFloatCount + bodyVertices.length);
3359
+ const glowPasses = [];
3360
+ let floatOffset = 0;
3361
+ glowVertexLayers.forEach((layerVertices, index) => {
3362
+ const count = layerVertices.length / FLOATS_PER_VERTEX;
3363
+ if (count > 0) {
3364
+ glowPasses.push({
3365
+ first: floatOffset / FLOATS_PER_VERTEX,
3366
+ count,
3367
+ opacity: GLOW_LAYERS[index].opacity
3368
+ });
3369
+ }
3370
+ vertices.set(layerVertices, floatOffset);
3371
+ floatOffset += layerVertices.length;
3372
+ });
3373
+ const bodyFirst = floatOffset / FLOATS_PER_VERTEX;
3374
+ vertices.set(bodyVertices, floatOffset);
3375
+ return {
3376
+ vertices,
3377
+ glowPasses,
3378
+ bodyFirst,
3379
+ bodyVertexCount: bodyVertices.length / FLOATS_PER_VERTEX,
3380
+ animated
3381
+ };
3382
+ };
3383
+ const createShader = (gl, type, source) => {
3384
+ const shader = gl.createShader(type);
3385
+ if (!shader) {
3386
+ throw new Error("无法创建电子围栏 WebGL shader。");
3387
+ }
3388
+ gl.shaderSource(shader, source);
3389
+ gl.compileShader(shader);
3390
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
3391
+ const message = gl.getShaderInfoLog(shader) || "未知 shader 编译错误";
3392
+ gl.deleteShader(shader);
3393
+ throw new Error(`电子围栏 WebGL shader 编译失败:${message}`);
3394
+ }
3395
+ return shader;
3396
+ };
3397
+ const createProgram = (gl) => {
3398
+ const isWebGL2 = typeof WebGL2RenderingContext !== "undefined" && gl instanceof WebGL2RenderingContext;
3399
+ const vertexSource = isWebGL2 ? `#version 300 es
3400
+ precision highp float;
3401
+ in vec3 a_position;
3402
+ in vec3 a_normal;
3403
+ in float a_height_ratio;
3404
+ in float a_path_progress;
3405
+ in vec4 a_color;
3406
+ in vec3 a_flow_color;
3407
+ in float a_glow_strength;
3408
+ in float a_flow_width;
3409
+ in float a_flow_count;
3410
+ in float a_flow_line_style;
3411
+ uniform mat4 u_matrix;
3412
+ out vec3 v_normal;
3413
+ out float v_height_ratio;
3414
+ out float v_path_progress;
3415
+ out vec4 v_color;
3416
+ out vec3 v_flow_color;
3417
+ out float v_glow_strength;
3418
+ out float v_flow_width;
3419
+ out float v_flow_count;
3420
+ out float v_flow_line_style;
3421
+ void main() {
3422
+ gl_Position = u_matrix * vec4(a_position, 1.0);
3423
+ v_normal = a_normal;
3424
+ v_height_ratio = a_height_ratio;
3425
+ v_path_progress = a_path_progress;
3426
+ v_color = a_color;
3427
+ v_flow_color = a_flow_color;
3428
+ v_glow_strength = a_glow_strength;
3429
+ v_flow_width = a_flow_width;
3430
+ v_flow_count = a_flow_count;
3431
+ v_flow_line_style = a_flow_line_style;
3432
+ }
3433
+ ` : `
3434
+ precision highp float;
3435
+ attribute vec3 a_position;
3436
+ attribute vec3 a_normal;
3437
+ attribute float a_height_ratio;
3438
+ attribute float a_path_progress;
3439
+ attribute vec4 a_color;
3440
+ attribute vec3 a_flow_color;
3441
+ attribute float a_glow_strength;
3442
+ attribute float a_flow_width;
3443
+ attribute float a_flow_count;
3444
+ attribute float a_flow_line_style;
3445
+ uniform mat4 u_matrix;
3446
+ varying vec3 v_normal;
3447
+ varying float v_height_ratio;
3448
+ varying float v_path_progress;
3449
+ varying vec4 v_color;
3450
+ varying vec3 v_flow_color;
3451
+ varying float v_glow_strength;
3452
+ varying float v_flow_width;
3453
+ varying float v_flow_count;
3454
+ varying float v_flow_line_style;
3455
+ void main() {
3456
+ gl_Position = u_matrix * vec4(a_position, 1.0);
3457
+ v_normal = a_normal;
3458
+ v_height_ratio = a_height_ratio;
3459
+ v_path_progress = a_path_progress;
3460
+ v_color = a_color;
3461
+ v_flow_color = a_flow_color;
3462
+ v_glow_strength = a_glow_strength;
3463
+ v_flow_width = a_flow_width;
3464
+ v_flow_count = a_flow_count;
3465
+ v_flow_line_style = a_flow_line_style;
3466
+ }
3467
+ `;
3468
+ const fragmentBody = `
3469
+ precision highp float;
3470
+ uniform float u_time;
3471
+ uniform float u_speed;
3472
+ uniform float u_animation_enabled;
3473
+ uniform float u_glow_pass;
3474
+ uniform float u_glow_opacity;
3475
+ VARYING vec3 v_normal;
3476
+ VARYING float v_height_ratio;
3477
+ VARYING float v_path_progress;
3478
+ VARYING vec4 v_color;
3479
+ VARYING vec3 v_flow_color;
3480
+ VARYING float v_glow_strength;
3481
+ VARYING float v_flow_width;
3482
+ VARYING float v_flow_count;
3483
+ VARYING float v_flow_line_style;
3484
+ void main() {
3485
+ float bandCore = 0.0;
3486
+ float bandGlow = 0.0;
3487
+ float verticalFace = 1.0 - smoothstep(
3488
+ 0.82,
3489
+ 0.98,
3490
+ abs(normalize(v_normal).z)
3491
+ );
3492
+
3493
+ if (
3494
+ u_animation_enabled > 0.5 &&
3495
+ v_flow_count > 0.5 &&
3496
+ v_flow_width > 0.0001
3497
+ ) {
3498
+ float count = max(v_flow_count, 1.0);
3499
+ float phase = fract(
3500
+ v_height_ratio * count + u_time * u_speed * count
3501
+ );
3502
+ float bandDistance = abs(phase - 0.5);
3503
+ float dashPhase = fract(
3504
+ v_path_progress * 36.0 - u_time * u_speed * 2.4
3505
+ );
3506
+ float dash = smoothstep(0.02, 0.1, dashPhase) *
3507
+ (1.0 - smoothstep(0.62, 0.74, dashPhase));
3508
+ float linePattern = v_flow_line_style > 0.5 ? dash : 1.0;
3509
+
3510
+ bandCore = 1.0 - smoothstep(
3511
+ max(v_flow_width * 0.08, 0.001),
3512
+ max(v_flow_width * 0.42, 0.003),
3513
+ bandDistance
3514
+ );
3515
+ float glowDistance = bandDistance / max(v_flow_width, 0.001);
3516
+ bandGlow = exp(
3517
+ -glowDistance * glowDistance * 2.6
3518
+ );
3519
+ bandCore *= linePattern * verticalFace;
3520
+ bandGlow *= verticalFace;
3521
+ }
3522
+
3523
+ if (u_glow_pass > 0.5) {
3524
+ float glowAlpha = clamp(
3525
+ (v_color.a * 0.003 + bandGlow * 0.085) *
3526
+ v_glow_strength * u_glow_opacity,
3527
+ 0.0,
3528
+ 0.14
3529
+ );
3530
+ OUTPUT_COLOR = vec4(v_color.rgb, glowAlpha);
3531
+ return;
3532
+ }
3533
+
3534
+ float faceLight = 0.88 + 0.12 * abs(normalize(v_normal).z);
3535
+ vec3 bodyColor = v_color.rgb * faceLight;
3536
+ vec3 color = mix(bodyColor, v_flow_color, bandCore);
3537
+ float alpha = clamp(
3538
+ v_color.a + bandCore * 0.68,
3539
+ 0.0,
3540
+ 1.0
3541
+ );
3542
+ OUTPUT_COLOR = vec4(color, alpha);
3543
+ }
3544
+ `;
3545
+ const replaceShaderToken = (source, token, value) => source.split(token).join(value);
3546
+ const fragmentSource = isWebGL2 ? `#version 300 es
3547
+ ${replaceShaderToken(
3548
+ replaceShaderToken(fragmentBody, "VARYING", "in"),
3549
+ "OUTPUT_COLOR",
3550
+ "fragmentColor"
3551
+ ).replace(
3552
+ "precision highp float;",
3553
+ "precision highp float;\n out vec4 fragmentColor;"
3554
+ )}
3555
+ ` : replaceShaderToken(
3556
+ replaceShaderToken(fragmentBody, "VARYING", "varying"),
3557
+ "OUTPUT_COLOR",
3558
+ "gl_FragColor"
3559
+ );
3560
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
3561
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
3562
+ const program = gl.createProgram();
3563
+ if (!program) {
3564
+ gl.deleteShader(vertexShader);
3565
+ gl.deleteShader(fragmentShader);
3566
+ throw new Error("无法创建电子围栏 WebGL program。");
3567
+ }
3568
+ gl.attachShader(program, vertexShader);
3569
+ gl.attachShader(program, fragmentShader);
3570
+ gl.linkProgram(program);
3571
+ gl.deleteShader(vertexShader);
3572
+ gl.deleteShader(fragmentShader);
3573
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
3574
+ const message = gl.getProgramInfoLog(program) || "未知 program 链接错误";
3575
+ gl.deleteProgram(program);
3576
+ throw new Error(`电子围栏 WebGL program 链接失败:${message}`);
3577
+ }
3578
+ return program;
3579
+ };
3580
+ const requireUniform = (gl, program, name) => {
3581
+ const location = gl.getUniformLocation(program, name);
3582
+ if (!location) {
3583
+ throw new Error(`电子围栏 WebGL uniform 不存在:${name}`);
3584
+ }
3585
+ return location;
3586
+ };
3587
+ const createLocations = (gl, program) => ({
3588
+ position: gl.getAttribLocation(program, "a_position"),
3589
+ normal: gl.getAttribLocation(program, "a_normal"),
3590
+ heightRatio: gl.getAttribLocation(program, "a_height_ratio"),
3591
+ pathProgress: gl.getAttribLocation(program, "a_path_progress"),
3592
+ color: gl.getAttribLocation(program, "a_color"),
3593
+ flowColor: gl.getAttribLocation(program, "a_flow_color"),
3594
+ glowStrength: gl.getAttribLocation(program, "a_glow_strength"),
3595
+ flowWidth: gl.getAttribLocation(program, "a_flow_width"),
3596
+ flowCount: gl.getAttribLocation(program, "a_flow_count"),
3597
+ flowLineStyle: gl.getAttribLocation(program, "a_flow_line_style"),
3598
+ matrix: requireUniform(gl, program, "u_matrix"),
3599
+ time: requireUniform(gl, program, "u_time"),
3600
+ speed: requireUniform(gl, program, "u_speed"),
3601
+ animationEnabled: requireUniform(gl, program, "u_animation_enabled"),
3602
+ glowPass: requireUniform(gl, program, "u_glow_pass"),
3603
+ glowOpacity: requireUniform(gl, program, "u_glow_opacity")
3604
+ });
3605
+ class WebGLElectronicFenceCustomLayer {
3606
+ constructor(id, animation) {
3607
+ this.type = "custom";
3608
+ this.renderingMode = "3d";
3609
+ this.geometry = {
3610
+ vertices: new Float32Array(),
3611
+ glowPasses: [],
3612
+ bodyFirst: 0,
3613
+ bodyVertexCount: 0,
3614
+ animated: false
3615
+ };
3616
+ this.visible = true;
3617
+ this.id = id;
3618
+ this.animation = animation;
3619
+ }
3620
+ setGeometry(geometry) {
3621
+ var _a;
3622
+ this.geometry = geometry;
3623
+ this.uploadGeometry();
3624
+ (_a = this.map) == null ? void 0 : _a.triggerRepaint();
3625
+ }
3626
+ setVisible(visible) {
3627
+ var _a;
3628
+ this.visible = visible;
3629
+ (_a = this.map) == null ? void 0 : _a.triggerRepaint();
3630
+ }
3631
+ onAdd(map, gl) {
3632
+ this.map = map;
3633
+ this.gl = gl;
3634
+ this.program = createProgram(gl);
3635
+ this.locations = createLocations(gl, this.program);
3636
+ this.buffer = gl.createBuffer() || void 0;
3637
+ if (!this.buffer) {
3638
+ throw new Error("无法创建电子围栏 WebGL 顶点缓冲。");
3639
+ }
3640
+ this.uploadGeometry(gl);
3641
+ }
3642
+ render(gl, input) {
3643
+ if (!this.visible || !this.program || !this.buffer || !this.locations || !this.geometry.glowPasses.length && !this.geometry.bodyVertexCount) {
3644
+ return;
3645
+ }
3646
+ const stride = FLOATS_PER_VERTEX * Float32Array.BYTES_PER_ELEMENT;
3647
+ const pointer = (location, size, offset) => {
3648
+ gl.enableVertexAttribArray(location);
3649
+ gl.vertexAttribPointer(
3650
+ location,
3651
+ size,
3652
+ gl.FLOAT,
3653
+ false,
3654
+ stride,
3655
+ offset * Float32Array.BYTES_PER_ELEMENT
3656
+ );
3657
+ };
3658
+ gl.useProgram(this.program);
3659
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
3660
+ pointer(this.locations.position, 3, 0);
3661
+ pointer(this.locations.normal, 3, 3);
3662
+ pointer(this.locations.heightRatio, 1, 6);
3663
+ pointer(this.locations.pathProgress, 1, 7);
3664
+ pointer(this.locations.color, 4, 8);
3665
+ pointer(this.locations.flowColor, 3, 12);
3666
+ pointer(this.locations.glowStrength, 1, 15);
3667
+ pointer(this.locations.flowWidth, 1, 16);
3668
+ pointer(this.locations.flowCount, 1, 17);
3669
+ pointer(this.locations.flowLineStyle, 1, 18);
3670
+ gl.uniformMatrix4fv(
3671
+ this.locations.matrix,
3672
+ false,
3673
+ input.defaultProjectionData.mainMatrix
3674
+ );
3675
+ gl.uniform1f(this.locations.time, performance.now() / 1e3);
3676
+ gl.uniform1f(this.locations.speed, this.animation.speed);
3677
+ gl.uniform1f(
3678
+ this.locations.animationEnabled,
3679
+ this.animation.enabled ? 1 : 0
3680
+ );
3681
+ gl.enable(gl.BLEND);
3682
+ gl.enable(gl.DEPTH_TEST);
3683
+ gl.disable(gl.CULL_FACE);
3684
+ gl.depthMask(false);
3685
+ if (this.geometry.glowPasses.length) {
3686
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
3687
+ gl.uniform1f(this.locations.glowPass, 1);
3688
+ this.geometry.glowPasses.forEach((glowPass) => {
3689
+ gl.uniform1f(this.locations.glowOpacity, glowPass.opacity);
3690
+ gl.drawArrays(gl.TRIANGLES, glowPass.first, glowPass.count);
3691
+ });
3692
+ }
3693
+ if (this.geometry.bodyVertexCount) {
3694
+ gl.blendFuncSeparate(
3695
+ gl.SRC_ALPHA,
3696
+ gl.ONE_MINUS_SRC_ALPHA,
3697
+ gl.ONE,
3698
+ gl.ONE_MINUS_SRC_ALPHA
3699
+ );
3700
+ gl.uniform1f(this.locations.glowPass, 0);
3701
+ gl.uniform1f(this.locations.glowOpacity, 0);
3702
+ gl.drawArrays(
3703
+ gl.TRIANGLES,
3704
+ this.geometry.bodyFirst,
3705
+ this.geometry.bodyVertexCount
3706
+ );
3707
+ }
3708
+ gl.depthMask(true);
3709
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
3710
+ }
3711
+ onRemove(_map, gl) {
3712
+ if (this.buffer) {
3713
+ gl.deleteBuffer(this.buffer);
3714
+ }
3715
+ if (this.program) {
3716
+ gl.deleteProgram(this.program);
3717
+ }
3718
+ this.map = void 0;
3719
+ this.gl = void 0;
3720
+ this.buffer = void 0;
3721
+ this.program = void 0;
3722
+ this.locations = void 0;
3723
+ }
3724
+ uploadGeometry(providedGl) {
3725
+ const gl = providedGl || this.gl;
3726
+ if (!gl || !this.buffer) {
3727
+ return;
3728
+ }
3729
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
3730
+ gl.bufferData(gl.ARRAY_BUFFER, this.geometry.vertices, gl.STATIC_DRAW);
3731
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
3732
+ }
3733
+ }
3734
+ const addWebGLElectronicFenceLayer = (map, options) => {
3735
+ validateFenceDefaults(options.id, options.defaults);
3736
+ const animation = resolveAnimationOptions(options.animation);
3737
+ const store = new ElectronicFenceStore();
3738
+ const customLayerId = `${options.id}-webgl`;
3739
+ const interactionSourceId = `${options.id}-webgl-interaction-source`;
3740
+ const interactionLayerId = `${options.id}-webgl-interaction`;
3741
+ const customLayer = new WebGLElectronicFenceCustomLayer(
3742
+ customLayerId,
3743
+ animation
3744
+ );
3745
+ let interactionData = createFenceInteractionData(store);
3746
+ let geometry = buildWebGLFenceGeometry(store, options, animation);
3747
+ let visible = options.visible !== false;
3748
+ let destroyed = false;
3749
+ let animationFrame = 0;
3750
+ let lastRepaintTime = 0;
3751
+ const stopAnimation = () => {
3752
+ if (animationFrame) {
3753
+ cancelAnimationFrame(animationFrame);
3754
+ animationFrame = 0;
3755
+ }
3756
+ };
3757
+ const tick = (timestamp) => {
3758
+ if (destroyed || !visible || !geometry.animated) {
3759
+ animationFrame = 0;
3760
+ return;
3761
+ }
3762
+ if ((typeof document === "undefined" || !document.hidden) && timestamp - lastRepaintTime >= 1e3 / animation.fps) {
3763
+ lastRepaintTime = timestamp;
3764
+ map.triggerRepaint();
3765
+ }
3766
+ animationFrame = requestAnimationFrame(tick);
3767
+ };
3768
+ const updateAnimation = () => {
3769
+ const shouldAnimate = animation.enabled && visible && geometry.animated && !!map.getLayer(customLayerId);
3770
+ if (shouldAnimate && !animationFrame) {
3771
+ lastRepaintTime = 0;
3772
+ animationFrame = requestAnimationFrame(tick);
3773
+ } else if (!shouldAnimate) {
3774
+ stopAnimation();
3775
+ }
3776
+ };
3777
+ const ensureLayers = () => {
3778
+ if (destroyed) {
3779
+ return;
3780
+ }
3781
+ try {
3782
+ if (options.interactive !== false && !map.getSource(interactionSourceId)) {
3783
+ map.addSource(interactionSourceId, {
3784
+ type: "geojson",
3785
+ data: interactionData
3786
+ });
3787
+ }
3788
+ if (!map.getLayer(customLayerId)) {
3789
+ map.addLayer(customLayer, options.beforeId);
3790
+ }
3791
+ if (options.interactive !== false && !map.getLayer(interactionLayerId)) {
3792
+ map.addLayer(
3793
+ {
3794
+ id: interactionLayerId,
3795
+ type: "fill",
3796
+ source: interactionSourceId,
3797
+ paint: {
3798
+ "fill-color": "#000000",
3799
+ "fill-opacity": 0
3800
+ }
3801
+ },
3802
+ options.beforeId
3803
+ );
3804
+ }
3805
+ customLayer.setVisible(visible);
3806
+ setFenceLayerVisibility(
3807
+ map,
3808
+ options.interactive === false ? [] : [interactionLayerId],
3809
+ visible
3810
+ );
3811
+ updateAnimation();
3812
+ } catch (error) {
3813
+ if (!isStyleNotReadyError(error)) {
3814
+ throw error;
3815
+ }
3816
+ }
3817
+ };
3818
+ const refresh = () => {
3819
+ geometry = buildWebGLFenceGeometry(store, options, animation);
3820
+ interactionData = createFenceInteractionData(store);
3821
+ customLayer.setGeometry(geometry);
3822
+ const source = map.getSource(interactionSourceId);
3823
+ if (source) {
3824
+ source.setData(interactionData);
3825
+ }
3826
+ ensureLayers();
3827
+ updateAnimation();
3828
+ };
3829
+ const handleStyleData = () => {
3830
+ ensureLayers();
3831
+ };
3832
+ customLayer.setGeometry(geometry);
3833
+ map.on("styledata", handleStyleData);
3834
+ ensureLayers();
3835
+ const unbindInteractions = options.interactive === false ? () => void 0 : bindFenceInteractions({
3836
+ map,
3837
+ instanceId: options.id,
3838
+ renderer: "webgl",
3839
+ layerId: interactionLayerId,
3840
+ store
3841
+ });
3842
+ return createElectronicFenceController({
3843
+ id: options.id,
3844
+ renderer: "webgl",
3845
+ store,
3846
+ refresh,
3847
+ setPluginVisible(nextVisible) {
3848
+ visible = nextVisible;
3849
+ customLayer.setVisible(visible);
3850
+ ensureLayers();
3851
+ setFenceLayerVisibility(
3852
+ map,
3853
+ options.interactive === false ? [] : [interactionLayerId],
3854
+ visible
3855
+ );
3856
+ updateAnimation();
3857
+ },
3858
+ destroy() {
3859
+ destroyed = true;
3860
+ stopAnimation();
3861
+ map.off("styledata", handleStyleData);
3862
+ unbindInteractions();
3863
+ if (map.getLayer(interactionLayerId)) {
3864
+ map.removeLayer(interactionLayerId);
3865
+ }
3866
+ if (map.getLayer(customLayerId)) {
3867
+ map.removeLayer(customLayerId);
3868
+ }
3869
+ if (map.getSource(interactionSourceId)) {
3870
+ map.removeSource(interactionSourceId);
3871
+ }
3872
+ }
3873
+ });
3874
+ };
3875
+ const resolveStyle = (style, tdtToken) => {
3876
+ if (!style) {
3877
+ return getBaseMapStyle("openfreemap-liberty");
3878
+ }
3879
+ if (typeof style === "string" && isBaseMapType(style)) {
3880
+ return getBaseMapStyle(style, { token: tdtToken });
3881
+ }
3882
+ return style;
3883
+ };
3884
+ const createMap = (options) => {
3885
+ const { style, tdtToken, projection, ...mapOptions } = options;
3886
+ const map = new maplibregl.Map({
3887
+ ...mapOptions,
3888
+ style: resolveStyle(style, tdtToken)
3889
+ });
3890
+ if (projection) {
3891
+ map.on("style.load", () => {
3892
+ map.setProjection({ type: projection });
3893
+ });
3894
+ }
3895
+ return map;
3896
+ };
2440
3897
  export {
2441
3898
  BASE_MAP_TYPES,
2442
3899
  OPENFREEMAP_STYLES,
@@ -2454,7 +3911,9 @@ export {
2454
3911
  addRadarSweep,
2455
3912
  addRingPulseMarker,
2456
3913
  addSectorScan,
3914
+ addStandardElectronicFenceLayer,
2457
3915
  addTargetLock,
3916
+ addWebGLElectronicFenceLayer,
2458
3917
  createFenceCircle,
2459
3918
  createMap,
2460
3919
  createTiandituImageStyle,