@bitalltech-maplibre/core 1.0.2 → 1.0.4

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