@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/README.md +102 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1469 -12
- 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/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/package.json +37 -37
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:
|
|
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
|
-
|
|
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,
|