aur-openlayers 19.6.3 → 19.6.5

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.
@@ -9,6 +9,8 @@ import Collection from 'ol/Collection';
9
9
  import DragPan from 'ol/interaction/DragPan';
10
10
  import Modify from 'ol/interaction/Modify';
11
11
  import * as Observable from 'ol/Observable';
12
+ import { unByKey } from 'ol/Observable';
13
+ import { LineString, MultiLineString, Point } from 'ol/geom';
12
14
 
13
15
  class LibService {
14
16
  constructor() { }
@@ -180,12 +182,15 @@ function toOlPadding(p, fallback = DEFAULT_OL_PADDING) {
180
182
  }
181
183
  return [p.top, p.right, p.bottom, p.left];
182
184
  }
183
- function toOlFitOptions(opts) {
185
+ function toOlFitOptions(opts, map) {
184
186
  const padding = toOlPadding(opts?.padding) ?? DEFAULT_OL_PADDING;
185
187
  const duration = opts?.duration ?? DEFAULT_FIT_DURATION;
186
188
  // OL View#fit options are a plain object; keep it minimal & typed-friendly
187
189
  const fitOpts = { padding, duration };
188
- if (opts?.maxZoom != null) {
190
+ if (opts?.keepZoom && map) {
191
+ fitOpts.maxZoom = map.getView().getZoom();
192
+ }
193
+ else if (opts?.maxZoom != null) {
189
194
  fitOpts.maxZoom = opts.maxZoom;
190
195
  }
191
196
  return fitOpts;
@@ -214,12 +219,12 @@ const createMapContext = (map, layers, popupHost, scheduler = new FlushScheduler
214
219
  centerOnAllLayers: (opts) => {
215
220
  const extent = collectLayersExtent(layers);
216
221
  if (extent)
217
- map.getView().fit(extent, toOlFitOptions(opts));
222
+ map.getView().fit(extent, toOlFitOptions(opts, map));
218
223
  },
219
224
  centerOnLayers: (layerIds, opts) => {
220
225
  const extent = collectLayersExtent(layers, layerIds);
221
226
  if (extent)
222
- map.getView().fit(extent, toOlFitOptions(opts));
227
+ map.getView().fit(extent, toOlFitOptions(opts, map));
223
228
  },
224
229
  };
225
230
  };
@@ -1582,7 +1587,7 @@ class StyleCache {
1582
1587
  }
1583
1588
  }
1584
1589
 
1585
- const resolveMaybeFn = (value, args) => {
1590
+ const resolveMaybeFn$1 = (value, args) => {
1586
1591
  if (typeof value === 'function') {
1587
1592
  return value(...args);
1588
1593
  }
@@ -1617,7 +1622,7 @@ const createStyleFunction = (options) => {
1617
1622
  resolution,
1618
1623
  zoom: map?.getView().getZoom(),
1619
1624
  };
1620
- let opts = resolveMaybeFn(style.base, [model, view]);
1625
+ let opts = resolveMaybeFn$1(style.base, [model, view]);
1621
1626
  if (style.states) {
1622
1627
  const featureStates = orderStates(getFeatureStates(feature), style.statePriority);
1623
1628
  featureStates.forEach((state) => {
@@ -1625,7 +1630,7 @@ const createStyleFunction = (options) => {
1625
1630
  if (!patchFactory) {
1626
1631
  return;
1627
1632
  }
1628
- const patch = resolveMaybeFn(patchFactory, [model, view]);
1633
+ const patch = resolveMaybeFn$1(patchFactory, [model, view]);
1629
1634
  opts = applyPatch(opts, patch);
1630
1635
  });
1631
1636
  }
@@ -1886,7 +1891,7 @@ class VectorLayerBase {
1886
1891
  if (!extent || isEmpty(extent)) {
1887
1892
  return;
1888
1893
  }
1889
- this.ctx.map.getView().fit(extent, toOlFitOptions(opts));
1894
+ this.ctx.map.getView().fit(extent, toOlFitOptions(opts, this.ctx.map));
1890
1895
  }
1891
1896
  /** Fit view to a single feature by id. No-op if feature/geometry is missing. */
1892
1897
  centerOnModel(id, opts) {
@@ -1895,7 +1900,7 @@ class VectorLayerBase {
1895
1900
  if (!geom) {
1896
1901
  return;
1897
1902
  }
1898
- this.ctx.map.getView().fit(geom.getExtent(), toOlFitOptions(opts));
1903
+ this.ctx.map.getView().fit(geom.getExtent(), toOlFitOptions(opts, this.ctx.map));
1899
1904
  }
1900
1905
  /**
1901
1906
  * Fit view to a subset of features by ids (combined extent).
@@ -1915,7 +1920,7 @@ class VectorLayerBase {
1915
1920
  if (!found || isEmpty(extent)) {
1916
1921
  return;
1917
1922
  }
1918
- this.ctx.map.getView().fit(extent, toOlFitOptions(opts));
1923
+ this.ctx.map.getView().fit(extent, toOlFitOptions(opts, this.ctx.map));
1919
1924
  }
1920
1925
  getExtent() {
1921
1926
  const extent = this.source.getExtent();
@@ -2289,6 +2294,140 @@ class PopupHost {
2289
2294
  }
2290
2295
  }
2291
2296
 
2297
+ const resolveMaybeFn = (value, args) => typeof value === 'function' ? value(...args) : value;
2298
+ /**
2299
+ * Extracts flat coordinate arrays from a feature's geometry.
2300
+ * Supports LineString and MultiLineString; returns empty array for others.
2301
+ */
2302
+ function extractLineCoords(geometry) {
2303
+ if (geometry instanceof LineString) {
2304
+ return [geometry.getCoordinates()];
2305
+ }
2306
+ if (geometry instanceof MultiLineString) {
2307
+ return geometry.getCoordinates();
2308
+ }
2309
+ return [];
2310
+ }
2311
+ /**
2312
+ * Generate arrow points along a polyline at a given interval (meters in EPSG:3857).
2313
+ */
2314
+ function generateArrowPoints(coords, intervalMeters, offsetRatio) {
2315
+ if (coords.length < 2 || intervalMeters <= 0)
2316
+ return [];
2317
+ const arrows = [];
2318
+ let accumulated = intervalMeters * offsetRatio;
2319
+ for (let i = 0; i < coords.length - 1; i++) {
2320
+ const ax = coords[i][0], ay = coords[i][1];
2321
+ const bx = coords[i + 1][0], by = coords[i + 1][1];
2322
+ const segLen = Math.hypot(bx - ax, by - ay);
2323
+ const rotation = Math.atan2(bx - ax, by - ay);
2324
+ while (accumulated <= segLen) {
2325
+ const t = accumulated / segLen;
2326
+ arrows.push({
2327
+ coordinate: [ax + t * (bx - ax), ay + t * (by - ay)],
2328
+ rotation,
2329
+ });
2330
+ accumulated += intervalMeters;
2331
+ }
2332
+ accumulated -= segLen;
2333
+ }
2334
+ return arrows;
2335
+ }
2336
+ class ArrowDecorationManager {
2337
+ source = new VectorSource();
2338
+ layer;
2339
+ config;
2340
+ map;
2341
+ parentLayer;
2342
+ parentApi;
2343
+ moveEndKey;
2344
+ unsubCollection;
2345
+ unsubChanges;
2346
+ rafId = null;
2347
+ constructor(options) {
2348
+ this.config = options.config;
2349
+ this.map = options.map;
2350
+ this.parentLayer = options.parentLayer;
2351
+ this.parentApi = options.parentApi;
2352
+ const parentZ = this.parentLayer.getZIndex() ?? 0;
2353
+ this.layer = new VectorLayer({
2354
+ source: this.source,
2355
+ zIndex: parentZ + 1,
2356
+ });
2357
+ this.layer.set('id', `__decoration_arrows`);
2358
+ this.map.addLayer(this.layer);
2359
+ this.syncVisibility();
2360
+ this.syncOpacity();
2361
+ this.moveEndKey = this.map.on('moveend', () => this.scheduleUpdate());
2362
+ this.unsubCollection = this.parentApi.onModelsCollectionChanged(() => this.scheduleUpdate());
2363
+ this.unsubChanges = this.parentApi.onModelsChanged?.(() => this.scheduleUpdate());
2364
+ }
2365
+ scheduleUpdate() {
2366
+ if (this.rafId !== null)
2367
+ return;
2368
+ this.rafId = requestAnimationFrame(() => {
2369
+ this.rafId = null;
2370
+ this.rebuild();
2371
+ });
2372
+ }
2373
+ rebuild() {
2374
+ this.syncVisibility();
2375
+ this.syncOpacity();
2376
+ if (!this.parentLayer.getVisible()) {
2377
+ this.source.clear();
2378
+ return;
2379
+ }
2380
+ const view = this.map.getView();
2381
+ const resolution = view.getResolution() ?? 1;
2382
+ const styleView = { resolution, zoom: view.getZoom() };
2383
+ const interval = resolveMaybeFn(this.config.interval, [styleView]);
2384
+ const offsetRatio = this.config.offsetRatio ?? 0.5;
2385
+ const parentSource = this.parentLayer.getSource();
2386
+ if (!parentSource) {
2387
+ this.source.clear();
2388
+ return;
2389
+ }
2390
+ const allArrows = [];
2391
+ parentSource.getFeatures().forEach((feature) => {
2392
+ const geom = feature.getGeometry();
2393
+ if (!geom)
2394
+ return;
2395
+ const lines = extractLineCoords(geom);
2396
+ for (const coords of lines) {
2397
+ const arrows = generateArrowPoints(coords, interval, offsetRatio);
2398
+ allArrows.push(...arrows);
2399
+ }
2400
+ });
2401
+ const features = allArrows.map((arrow, i) => {
2402
+ const f = new Feature({ geometry: new Point(arrow.coordinate) });
2403
+ f.setId(`__arrow_${i}`);
2404
+ const styleResult = this.config.style({ rotation: arrow.rotation, view: styleView });
2405
+ f.setStyle(Array.isArray(styleResult) ? styleResult : [styleResult]);
2406
+ return f;
2407
+ });
2408
+ this.source.clear();
2409
+ if (features.length > 0) {
2410
+ this.source.addFeatures(features);
2411
+ }
2412
+ }
2413
+ syncVisibility() {
2414
+ this.layer.setVisible(this.parentLayer.getVisible());
2415
+ }
2416
+ syncOpacity() {
2417
+ this.layer.setOpacity(this.parentLayer.getOpacity());
2418
+ }
2419
+ dispose() {
2420
+ if (this.rafId !== null) {
2421
+ cancelAnimationFrame(this.rafId);
2422
+ this.rafId = null;
2423
+ }
2424
+ unByKey(this.moveEndKey);
2425
+ this.unsubCollection();
2426
+ this.unsubChanges?.();
2427
+ this.map.removeLayer(this.layer);
2428
+ }
2429
+ }
2430
+
2292
2431
  class LayerManager {
2293
2432
  map;
2294
2433
  layers = {};
@@ -2297,6 +2436,7 @@ class LayerManager {
2297
2436
  scheduler;
2298
2437
  popupHost;
2299
2438
  ctx;
2439
+ decorationManagers = [];
2300
2440
  constructor(map, schema) {
2301
2441
  this.map = map;
2302
2442
  const popupHost = schema.options?.popupHost ? new PopupHost(schema.options.popupHost) : undefined;
@@ -2344,6 +2484,15 @@ class LayerManager {
2344
2484
  this.layers[descriptor.id] = layer;
2345
2485
  this.apis[descriptor.id] = api;
2346
2486
  this.map.addLayer(layer);
2487
+ if (descriptor.feature.decorations?.arrows) {
2488
+ const decorationManager = new ArrowDecorationManager({
2489
+ map: this.map,
2490
+ parentLayer: layer,
2491
+ parentApi: api,
2492
+ config: descriptor.feature.decorations.arrows,
2493
+ });
2494
+ this.decorationManagers.push(decorationManager);
2495
+ }
2347
2496
  });
2348
2497
  this.interactions = new InteractionManager({
2349
2498
  ctx,
@@ -2375,6 +2524,7 @@ class LayerManager {
2375
2524
  this.interactions.dispose();
2376
2525
  this.popupHost?.dispose();
2377
2526
  this.scheduler.dispose();
2527
+ this.decorationManagers.forEach((dm) => dm.dispose());
2378
2528
  Object.values(this.layers).forEach((layer) => this.map.removeLayer(layer));
2379
2529
  }
2380
2530
  }