aur-openlayers 19.6.5 → 19.6.9
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/fesm2022/aur-openlayers.mjs +241 -4
- package/fesm2022/aur-openlayers.mjs.map +1 -1
- package/lib/map-framework/public/types.d.ts +13 -0
- package/lib/map-framework/runtime/decorations/arrow-decoration-manager.d.ts +1 -0
- package/lib/map-framework/runtime/decorations/buffer-decoration-manager.d.ts +28 -0
- package/lib/map-framework/runtime/decorations/generate-buffer-polygon.d.ts +9 -0
- package/package.json +1 -1
|
@@ -10,7 +10,8 @@ import DragPan from 'ol/interaction/DragPan';
|
|
|
10
10
|
import Modify from 'ol/interaction/Modify';
|
|
11
11
|
import * as Observable from 'ol/Observable';
|
|
12
12
|
import { unByKey } from 'ol/Observable';
|
|
13
|
-
import { LineString, MultiLineString, Point } from 'ol/geom';
|
|
13
|
+
import { LineString, MultiLineString, Point, Polygon } from 'ol/geom';
|
|
14
|
+
import { toLonLat } from 'ol/proj';
|
|
14
15
|
|
|
15
16
|
class LibService {
|
|
16
17
|
constructor() { }
|
|
@@ -2299,7 +2300,7 @@ const resolveMaybeFn = (value, args) => typeof value === 'function' ? value(...a
|
|
|
2299
2300
|
* Extracts flat coordinate arrays from a feature's geometry.
|
|
2300
2301
|
* Supports LineString and MultiLineString; returns empty array for others.
|
|
2301
2302
|
*/
|
|
2302
|
-
function extractLineCoords(geometry) {
|
|
2303
|
+
function extractLineCoords$1(geometry) {
|
|
2303
2304
|
if (geometry instanceof LineString) {
|
|
2304
2305
|
return [geometry.getCoordinates()];
|
|
2305
2306
|
}
|
|
@@ -2341,6 +2342,7 @@ class ArrowDecorationManager {
|
|
|
2341
2342
|
parentLayer;
|
|
2342
2343
|
parentApi;
|
|
2343
2344
|
moveEndKey;
|
|
2345
|
+
visibilityKey;
|
|
2344
2346
|
unsubCollection;
|
|
2345
2347
|
unsubChanges;
|
|
2346
2348
|
rafId = null;
|
|
@@ -2352,12 +2354,13 @@ class ArrowDecorationManager {
|
|
|
2352
2354
|
const parentZ = this.parentLayer.getZIndex() ?? 0;
|
|
2353
2355
|
this.layer = new VectorLayer({
|
|
2354
2356
|
source: this.source,
|
|
2355
|
-
zIndex: parentZ +
|
|
2357
|
+
zIndex: parentZ + 2,
|
|
2356
2358
|
});
|
|
2357
2359
|
this.layer.set('id', `__decoration_arrows`);
|
|
2358
2360
|
this.map.addLayer(this.layer);
|
|
2359
2361
|
this.syncVisibility();
|
|
2360
2362
|
this.syncOpacity();
|
|
2363
|
+
this.visibilityKey = this.parentLayer.on('change:visible', () => this.syncVisibility());
|
|
2361
2364
|
this.moveEndKey = this.map.on('moveend', () => this.scheduleUpdate());
|
|
2362
2365
|
this.unsubCollection = this.parentApi.onModelsCollectionChanged(() => this.scheduleUpdate());
|
|
2363
2366
|
this.unsubChanges = this.parentApi.onModelsChanged?.(() => this.scheduleUpdate());
|
|
@@ -2392,7 +2395,7 @@ class ArrowDecorationManager {
|
|
|
2392
2395
|
const geom = feature.getGeometry();
|
|
2393
2396
|
if (!geom)
|
|
2394
2397
|
return;
|
|
2395
|
-
const lines = extractLineCoords(geom);
|
|
2398
|
+
const lines = extractLineCoords$1(geom);
|
|
2396
2399
|
for (const coords of lines) {
|
|
2397
2400
|
const arrows = generateArrowPoints(coords, interval, offsetRatio);
|
|
2398
2401
|
allArrows.push(...arrows);
|
|
@@ -2422,6 +2425,230 @@ class ArrowDecorationManager {
|
|
|
2422
2425
|
this.rafId = null;
|
|
2423
2426
|
}
|
|
2424
2427
|
unByKey(this.moveEndKey);
|
|
2428
|
+
unByKey(this.visibilityKey);
|
|
2429
|
+
this.unsubCollection();
|
|
2430
|
+
this.unsubChanges?.();
|
|
2431
|
+
this.map.removeLayer(this.layer);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
/**
|
|
2436
|
+
* Compute projected buffer distance for a segment by converting meters
|
|
2437
|
+
* to EPSG:3857 units using the segment's average latitude.
|
|
2438
|
+
*/
|
|
2439
|
+
function projectedDistance(ax, ay, bx, by, meters) {
|
|
2440
|
+
const midY = (ay + by) / 2;
|
|
2441
|
+
const [, lat] = toLonLat([0, midY]);
|
|
2442
|
+
const scale = Math.cos((lat * Math.PI) / 180);
|
|
2443
|
+
return scale > 1e-10 ? meters / scale : meters;
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Compute the unit normal of a segment (pointing left when walking from a→b).
|
|
2447
|
+
*/
|
|
2448
|
+
function segmentNormal(ax, ay, bx, by) {
|
|
2449
|
+
const dx = bx - ax;
|
|
2450
|
+
const dy = by - ay;
|
|
2451
|
+
const len = Math.hypot(dx, dy);
|
|
2452
|
+
if (len === 0)
|
|
2453
|
+
return [0, 0];
|
|
2454
|
+
return [-dy / len, dx / len];
|
|
2455
|
+
}
|
|
2456
|
+
/**
|
|
2457
|
+
* Generate a semicircle of points around a center, from startAngle to startAngle + PI.
|
|
2458
|
+
*/
|
|
2459
|
+
function semicircle(cx, cy, radius, startAngle, segments) {
|
|
2460
|
+
const points = [];
|
|
2461
|
+
for (let i = 0; i <= segments; i++) {
|
|
2462
|
+
const angle = startAngle + (Math.PI * i) / segments;
|
|
2463
|
+
points.push([cx + radius * Math.cos(angle), cy + radius * Math.sin(angle)]);
|
|
2464
|
+
}
|
|
2465
|
+
return points;
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* Generate a closed polygon ring representing a buffer around a polyline.
|
|
2469
|
+
*
|
|
2470
|
+
* @param coords Line vertices in EPSG:3857.
|
|
2471
|
+
* @param distance Buffer width in meters (one side).
|
|
2472
|
+
* @param cap End cap style: 'round' or 'flat'.
|
|
2473
|
+
* @returns Closed ring (first == last point), or empty array if line is degenerate.
|
|
2474
|
+
*/
|
|
2475
|
+
function generateBufferPolygon(coords, distance, cap) {
|
|
2476
|
+
if (coords.length < 2)
|
|
2477
|
+
return [];
|
|
2478
|
+
const n = coords.length;
|
|
2479
|
+
const left = [];
|
|
2480
|
+
const right = [];
|
|
2481
|
+
// Compute per-vertex offset using averaged normals at joins
|
|
2482
|
+
for (let i = 0; i < n; i++) {
|
|
2483
|
+
let nx, ny, dist;
|
|
2484
|
+
if (i === 0) {
|
|
2485
|
+
// First vertex — use first segment's normal
|
|
2486
|
+
dist = projectedDistance(coords[0][0], coords[0][1], coords[1][0], coords[1][1], distance);
|
|
2487
|
+
[nx, ny] = segmentNormal(coords[0][0], coords[0][1], coords[1][0], coords[1][1]);
|
|
2488
|
+
}
|
|
2489
|
+
else if (i === n - 1) {
|
|
2490
|
+
// Last vertex — use last segment's normal
|
|
2491
|
+
dist = projectedDistance(coords[n - 2][0], coords[n - 2][1], coords[n - 1][0], coords[n - 1][1], distance);
|
|
2492
|
+
[nx, ny] = segmentNormal(coords[n - 2][0], coords[n - 2][1], coords[n - 1][0], coords[n - 1][1]);
|
|
2493
|
+
}
|
|
2494
|
+
else {
|
|
2495
|
+
// Interior vertex — average normals of adjacent segments
|
|
2496
|
+
const dist1 = projectedDistance(coords[i - 1][0], coords[i - 1][1], coords[i][0], coords[i][1], distance);
|
|
2497
|
+
const dist2 = projectedDistance(coords[i][0], coords[i][1], coords[i + 1][0], coords[i + 1][1], distance);
|
|
2498
|
+
dist = (dist1 + dist2) / 2;
|
|
2499
|
+
const [n1x, n1y] = segmentNormal(coords[i - 1][0], coords[i - 1][1], coords[i][0], coords[i][1]);
|
|
2500
|
+
const [n2x, n2y] = segmentNormal(coords[i][0], coords[i][1], coords[i + 1][0], coords[i + 1][1]);
|
|
2501
|
+
nx = n1x + n2x;
|
|
2502
|
+
ny = n1y + n2y;
|
|
2503
|
+
const len = Math.hypot(nx, ny);
|
|
2504
|
+
if (len > 1e-10) {
|
|
2505
|
+
nx /= len;
|
|
2506
|
+
ny /= len;
|
|
2507
|
+
// Miter length factor: 1 / cos(half-angle between segments)
|
|
2508
|
+
const dot = n1x * nx + n1y * ny;
|
|
2509
|
+
const miterScale = dot > 0.5 ? 1 / dot : 2; // Clamp miter at ratio 2 to prevent spikes
|
|
2510
|
+
dist *= miterScale;
|
|
2511
|
+
}
|
|
2512
|
+
else {
|
|
2513
|
+
[nx, ny] = segmentNormal(coords[i - 1][0], coords[i - 1][1], coords[i][0], coords[i][1]);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
const x = coords[i][0];
|
|
2517
|
+
const y = coords[i][1];
|
|
2518
|
+
left.push([x + nx * dist, y + ny * dist]);
|
|
2519
|
+
right.push([x - nx * dist, y - ny * dist]);
|
|
2520
|
+
}
|
|
2521
|
+
// Assemble the polygon ring: left forward → end cap → right backward → start cap → close
|
|
2522
|
+
const ring = [];
|
|
2523
|
+
// Left side (forward)
|
|
2524
|
+
for (let i = 0; i < n; i++) {
|
|
2525
|
+
ring.push(left[i]);
|
|
2526
|
+
}
|
|
2527
|
+
// End cap
|
|
2528
|
+
if (cap === 'round') {
|
|
2529
|
+
const [n2x, n2y] = segmentNormal(coords[n - 2][0], coords[n - 2][1], coords[n - 1][0], coords[n - 1][1]);
|
|
2530
|
+
const endAngle = Math.atan2(-n2y, -n2x);
|
|
2531
|
+
const dist = projectedDistance(coords[n - 2][0], coords[n - 2][1], coords[n - 1][0], coords[n - 1][1], distance);
|
|
2532
|
+
ring.push(...semicircle(coords[n - 1][0], coords[n - 1][1], dist, endAngle, 8));
|
|
2533
|
+
}
|
|
2534
|
+
else {
|
|
2535
|
+
ring.push(right[n - 1]);
|
|
2536
|
+
}
|
|
2537
|
+
// Right side (backward)
|
|
2538
|
+
for (let i = n - 2; i >= 0; i--) {
|
|
2539
|
+
ring.push(right[i]);
|
|
2540
|
+
}
|
|
2541
|
+
// Start cap
|
|
2542
|
+
if (cap === 'round') {
|
|
2543
|
+
const [n1x, n1y] = segmentNormal(coords[0][0], coords[0][1], coords[1][0], coords[1][1]);
|
|
2544
|
+
const startAngle = Math.atan2(n1y, n1x);
|
|
2545
|
+
const dist = projectedDistance(coords[0][0], coords[0][1], coords[1][0], coords[1][1], distance);
|
|
2546
|
+
ring.push(...semicircle(coords[0][0], coords[0][1], dist, startAngle, 8));
|
|
2547
|
+
}
|
|
2548
|
+
// Close ring
|
|
2549
|
+
ring.push(ring[0].slice());
|
|
2550
|
+
return ring;
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
/**
|
|
2554
|
+
* Extracts flat coordinate arrays from a feature's geometry.
|
|
2555
|
+
* Supports LineString and MultiLineString; returns empty array for others.
|
|
2556
|
+
*/
|
|
2557
|
+
function extractLineCoords(geometry) {
|
|
2558
|
+
if (geometry instanceof LineString) {
|
|
2559
|
+
return [geometry.getCoordinates()];
|
|
2560
|
+
}
|
|
2561
|
+
if (geometry instanceof MultiLineString) {
|
|
2562
|
+
return geometry.getCoordinates();
|
|
2563
|
+
}
|
|
2564
|
+
return [];
|
|
2565
|
+
}
|
|
2566
|
+
class BufferDecorationManager {
|
|
2567
|
+
source = new VectorSource();
|
|
2568
|
+
layer;
|
|
2569
|
+
config;
|
|
2570
|
+
map;
|
|
2571
|
+
parentLayer;
|
|
2572
|
+
parentApi;
|
|
2573
|
+
moveEndKey;
|
|
2574
|
+
visibilityKey;
|
|
2575
|
+
unsubCollection;
|
|
2576
|
+
unsubChanges;
|
|
2577
|
+
rafId = null;
|
|
2578
|
+
constructor(options) {
|
|
2579
|
+
this.config = options.config;
|
|
2580
|
+
this.map = options.map;
|
|
2581
|
+
this.parentLayer = options.parentLayer;
|
|
2582
|
+
this.parentApi = options.parentApi;
|
|
2583
|
+
const parentZ = this.parentLayer.getZIndex() ?? 0;
|
|
2584
|
+
this.layer = new VectorLayer({
|
|
2585
|
+
source: this.source,
|
|
2586
|
+
zIndex: parentZ,
|
|
2587
|
+
});
|
|
2588
|
+
this.layer.set('id', '__decoration_buffer');
|
|
2589
|
+
this.map.addLayer(this.layer);
|
|
2590
|
+
this.syncVisibility();
|
|
2591
|
+
this.syncOpacity();
|
|
2592
|
+
this.visibilityKey = this.parentLayer.on('change:visible', () => this.syncVisibility());
|
|
2593
|
+
this.moveEndKey = this.map.on('moveend', () => this.scheduleUpdate());
|
|
2594
|
+
this.unsubCollection = this.parentApi.onModelsCollectionChanged(() => this.scheduleUpdate());
|
|
2595
|
+
this.unsubChanges = this.parentApi.onModelsChanged?.(() => this.scheduleUpdate());
|
|
2596
|
+
}
|
|
2597
|
+
scheduleUpdate() {
|
|
2598
|
+
if (this.rafId !== null)
|
|
2599
|
+
return;
|
|
2600
|
+
this.rafId = requestAnimationFrame(() => {
|
|
2601
|
+
this.rafId = null;
|
|
2602
|
+
this.rebuild();
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
rebuild() {
|
|
2606
|
+
this.syncVisibility();
|
|
2607
|
+
this.syncOpacity();
|
|
2608
|
+
if (!this.parentLayer.getVisible()) {
|
|
2609
|
+
this.source.clear();
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
const parentSource = this.parentLayer.getSource();
|
|
2613
|
+
if (!parentSource) {
|
|
2614
|
+
this.source.clear();
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
const cap = this.config.cap ?? 'round';
|
|
2618
|
+
const style = this.config.style;
|
|
2619
|
+
const allFeatures = [];
|
|
2620
|
+
parentSource.getFeatures().forEach((feature) => {
|
|
2621
|
+
const geom = feature.getGeometry();
|
|
2622
|
+
if (!geom)
|
|
2623
|
+
return;
|
|
2624
|
+
const lines = extractLineCoords(geom);
|
|
2625
|
+
for (const coords of lines) {
|
|
2626
|
+
const ring = generateBufferPolygon(coords, this.config.distance, cap);
|
|
2627
|
+
if (ring.length === 0)
|
|
2628
|
+
continue;
|
|
2629
|
+
const f = new Feature({ geometry: new Polygon([ring]) });
|
|
2630
|
+
f.setStyle(Array.isArray(style) ? style : [style]);
|
|
2631
|
+
allFeatures.push(f);
|
|
2632
|
+
}
|
|
2633
|
+
});
|
|
2634
|
+
this.source.clear();
|
|
2635
|
+
if (allFeatures.length > 0) {
|
|
2636
|
+
this.source.addFeatures(allFeatures);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
syncVisibility() {
|
|
2640
|
+
this.layer.setVisible(this.parentLayer.getVisible());
|
|
2641
|
+
}
|
|
2642
|
+
syncOpacity() {
|
|
2643
|
+
this.layer.setOpacity(this.parentLayer.getOpacity());
|
|
2644
|
+
}
|
|
2645
|
+
dispose() {
|
|
2646
|
+
if (this.rafId !== null) {
|
|
2647
|
+
cancelAnimationFrame(this.rafId);
|
|
2648
|
+
this.rafId = null;
|
|
2649
|
+
}
|
|
2650
|
+
unByKey(this.moveEndKey);
|
|
2651
|
+
unByKey(this.visibilityKey);
|
|
2425
2652
|
this.unsubCollection();
|
|
2426
2653
|
this.unsubChanges?.();
|
|
2427
2654
|
this.map.removeLayer(this.layer);
|
|
@@ -2484,6 +2711,16 @@ class LayerManager {
|
|
|
2484
2711
|
this.layers[descriptor.id] = layer;
|
|
2485
2712
|
this.apis[descriptor.id] = api;
|
|
2486
2713
|
this.map.addLayer(layer);
|
|
2714
|
+
if (descriptor.feature.decorations?.buffer) {
|
|
2715
|
+
const bufferManager = new BufferDecorationManager({
|
|
2716
|
+
map: this.map,
|
|
2717
|
+
parentLayer: layer,
|
|
2718
|
+
parentApi: api,
|
|
2719
|
+
config: descriptor.feature.decorations.buffer,
|
|
2720
|
+
});
|
|
2721
|
+
this.decorationManagers.push(bufferManager);
|
|
2722
|
+
layer.setZIndex((descriptor.zIndex ?? 0) + 1);
|
|
2723
|
+
}
|
|
2487
2724
|
if (descriptor.feature.decorations?.arrows) {
|
|
2488
2725
|
const decorationManager = new ArrowDecorationManager({
|
|
2489
2726
|
map: this.map,
|