@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.
- package/README.md +102 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1566 -107
- package/dist/index.umd.js +1 -1
- package/dist/types/effects/electronic-fence-common.d.ts +76 -0
- package/dist/types/effects/electronic-fence-types.d.ts +131 -0
- package/dist/types/effects/index.d.ts +3 -0
- package/dist/types/effects/low-altitude/breach-alert.d.ts +61 -0
- package/dist/types/effects/low-altitude/breathing-circle.d.ts +5 -0
- package/dist/types/effects/low-altitude/coordinated-countermeasure.d.ts +5 -0
- package/dist/types/effects/low-altitude/countermeasure-beam.d.ts +5 -0
- package/dist/types/effects/low-altitude/defence-circle.d.ts +5 -0
- package/dist/types/effects/low-altitude/directional-pulse.d.ts +6 -0
- package/dist/types/effects/low-altitude/navigation-spoofing.d.ts +6 -0
- package/dist/types/effects/low-altitude/pulse-marker.d.ts +5 -0
- package/dist/types/effects/low-altitude/radar-sweep.d.ts +5 -0
- package/dist/types/effects/low-altitude/ring-pulse-marker.d.ts +5 -0
- package/dist/types/effects/low-altitude/sector-scan.d.ts +5 -0
- package/dist/types/effects/low-altitude/shared.d.ts +67 -0
- package/dist/types/effects/low-altitude/target-lock.d.ts +4 -0
- package/dist/types/effects/low-altitude/types.d.ts +333 -0
- package/dist/types/effects/low-altitude/visual.d.ts +15 -0
- package/dist/types/effects/low-altitude.d.ts +15 -441
- package/dist/types/effects/standard-electronic-fence.d.ts +7 -0
- package/dist/types/effects/webgl-electronic-fence.d.ts +7 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/map/create-map.d.ts +2 -1
- 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
|
|
642
|
-
|
|
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
|
|
2425
|
-
|
|
2426
|
-
|
|
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 (
|
|
2429
|
-
|
|
2439
|
+
if (!isSamePosition(coordinates[0], coordinates[coordinates.length - 1])) {
|
|
2440
|
+
coordinates.push([...coordinates[0]]);
|
|
2430
2441
|
}
|
|
2431
|
-
|
|
2442
|
+
if (coordinates.length < 4) {
|
|
2443
|
+
throw new Error("电子围栏 Polygon 环无效。");
|
|
2444
|
+
}
|
|
2445
|
+
return coordinates;
|
|
2432
2446
|
};
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
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,
|