@angular-helpers/openlayers 0.5.0 → 0.5.1
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 +119 -34
- package/core/README.md +43 -0
- package/fesm2022/angular-helpers-openlayers-core.mjs +215 -2
- package/fesm2022/angular-helpers-openlayers-interactions.mjs +121 -54
- package/fesm2022/angular-helpers-openlayers-layers.mjs +450 -365
- package/fesm2022/angular-helpers-openlayers-overlays.mjs +26 -2
- package/package.json +1 -1
- package/types/angular-helpers-openlayers-core.d.ts +73 -4
- package/types/angular-helpers-openlayers-interactions.d.ts +10 -13
- package/types/angular-helpers-openlayers-layers.d.ts +16 -16
package/README.md
CHANGED
|
@@ -184,87 +184,150 @@ const handle = popups.openComponent({
|
|
|
184
184
|
|
|
185
185
|
`open` is idempotent by `id` and updates the existing overlay in place. `openComponent` always recreates the `ComponentRef` on a repeated id and disposes the previous one (`appRef.detachView` + `ref.destroy`) to avoid CD leaks. Calls made before the map is ready are queued and replayed on `OlMapService.onReady`.
|
|
186
186
|
|
|
187
|
-
##
|
|
187
|
+
## WebGL Layers — GPU-accelerated rendering
|
|
188
188
|
|
|
189
|
-
Available since `0.
|
|
189
|
+
Available since `0.3.0` from `@angular-helpers/openlayers/layers`.
|
|
190
|
+
|
|
191
|
+
WebGL layers render directly on the GPU, making them perfect for extremely heavy tile configurations (with real-time styling expressions) and massive coordinate datasets (10,000+ vector points).
|
|
192
|
+
|
|
193
|
+
### `<ol-webgl-tile-layer>` — Raster style manipulation
|
|
194
|
+
|
|
195
|
+
Renders tile layers (OSM, XYZ, MVT) via WebGL. Supports the dynamic application of WebGL tile styles (raster expressions) for dynamic, GPU-powered adjustments like brightness, contrast, saturation, and gamma.
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<ol-webgl-tile-layer
|
|
199
|
+
id="satellite-webgl"
|
|
200
|
+
source="xyz"
|
|
201
|
+
[url]="'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'"
|
|
202
|
+
[tileStyle]="{
|
|
203
|
+
brightness: 0.1,
|
|
204
|
+
contrast: 0.2,
|
|
205
|
+
saturation: -0.5
|
|
206
|
+
}"
|
|
207
|
+
/>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### `<ol-webgl-vector-layer>` — Smooth massive datasets (10k+ features)
|
|
211
|
+
|
|
212
|
+
Renders points, lines, and polygons using WebGL 2. For peak performance, hit detection is disabled by default, and styling must be declared using `FlatStyleLike` expressions rather than standard `ol/style/Style` instances.
|
|
213
|
+
|
|
214
|
+
```html
|
|
215
|
+
<ol-webgl-vector-layer
|
|
216
|
+
id="massive-points"
|
|
217
|
+
[features]="densePoints()"
|
|
218
|
+
[flatStyle]="{
|
|
219
|
+
'circle-radius': 6,
|
|
220
|
+
'circle-fill-color': '#10b981',
|
|
221
|
+
'stroke-color': '#334155',
|
|
222
|
+
'stroke-width': 1
|
|
223
|
+
}"
|
|
224
|
+
[disableHitDetection]="true"
|
|
225
|
+
/>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Rigorous cleanup guarantees that WebGL contexts, framebuffers, and active buffers are fully released on destroy (`layer.dispose()`), preventing GPU leaks.
|
|
229
|
+
|
|
230
|
+
## Geodesic Geometry Helpers
|
|
190
231
|
|
|
191
|
-
|
|
232
|
+
Available from `@angular-helpers/openlayers/core` via `OlGeometryService`.
|
|
233
|
+
|
|
234
|
+
Approximates standard shapes in metric space using true geodesic calculations (`Vincenty`'s formulae via `ol/sphere`). This means your shapes remain mathematically accurate and visually consistent (without map projection scale distortion) across massive global distances.
|
|
192
235
|
|
|
193
236
|
```typescript
|
|
194
|
-
import { inject, signal } from '@angular/core';
|
|
195
|
-
import {
|
|
237
|
+
import { inject, Component, signal } from '@angular/core';
|
|
238
|
+
import { OlGeometryService } from '@angular-helpers/openlayers/core';
|
|
196
239
|
import type { Feature } from '@angular-helpers/openlayers/core';
|
|
197
240
|
|
|
198
241
|
@Component({
|
|
199
|
-
// …
|
|
200
242
|
imports: [OlMapComponent, OlVectorLayerComponent],
|
|
201
243
|
template: `
|
|
202
244
|
<ol-map [center]="[2.17, 41.38]" [zoom]="8">
|
|
203
245
|
<ol-tile-layer id="osm" source="osm" />
|
|
204
|
-
<ol-vector-layer id="
|
|
246
|
+
<ol-vector-layer id="shapes" [features]="features()" />
|
|
205
247
|
</ol-map>
|
|
206
248
|
`,
|
|
207
249
|
})
|
|
208
|
-
export class
|
|
209
|
-
private
|
|
250
|
+
export class GeodesicDemo {
|
|
251
|
+
private geomSvc = inject(OlGeometryService);
|
|
210
252
|
features = signal<Feature[]>([]);
|
|
211
253
|
|
|
212
|
-
|
|
213
|
-
const ellipse = this.
|
|
254
|
+
ngOnInit() {
|
|
255
|
+
const ellipse = this.geomSvc.createEllipse({
|
|
214
256
|
center: [2.17, 41.38],
|
|
215
257
|
semiMajor: 6_000,
|
|
216
258
|
semiMinor: 3_000,
|
|
217
259
|
rotation: Math.PI / 6,
|
|
218
260
|
});
|
|
219
|
-
const sector = this.
|
|
261
|
+
const sector = this.geomSvc.createSector({
|
|
220
262
|
center: [-0.38, 39.47],
|
|
221
263
|
radius: 8_000,
|
|
222
264
|
startAngle: Math.PI / 6,
|
|
223
265
|
endAngle: Math.PI / 2,
|
|
224
266
|
});
|
|
225
|
-
const donut = this.
|
|
267
|
+
const donut = this.geomSvc.createDonut({
|
|
226
268
|
center: [-5.99, 37.39],
|
|
227
269
|
innerRadius: 5_000,
|
|
228
270
|
outerRadius: 10_000,
|
|
229
271
|
});
|
|
230
|
-
|
|
231
|
-
sidc: 'SFGPUCI-----',
|
|
232
|
-
position: [-3.7, 40.42],
|
|
233
|
-
size: 36,
|
|
234
|
-
});
|
|
235
|
-
this.features.set([ellipse, sector, donut, symbol]);
|
|
272
|
+
this.features.set([ellipse, sector, donut]);
|
|
236
273
|
}
|
|
237
274
|
}
|
|
238
275
|
```
|
|
239
276
|
|
|
240
|
-
### Geometry helpers
|
|
241
|
-
|
|
242
277
|
| Method | Output | Notes |
|
|
243
278
|
| ----------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
|
|
244
279
|
| `createEllipse(config)` | `Feature<Polygon>` | Optional `rotation` in radians, configurable `segments` (default 64) |
|
|
245
280
|
| `createSector(config)` | `Feature<Polygon>` | Pie-slice (apex-arc-apex). `startAngle < endAngle ≤ start + 2π` |
|
|
246
281
|
| `createDonut(config)` | `Feature<Polygon>` | Two rings: outer CCW, inner CW (right-hand rule). Renders as an annular band with the basemap visible through the hole |
|
|
247
282
|
|
|
248
|
-
|
|
283
|
+
---
|
|
249
284
|
|
|
250
|
-
|
|
285
|
+
## Military Symbology & Tactical Graphics
|
|
251
286
|
|
|
252
|
-
|
|
287
|
+
Available since `0.4.0` from `@angular-helpers/openlayers/military`.
|
|
253
288
|
|
|
254
|
-
|
|
255
|
-
|
|
289
|
+
Exposes NATO MIL-STD-2525 symbol rendering backed by the optional [`milsymbol`](https://github.com/spatialillusions/milsymbol) peer dependency, plus tactical military graphic components (frontlines, attack vectors).
|
|
290
|
+
|
|
291
|
+
### MIL-STD-2525 Point Symbology (`OlMilitaryService`)
|
|
292
|
+
|
|
293
|
+
Lazy-loads the heavy `milsymbol` package dynamically on demand, returning a styled `Feature<Point>` so the vector layer renders it natively.
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { inject, Component, signal } from '@angular/core';
|
|
297
|
+
import { OlMilitaryService } from '@angular-helpers/openlayers/military';
|
|
298
|
+
import type { Feature } from '@angular-helpers/openlayers/core';
|
|
299
|
+
|
|
300
|
+
@Component({
|
|
301
|
+
imports: [OlMapComponent, OlVectorLayerComponent],
|
|
302
|
+
providers: [OlMilitaryService],
|
|
303
|
+
template: `
|
|
304
|
+
<ol-map [center]="[-3.7, 40.42]" [zoom]="8">
|
|
305
|
+
<ol-vector-layer id="military" [features]="features()" />
|
|
306
|
+
</ol-map>
|
|
307
|
+
`,
|
|
308
|
+
})
|
|
309
|
+
export class MilDemo {
|
|
310
|
+
private milSvc = inject(OlMilitaryService);
|
|
311
|
+
features = signal<Feature[]>([]);
|
|
312
|
+
|
|
313
|
+
async ngOnInit() {
|
|
314
|
+
const symbol = await this.milSvc.createMilSymbol({
|
|
315
|
+
sidc: 'SFGPUCI-----', // Friendly Infantry Unit
|
|
316
|
+
position: [-3.7, 40.42],
|
|
317
|
+
size: 36,
|
|
318
|
+
});
|
|
319
|
+
this.features.set([symbol]);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
256
322
|
```
|
|
257
323
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
size: 36,
|
|
263
|
-
uniqueDesignation: 'A1',
|
|
264
|
-
});
|
|
324
|
+
Install the optional peer dependency if utilizing NATO symbology:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
pnpm add milsymbol
|
|
265
328
|
```
|
|
266
329
|
|
|
267
|
-
Three
|
|
330
|
+
Three execution strategies:
|
|
268
331
|
|
|
269
332
|
- **`createMilSymbol(config)`** — async; lazy-loads on first call.
|
|
270
333
|
- **`createMilSymbolSync(config)`** — sync; throws if `milsymbol` is not loaded yet.
|
|
@@ -272,6 +335,28 @@ Three flavors:
|
|
|
272
335
|
|
|
273
336
|
The service throws clearly on non-browser environments (`createMilSymbol` requires `window`).
|
|
274
337
|
|
|
338
|
+
### Tactical Graphics (`OlTacticalGraphicsService`)
|
|
339
|
+
|
|
340
|
+
Builds advanced multi-point military tactical graphic features (frontlines with directional teeth, attack arrow coordinates) and provides custom styles.
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
import { OlTacticalGraphicsService } from '@angular-helpers/openlayers/military';
|
|
344
|
+
|
|
345
|
+
const tacticalSvc = inject(OlTacticalGraphicsService);
|
|
346
|
+
|
|
347
|
+
// Create a frontline graphic
|
|
348
|
+
const frontline = tacticalSvc.createFrontLine(
|
|
349
|
+
[
|
|
350
|
+
[2.1, 41.3],
|
|
351
|
+
[2.2, 41.4],
|
|
352
|
+
],
|
|
353
|
+
'friendly',
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
// Apply specialized style for frontline teeth
|
|
357
|
+
const frontlineStyle = tacticalSvc.createFrontLineStyle('#4f46e5', 'friendly');
|
|
358
|
+
```
|
|
359
|
+
|
|
275
360
|
## Architecture
|
|
276
361
|
|
|
277
362
|
### Data vs UI Separation
|
package/core/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @angular-helpers/openlayers/core
|
|
2
|
+
|
|
3
|
+
Core library for Angular bindings to OpenLayers.
|
|
4
|
+
Provides essential models, services, and the base `<ol-map>` component.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
\`\`\`bash
|
|
9
|
+
npm install @angular-helpers/openlayers
|
|
10
|
+
\`\`\`
|
|
11
|
+
|
|
12
|
+
## Core Services & Components
|
|
13
|
+
|
|
14
|
+
- \`OlMapComponent\`: The root map component that manages the core OpenLayers `Map` instance.
|
|
15
|
+
- \`OlMapService\`: A service to retrieve and manage the map instance across child components.
|
|
16
|
+
- \`OlLayerService\`: Manages map layers dynamically.
|
|
17
|
+
- \`OlZoneHelper\`: Optimizes Angular change detection around OpenLayers events.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
\`\`\`typescript
|
|
22
|
+
import { Component } from '@angular/core';
|
|
23
|
+
import { OlMapComponent } from '@angular-helpers/openlayers';
|
|
24
|
+
import { OlTileLayerComponent } from '@angular-helpers/openlayers/layers';
|
|
25
|
+
|
|
26
|
+
@Component({
|
|
27
|
+
selector: 'app-map-demo',
|
|
28
|
+
imports: [OlMapComponent, OlTileLayerComponent],
|
|
29
|
+
template: \`
|
|
30
|
+
<ol-map [center]="[0, 0]" [zoom]="4" class="map-container">
|
|
31
|
+
<ol-tile-layer source="osm"></ol-tile-layer>
|
|
32
|
+
</ol-map>
|
|
33
|
+
\`,
|
|
34
|
+
styles: [\`
|
|
35
|
+
.map-container {
|
|
36
|
+
width: 100%;
|
|
37
|
+
height: 400px;
|
|
38
|
+
display: block;
|
|
39
|
+
}
|
|
40
|
+
\`]
|
|
41
|
+
})
|
|
42
|
+
export class MapDemoComponent {}
|
|
43
|
+
\`\`\`
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, NgZone, Injectable, signal, DestroyRef, input, output, viewChild, afterNextRender, effect, ChangeDetectionStrategy, Component, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
2
|
+
import { inject, NgZone, Injectable, signal, DestroyRef, input, output, viewChild, afterNextRender, effect, ChangeDetectionStrategy, Component, computed, resource, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
3
3
|
import OLMap from 'ol/Map';
|
|
4
4
|
import View from 'ol/View';
|
|
5
5
|
import { fromLonLat, toLonLat, get } from 'ol/proj';
|
|
6
6
|
import { offset } from 'ol/sphere';
|
|
7
|
+
import GeoJSON from 'ol/format/GeoJSON';
|
|
8
|
+
import FeatureClass from 'ol/Feature';
|
|
9
|
+
import { Point, LineString, Polygon } from 'ol/geom';
|
|
7
10
|
import { register } from 'ol/proj/proj4';
|
|
8
11
|
|
|
9
12
|
// ZoneHelperService - Handles NgZone compatibility for zoneless mode
|
|
@@ -430,6 +433,216 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImpo
|
|
|
430
433
|
}]
|
|
431
434
|
}] });
|
|
432
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Service for orchestrating time-series animations in OpenLayers.
|
|
438
|
+
* Exposes a reactive currentTime signal that updates outside the Angular zone
|
|
439
|
+
* via requestAnimationFrame, ensuring 60FPS WebGL animations without triggering
|
|
440
|
+
* global change detection.
|
|
441
|
+
*/
|
|
442
|
+
class OlTimeService {
|
|
443
|
+
zoneHelper = inject(OlZoneHelper);
|
|
444
|
+
timeSignal = signal(Date.now(), ...(ngDevMode ? [{ debugName: "timeSignal" }] : /* istanbul ignore next */ []));
|
|
445
|
+
playingSignal = signal(false, ...(ngDevMode ? [{ debugName: "playingSignal" }] : /* istanbul ignore next */ []));
|
|
446
|
+
speedSignal = signal(1, ...(ngDevMode ? [{ debugName: "speedSignal" }] : /* istanbul ignore next */ []));
|
|
447
|
+
animationFrameId = null;
|
|
448
|
+
lastTick = 0;
|
|
449
|
+
currentTime = computed(() => this.timeSignal(), ...(ngDevMode ? [{ debugName: "currentTime" }] : /* istanbul ignore next */ []));
|
|
450
|
+
isPlaying = computed(() => this.playingSignal(), ...(ngDevMode ? [{ debugName: "isPlaying" }] : /* istanbul ignore next */ []));
|
|
451
|
+
speed = computed(() => this.speedSignal(), ...(ngDevMode ? [{ debugName: "speed" }] : /* istanbul ignore next */ []));
|
|
452
|
+
/**
|
|
453
|
+
* Sets the current time manually.
|
|
454
|
+
* @param time Epoch timestamp in milliseconds
|
|
455
|
+
*/
|
|
456
|
+
setTime(time) {
|
|
457
|
+
this.timeSignal.set(time);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Sets the playback speed multiplier.
|
|
461
|
+
* @param speed Multiplier (e.g. 1 = real time, 60 = 1 minute per second)
|
|
462
|
+
*/
|
|
463
|
+
setSpeed(speed) {
|
|
464
|
+
this.speedSignal.set(speed);
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Starts the time animation loop.
|
|
468
|
+
*/
|
|
469
|
+
play() {
|
|
470
|
+
if (this.playingSignal())
|
|
471
|
+
return;
|
|
472
|
+
this.playingSignal.set(true);
|
|
473
|
+
this.lastTick = performance.now();
|
|
474
|
+
this.zoneHelper.runOutsideAngular(() => {
|
|
475
|
+
const loop = (now) => {
|
|
476
|
+
if (!this.playingSignal())
|
|
477
|
+
return;
|
|
478
|
+
const delta = now - this.lastTick;
|
|
479
|
+
this.lastTick = now;
|
|
480
|
+
// Advance time based on delta and speed multiplier
|
|
481
|
+
const advance = delta * this.speedSignal();
|
|
482
|
+
this.timeSignal.update((t) => t + advance);
|
|
483
|
+
this.animationFrameId = requestAnimationFrame(loop);
|
|
484
|
+
};
|
|
485
|
+
this.animationFrameId = requestAnimationFrame(loop);
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Pauses the time animation loop.
|
|
490
|
+
*/
|
|
491
|
+
pause() {
|
|
492
|
+
this.playingSignal.set(false);
|
|
493
|
+
if (this.animationFrameId !== null) {
|
|
494
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
495
|
+
this.animationFrameId = null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Stops the animation and resets to a specific time.
|
|
500
|
+
*/
|
|
501
|
+
stop(resetTime = Date.now()) {
|
|
502
|
+
this.pause();
|
|
503
|
+
this.setTime(resetTime);
|
|
504
|
+
}
|
|
505
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTimeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
506
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTimeService, providedIn: 'root' });
|
|
507
|
+
}
|
|
508
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTimeService, decorators: [{
|
|
509
|
+
type: Injectable,
|
|
510
|
+
args: [{ providedIn: 'root' }]
|
|
511
|
+
}] });
|
|
512
|
+
|
|
513
|
+
// Feature conversion utilities for OpenLayers interactions
|
|
514
|
+
/**
|
|
515
|
+
* Converts an OpenLayers feature to the internal Feature format.
|
|
516
|
+
* Handles coordinate extraction and geometry type mapping.
|
|
517
|
+
*
|
|
518
|
+
* @param olFeature - The OpenLayers feature to convert
|
|
519
|
+
* @returns The converted Feature with normalized structure
|
|
520
|
+
*/
|
|
521
|
+
function olFeatureToFeature(olFeature) {
|
|
522
|
+
// Unwrap spider features
|
|
523
|
+
const spiderFeature = olFeature.get('spider-feature');
|
|
524
|
+
if (spiderFeature) {
|
|
525
|
+
return olFeatureToFeature(spiderFeature);
|
|
526
|
+
}
|
|
527
|
+
// Unwrap single-item clusters
|
|
528
|
+
const clusterFeatures = olFeature.get('features');
|
|
529
|
+
if (Array.isArray(clusterFeatures) && clusterFeatures.length === 1) {
|
|
530
|
+
return olFeatureToFeature(clusterFeatures[0]);
|
|
531
|
+
}
|
|
532
|
+
const geometry = olFeature.getGeometry();
|
|
533
|
+
const geomType = geometry?.getType() ?? 'Point';
|
|
534
|
+
// Convert coordinates based on geometry type
|
|
535
|
+
let coordinates;
|
|
536
|
+
if (geomType === 'Circle') {
|
|
537
|
+
// ol/geom/Circle has no getCoordinates() — use getCenter() instead
|
|
538
|
+
const circle = geometry;
|
|
539
|
+
coordinates = circle.getCenter();
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
// oxlint-disable-next-line no-explicit-any
|
|
543
|
+
const olCoords = geometry.getCoordinates();
|
|
544
|
+
if (Array.isArray(olCoords) && Array.isArray(olCoords[0])) {
|
|
545
|
+
// Multi-coordinate structures (LineString, Polygon, etc.)
|
|
546
|
+
coordinates = olCoords;
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
// Single point
|
|
550
|
+
coordinates = olCoords;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
id: olFeature.getId()?.toString() ?? `feature-${Math.random().toString(36).slice(2)}`,
|
|
555
|
+
geometry: {
|
|
556
|
+
type: geomType,
|
|
557
|
+
coordinates,
|
|
558
|
+
},
|
|
559
|
+
properties: olFeature.getProperties(),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Converts an internal Feature to an OpenLayers feature.
|
|
564
|
+
*/
|
|
565
|
+
function featureToOlFeature(feature) {
|
|
566
|
+
const geom = feature.geometry;
|
|
567
|
+
let geometry;
|
|
568
|
+
if (!geom.coordinates) {
|
|
569
|
+
geometry = new Point([0, 0]);
|
|
570
|
+
}
|
|
571
|
+
else if (geom.type === 'Point') {
|
|
572
|
+
const coords = geom.coordinates;
|
|
573
|
+
geometry = new Point(fromLonLat(coords));
|
|
574
|
+
}
|
|
575
|
+
else if (geom.type === 'LineString') {
|
|
576
|
+
const coords = geom.coordinates.map((c) => fromLonLat(c));
|
|
577
|
+
geometry = new LineString(coords);
|
|
578
|
+
}
|
|
579
|
+
else if (geom.type === 'Polygon') {
|
|
580
|
+
const rings = geom.coordinates.map((ring) => ring.map((c) => fromLonLat(c)));
|
|
581
|
+
geometry = new Polygon(rings);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
geometry = new Point([0, 0]);
|
|
585
|
+
}
|
|
586
|
+
// Create OL Feature
|
|
587
|
+
// Note: we must avoid passing 'geometry' as a plain object to OLFeature constructor,
|
|
588
|
+
// so we pass the object properties without it, then set geometry explicitly.
|
|
589
|
+
const props = { ...feature.properties };
|
|
590
|
+
delete props['geometry']; // Just in case
|
|
591
|
+
const olFeature = new FeatureClass(props);
|
|
592
|
+
olFeature.setGeometry(geometry);
|
|
593
|
+
olFeature.setId(feature.id);
|
|
594
|
+
return olFeature;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Creates an Angular resource for fetching and decoding GeoJSON into OpenLayers Features.
|
|
599
|
+
* Must be called in an injection context.
|
|
600
|
+
*
|
|
601
|
+
* @param url The URL signal or string to fetch data from
|
|
602
|
+
* @param options Additional vector resource options
|
|
603
|
+
* @returns An Angular Resource containing an array of parsed Features
|
|
604
|
+
*/
|
|
605
|
+
function createVectorResource(url, options) {
|
|
606
|
+
return resource({
|
|
607
|
+
loader: async ({ abortSignal }) => {
|
|
608
|
+
const fetchUrl = url();
|
|
609
|
+
if (!fetchUrl)
|
|
610
|
+
return [];
|
|
611
|
+
const cacheKey = 'ol-vector-cache-v1';
|
|
612
|
+
const isBrowser = typeof caches !== 'undefined';
|
|
613
|
+
let response;
|
|
614
|
+
let cache;
|
|
615
|
+
if (isBrowser) {
|
|
616
|
+
cache = await caches.open(cacheKey);
|
|
617
|
+
const cachedResponse = await cache.match(fetchUrl);
|
|
618
|
+
if (cachedResponse) {
|
|
619
|
+
response = cachedResponse;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (!response) {
|
|
623
|
+
response = await fetch(fetchUrl, {
|
|
624
|
+
...options?.fetchOptions,
|
|
625
|
+
signal: abortSignal,
|
|
626
|
+
});
|
|
627
|
+
if (!response.ok) {
|
|
628
|
+
throw new Error(`Failed to fetch vector data: ${response.statusText}`);
|
|
629
|
+
}
|
|
630
|
+
if (isBrowser && cache) {
|
|
631
|
+
await cache.put(fetchUrl, response.clone());
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// We process the text since GeoJSON readFeatures accepts string or object.
|
|
635
|
+
// Doing text() might be slightly faster before passing to OL's parser.
|
|
636
|
+
const data = await response.text();
|
|
637
|
+
const format = new GeoJSON();
|
|
638
|
+
// We parse the GeoJSON string into OpenLayers features.
|
|
639
|
+
const olFeatures = format.readFeatures(data);
|
|
640
|
+
// Convert to our generic Feature interface expected by OlVectorLayer
|
|
641
|
+
return olFeatures.map((f) => olFeatureToFeature(f));
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
433
646
|
// Provider functions
|
|
434
647
|
function provideOpenLayers(...features) {
|
|
435
648
|
return makeEnvironmentProviders([
|
|
@@ -478,4 +691,4 @@ function withProjections(proj4, definitions) {
|
|
|
478
691
|
* Generated bundle index. Do not edit.
|
|
479
692
|
*/
|
|
480
693
|
|
|
481
|
-
export { OlGeometryService, OlMapComponent, OlMapService, OlZoneHelper, provideOpenLayers, withProjections };
|
|
694
|
+
export { OlGeometryService, OlMapComponent, OlMapService, OlTimeService, OlZoneHelper, createVectorResource, featureToOlFeature, olFeatureToFeature, provideOpenLayers, withProjections };
|