@angular-helpers/openlayers 0.6.0 → 0.8.0
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 +62 -0
- package/fesm2022/angular-helpers-openlayers-controls.mjs +241 -13
- package/fesm2022/angular-helpers-openlayers-interactions.mjs +0 -3
- package/fesm2022/angular-helpers-openlayers-layers.mjs +1 -1
- package/fesm2022/angular-helpers-openlayers-testing.mjs +15 -0
- package/package.json +6 -1
- package/types/angular-helpers-openlayers-controls.d.ts +53 -3
- package/types/angular-helpers-openlayers-core.d.ts +2 -2
- package/types/angular-helpers-openlayers-interactions.d.ts +11 -11
- package/types/angular-helpers-openlayers-layers.d.ts +15 -15
- package/types/angular-helpers-openlayers-overlays.d.ts +2 -2
- package/types/angular-helpers-openlayers-testing.d.ts +5 -0
package/README.md
CHANGED
|
@@ -276,6 +276,68 @@ Renders points, lines, and polygons using WebGL 2. For peak performance, hit det
|
|
|
276
276
|
|
|
277
277
|
Rigorous cleanup guarantees that WebGL contexts, framebuffers, and active buffers are fully released on destroy (`layer.dispose()`), preventing GPU leaks.
|
|
278
278
|
|
|
279
|
+
## Time-Series Animation & Playback
|
|
280
|
+
|
|
281
|
+
Available since `0.5.0` from `@angular-helpers/openlayers/core` and `@angular-helpers/openlayers/controls`.
|
|
282
|
+
|
|
283
|
+
Provides high-performance, GPU-friendly reactive animation controls for time-series geospatial visualization (e.g. weather radar, vehicle tracking, historical paths).
|
|
284
|
+
|
|
285
|
+
### 1. `OlTimeService` — 60FPS Animation Loop
|
|
286
|
+
|
|
287
|
+
The core service coordinates playback timing. It runs its timing ticks entirely outside the Angular zone (using `requestAnimationFrame`) to prevent triggering global Angular change detection cycles at 60FPS, while exposing reactive signals for component consumption.
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { inject, Component } from '@angular/core';
|
|
291
|
+
import { OlTimeService } from '@angular-helpers/openlayers/core';
|
|
292
|
+
|
|
293
|
+
@Component({
|
|
294
|
+
template: `<p>Current Time: {{ timeSvc.currentTime() }}</p>`,
|
|
295
|
+
})
|
|
296
|
+
export class MapAnimation {
|
|
297
|
+
protected timeSvc = inject(OlTimeService);
|
|
298
|
+
|
|
299
|
+
constructor() {
|
|
300
|
+
// Start animation loop
|
|
301
|
+
this.timeSvc.play();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### 2. `<ol-timeline>` — Premium Playback UI Control
|
|
307
|
+
|
|
308
|
+
A sleek, glassmorphic UI overlay component displaying a timeline scrubber, play/pause toggle, and playback speed multiplier.
|
|
309
|
+
|
|
310
|
+
```html
|
|
311
|
+
<ol-map [center]="[2.17, 41.38]" [zoom]="12">
|
|
312
|
+
<ol-tile-layer source="osm" />
|
|
313
|
+
|
|
314
|
+
<ol-timeline
|
|
315
|
+
[startTime]="1700000000000"
|
|
316
|
+
[endTime]="1700086400000"
|
|
317
|
+
[playSpeed]="60"
|
|
318
|
+
[loop]="true"
|
|
319
|
+
position="bottom-center"
|
|
320
|
+
(timeChange)="onTimeChange($event)"
|
|
321
|
+
/>
|
|
322
|
+
</ol-map>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
| Input | Type | Default | Description |
|
|
326
|
+
| ------------- | -------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
327
|
+
| `startTime` | `number` | _Required_ | Start bounds of the time-series (Epoch ms) |
|
|
328
|
+
| `endTime` | `number` | _Required_ | End bounds of the time-series (Epoch ms) |
|
|
329
|
+
| `playSpeed` | `number` | `1` | Default speed multiplier (e.g. 1, 5, 10, 60, 3600) |
|
|
330
|
+
| `loop` | `boolean` | `false` | Loop playback when reaching `endTime` |
|
|
331
|
+
| `position` | `TimelinePosition` | `'bottom-center'` | Control alignment (`top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center`, `bottom-right`) |
|
|
332
|
+
| `formatLabel` | `(time: number) => string` | `new Date(t).toLocaleString()` | Custom label formatter for the time display |
|
|
333
|
+
|
|
334
|
+
| Output | Type | Description |
|
|
335
|
+
| ----------------- | --------- | --------------------------------------------------------------------------------- |
|
|
336
|
+
| `timeChange` | `number` | Emitted with the current active epoch timestamp when the timeline ticks or scrubs |
|
|
337
|
+
| `playStateChange` | `boolean` | Emitted when playback transitions between playing (`true`) and paused (`false`) |
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
279
341
|
## Geodesic Geometry Helpers
|
|
280
342
|
|
|
281
343
|
Available from `@angular-helpers/openlayers/core` via `OlGeometryService`.
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, input, DestroyRef, afterNextRender, ChangeDetectionStrategy, Component, InjectionToken, output, signal, ViewChild, Injectable } from '@angular/core';
|
|
3
|
-
import { OlMapService, OlZoneHelper } from '@angular-helpers/openlayers/core';
|
|
2
|
+
import { inject, input, DestroyRef, afterNextRender, ChangeDetectionStrategy, Component, InjectionToken, output, signal, ViewChild, Injectable, computed, effect } from '@angular/core';
|
|
3
|
+
import { OlMapService, OlZoneHelper, OlTimeService } from '@angular-helpers/openlayers/core';
|
|
4
4
|
import Zoom from 'ol/control/Zoom';
|
|
5
5
|
import Attribution from 'ol/control/Attribution';
|
|
6
6
|
import ScaleLine from 'ol/control/ScaleLine';
|
|
7
7
|
import FullScreen from 'ol/control/FullScreen';
|
|
8
8
|
import Rotate from 'ol/control/Rotate';
|
|
9
|
-
import * as i1 from '@angular/common';
|
|
10
|
-
import { CommonModule } from '@angular/common';
|
|
11
9
|
import Control from 'ol/control/Control';
|
|
12
10
|
import Geolocation from 'ol/Geolocation';
|
|
13
11
|
import Feature from 'ol/Feature';
|
|
@@ -383,11 +381,11 @@ class OlLayerSwitcherComponent {
|
|
|
383
381
|
</div>
|
|
384
382
|
}
|
|
385
383
|
</div>
|
|
386
|
-
`, isInline: true, styles: [":host{display:block}.ol-layer-switcher{position:absolute;background:#fffffff2;color:#333;border-radius:4px;border:none;box-shadow:0 1px 4px #0000004d;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;min-width:36px;z-index:10;transition:all .2s ease}.ol-layer-switcher--top-left{top:.5em;left:.5em}.ol-layer-switcher--top-right{top:10em;right:.5em}.ol-layer-switcher--bottom-left{bottom:.5em;left:.5em}.ol-layer-switcher--bottom-right{bottom:.5em;right:.5em}.ol-layer-switcher.collapsed{min-width:auto}.ol-layer-switcher__toggle{display:flex;align-items:center;gap:6px;padding:4px 8px;background:transparent;color:#333;border:none;border-bottom:1px solid rgba(0,0,0,.08);border-radius:4px 4px 0 0;cursor:pointer;width:100%;font-size:13px;font-weight:600;transition:background .15s ease;min-height:36px}.ol-layer-switcher.collapsed .ol-layer-switcher__toggle{border-bottom:none;border-radius:4px;padding:4px 6px;justify-content:center}.ol-layer-switcher__toggle:hover{background:#0000000d}.ol-layer-switcher__icon{font-size:14px;line-height:1}.ol-layer-switcher__title{font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.5px}.ol-layer-switcher.collapsed .ol-layer-switcher__title,.ol-layer-switcher.collapsed .ol-layer-switcher__panel{display:none}.ol-layer-switcher__panel{padding:6px;max-height:300px;overflow-y:auto}.ol-layer-switcher__empty{padding:12px;color:#6b7280;text-align:center;font-style:italic;font-size:12px}.ol-layer-switcher__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}.ol-layer-switcher__item{padding:5px 8px;border-radius:3px;transition:background .15s ease}.ol-layer-switcher__item:hover{background:#0000000a}.ol-layer-switcher__label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px}.ol-layer-switcher__checkbox{cursor:pointer;accent-color:#1a73e8}.ol-layer-switcher__name{flex:1;font-weight:500;color:#333;font-size:12px}.ol-layer-switcher__type{font-size:9px;padding:2px 5px;border-radius:3px;font-weight:700;text-transform:uppercase;background:#0000000f;color:#555;letter-spacing:.3px}.ol-layer-switcher__type--vector{background:#3b82f61f;color:#2563eb}.ol-layer-switcher__type--tile{background:#22c55e1f;color:#16a34a}.ol-layer-switcher__type--image{background:#f59e0b1f;color:#d97706}.ol-layer-switcher__opacity{width:100%;margin-top:4px;cursor:pointer;height:4px;accent-color:#1a73e8}\n"],
|
|
384
|
+
`, isInline: true, styles: [":host{display:block}.ol-layer-switcher{position:absolute;background:#fffffff2;color:#333;border-radius:4px;border:none;box-shadow:0 1px 4px #0000004d;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;min-width:36px;z-index:10;transition:all .2s ease}.ol-layer-switcher--top-left{top:.5em;left:.5em}.ol-layer-switcher--top-right{top:10em;right:.5em}.ol-layer-switcher--bottom-left{bottom:.5em;left:.5em}.ol-layer-switcher--bottom-right{bottom:.5em;right:.5em}.ol-layer-switcher.collapsed{min-width:auto}.ol-layer-switcher__toggle{display:flex;align-items:center;gap:6px;padding:4px 8px;background:transparent;color:#333;border:none;border-bottom:1px solid rgba(0,0,0,.08);border-radius:4px 4px 0 0;cursor:pointer;width:100%;font-size:13px;font-weight:600;transition:background .15s ease;min-height:36px}.ol-layer-switcher.collapsed .ol-layer-switcher__toggle{border-bottom:none;border-radius:4px;padding:4px 6px;justify-content:center}.ol-layer-switcher__toggle:hover{background:#0000000d}.ol-layer-switcher__icon{font-size:14px;line-height:1}.ol-layer-switcher__title{font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.5px}.ol-layer-switcher.collapsed .ol-layer-switcher__title,.ol-layer-switcher.collapsed .ol-layer-switcher__panel{display:none}.ol-layer-switcher__panel{padding:6px;max-height:300px;overflow-y:auto}.ol-layer-switcher__empty{padding:12px;color:#6b7280;text-align:center;font-style:italic;font-size:12px}.ol-layer-switcher__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}.ol-layer-switcher__item{padding:5px 8px;border-radius:3px;transition:background .15s ease}.ol-layer-switcher__item:hover{background:#0000000a}.ol-layer-switcher__label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px}.ol-layer-switcher__checkbox{cursor:pointer;accent-color:#1a73e8}.ol-layer-switcher__name{flex:1;font-weight:500;color:#333;font-size:12px}.ol-layer-switcher__type{font-size:9px;padding:2px 5px;border-radius:3px;font-weight:700;text-transform:uppercase;background:#0000000f;color:#555;letter-spacing:.3px}.ol-layer-switcher__type--vector{background:#3b82f61f;color:#2563eb}.ol-layer-switcher__type--tile{background:#22c55e1f;color:#16a34a}.ol-layer-switcher__type--image{background:#f59e0b1f;color:#d97706}.ol-layer-switcher__opacity{width:100%;margin-top:4px;cursor:pointer;height:4px;accent-color:#1a73e8}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
387
385
|
}
|
|
388
386
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlLayerSwitcherComponent, decorators: [{
|
|
389
387
|
type: Component,
|
|
390
|
-
args: [{ selector: 'ol-layer-switcher',
|
|
388
|
+
args: [{ selector: 'ol-layer-switcher', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
391
389
|
<div
|
|
392
390
|
class="ol-layer-switcher"
|
|
393
391
|
[class.collapsed]="isCollapsed()"
|
|
@@ -578,11 +576,11 @@ class OlBasemapSwitcherComponent {
|
|
|
578
576
|
</span>
|
|
579
577
|
</button>
|
|
580
578
|
</div>
|
|
581
|
-
`, isInline: true, styles: [":host{display:block}.ol-basemap-switcher{position:absolute;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;color:#333;z-index:10}.ol-basemap-switcher--top-left{top:.5em;left:.5em}.ol-basemap-switcher--top-center{top:.5em;left:50%;transform:translate(-50%)}.ol-basemap-switcher--top-right{top:.5em;right:.5em}.ol-basemap-switcher--bottom-left{bottom:.5em;left:.5em}.ol-basemap-switcher--bottom-center{bottom:.5em;left:50%;transform:translate(-50%)}.ol-basemap-switcher--bottom-right{bottom:.5em;right:.5em}.ol-basemap-switcher__toggle{display:flex;align-items:center;gap:6px;padding:4px 10px;background:#fffffff2;color:#333;border:none;border-radius:4px;box-shadow:0 1px 4px #0000004d;cursor:pointer;font-size:12px;font-weight:600;transition:background .15s ease;min-height:36px}.ol-basemap-switcher__toggle:hover{background:#fff}.ol-basemap-switcher__toggle-icon{font-size:14px;line-height:1}.ol-basemap-switcher__toggle-text{font-weight:600;font-size:12px}.ol-basemap-switcher__panel{position:absolute;bottom:calc(100% + 6px);left:0;background:#fffffff2;border:none;color:#333;border-radius:4px;box-shadow:0 1px 4px #0000004d;padding:4px;min-width:160px;display:flex;flex-direction:column;gap:2px}.ol-basemap-switcher--bottom-right .ol-basemap-switcher__panel,.ol-basemap-switcher--top-right .ol-basemap-switcher__panel{left:auto;right:0}.ol-basemap-switcher--top-left .ol-basemap-switcher__panel,.ol-basemap-switcher--top-center .ol-basemap-switcher__panel,.ol-basemap-switcher--top-right .ol-basemap-switcher__panel{bottom:auto;top:calc(100% + 6px)}.ol-basemap-switcher--top-center .ol-basemap-switcher__panel,.ol-basemap-switcher--bottom-center .ol-basemap-switcher__panel{left:50%;transform:translate(-50%)}.ol-basemap-switcher__item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:none;background:transparent;border-radius:3px;cursor:pointer;text-align:left;font-size:12px;transition:background .15s ease}.ol-basemap-switcher__item:hover{background:#0000000d}.ol-basemap-switcher__item--active{background:#1a73e81f;color:#1a73e8;font-weight:600}.ol-basemap-switcher__item--active:hover{background:#1a73e82e}.ol-basemap-switcher__icon{font-size:14px}.ol-basemap-switcher__name{font-weight:500;color:#333;font-size:12px}\n"],
|
|
579
|
+
`, isInline: true, styles: [":host{display:block}.ol-basemap-switcher{position:absolute;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;color:#333;z-index:10}.ol-basemap-switcher--top-left{top:.5em;left:.5em}.ol-basemap-switcher--top-center{top:.5em;left:50%;transform:translate(-50%)}.ol-basemap-switcher--top-right{top:.5em;right:.5em}.ol-basemap-switcher--bottom-left{bottom:.5em;left:.5em}.ol-basemap-switcher--bottom-center{bottom:.5em;left:50%;transform:translate(-50%)}.ol-basemap-switcher--bottom-right{bottom:.5em;right:.5em}.ol-basemap-switcher__toggle{display:flex;align-items:center;gap:6px;padding:4px 10px;background:#fffffff2;color:#333;border:none;border-radius:4px;box-shadow:0 1px 4px #0000004d;cursor:pointer;font-size:12px;font-weight:600;transition:background .15s ease;min-height:36px}.ol-basemap-switcher__toggle:hover{background:#fff}.ol-basemap-switcher__toggle-icon{font-size:14px;line-height:1}.ol-basemap-switcher__toggle-text{font-weight:600;font-size:12px}.ol-basemap-switcher__panel{position:absolute;bottom:calc(100% + 6px);left:0;background:#fffffff2;border:none;color:#333;border-radius:4px;box-shadow:0 1px 4px #0000004d;padding:4px;min-width:160px;display:flex;flex-direction:column;gap:2px}.ol-basemap-switcher--bottom-right .ol-basemap-switcher__panel,.ol-basemap-switcher--top-right .ol-basemap-switcher__panel{left:auto;right:0}.ol-basemap-switcher--top-left .ol-basemap-switcher__panel,.ol-basemap-switcher--top-center .ol-basemap-switcher__panel,.ol-basemap-switcher--top-right .ol-basemap-switcher__panel{bottom:auto;top:calc(100% + 6px)}.ol-basemap-switcher--top-center .ol-basemap-switcher__panel,.ol-basemap-switcher--bottom-center .ol-basemap-switcher__panel{left:50%;transform:translate(-50%)}.ol-basemap-switcher__item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:none;background:transparent;border-radius:3px;cursor:pointer;text-align:left;font-size:12px;transition:background .15s ease}.ol-basemap-switcher__item:hover{background:#0000000d}.ol-basemap-switcher__item--active{background:#1a73e81f;color:#1a73e8;font-weight:600}.ol-basemap-switcher__item--active:hover{background:#1a73e82e}.ol-basemap-switcher__icon{font-size:14px}.ol-basemap-switcher__name{font-weight:500;color:#333;font-size:12px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
582
580
|
}
|
|
583
581
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlBasemapSwitcherComponent, decorators: [{
|
|
584
582
|
type: Component,
|
|
585
|
-
args: [{ selector: 'ol-basemap-switcher',
|
|
583
|
+
args: [{ selector: 'ol-basemap-switcher', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
586
584
|
<div
|
|
587
585
|
class="ol-basemap-switcher"
|
|
588
586
|
[class.ol-basemap-switcher--top-left]="position() === 'top-left'"
|
|
@@ -753,7 +751,7 @@ class OlGeolocationControlComponent {
|
|
|
753
751
|
<div
|
|
754
752
|
#controlElement
|
|
755
753
|
class="ol-geolocation-control ol-unselectable ol-control"
|
|
756
|
-
[
|
|
754
|
+
[class]="position()"
|
|
757
755
|
>
|
|
758
756
|
<button
|
|
759
757
|
type="button"
|
|
@@ -780,15 +778,15 @@ class OlGeolocationControlComponent {
|
|
|
780
778
|
</svg>
|
|
781
779
|
</button>
|
|
782
780
|
</div>
|
|
783
|
-
`, isInline: true, styles: [".ol-geolocation-control{position:absolute}.ol-geolocation-control.top-left{top:4.5em;left:.5em}.ol-geolocation-control.top-right{top:4.5em;right:.5em}.ol-geolocation-control.bottom-left{bottom:.5em;left:.5em}.ol-geolocation-control.bottom-right{bottom:.5em;right:.5em}button{display:flex;align-items:center;justify-content:center;width:1.375em;height:1.375em;padding:0;background-color:#fff6;border:none;cursor:pointer;border-radius:2px;color:#333;transition:all .2s}button:hover{background-color:#fffc}button.active{color:#3b82f6;background-color:#ffffffe6}.geolocation-icon{width:1em;height:1em}\n"],
|
|
781
|
+
`, isInline: true, styles: [".ol-geolocation-control{position:absolute}.ol-geolocation-control.top-left{top:4.5em;left:.5em}.ol-geolocation-control.top-right{top:4.5em;right:.5em}.ol-geolocation-control.bottom-left{bottom:.5em;left:.5em}.ol-geolocation-control.bottom-right{bottom:.5em;right:.5em}button{display:flex;align-items:center;justify-content:center;width:1.375em;height:1.375em;padding:0;background-color:#fff6;border:none;cursor:pointer;border-radius:2px;color:#333;transition:all .2s}button:hover{background-color:#fffc}button.active{color:#3b82f6;background-color:#ffffffe6}.geolocation-icon{width:1em;height:1em}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
784
782
|
}
|
|
785
783
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlGeolocationControlComponent, decorators: [{
|
|
786
784
|
type: Component,
|
|
787
|
-
args: [{ selector: 'ol-geolocation-control',
|
|
785
|
+
args: [{ selector: 'ol-geolocation-control', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
788
786
|
<div
|
|
789
787
|
#controlElement
|
|
790
788
|
class="ol-geolocation-control ol-unselectable ol-control"
|
|
791
|
-
[
|
|
789
|
+
[class]="position()"
|
|
792
790
|
>
|
|
793
791
|
<button
|
|
794
792
|
type="button"
|
|
@@ -857,10 +855,240 @@ function provideControls() {
|
|
|
857
855
|
return withControls();
|
|
858
856
|
}
|
|
859
857
|
|
|
858
|
+
/**
|
|
859
|
+
* A premium, reactive visual timeline component for OpenLayers.
|
|
860
|
+
* Orchestrates time-series animations by binding directly to OlTimeService.
|
|
861
|
+
*
|
|
862
|
+
* Uses native controls, CSS grid/flex layouts, and a sleek glassmorphic theme.
|
|
863
|
+
* Completely free of CommonModule or FormsModule dependencies for maximum performance.
|
|
864
|
+
*
|
|
865
|
+
* @usageNotes
|
|
866
|
+
* ```html
|
|
867
|
+
* <ol-timeline
|
|
868
|
+
* [startTime]="1700000000000"
|
|
869
|
+
* [endTime]="1700086400000"
|
|
870
|
+
* [playSpeed]="60"
|
|
871
|
+
* [loop]="true"
|
|
872
|
+
* position="bottom-center">
|
|
873
|
+
* </ol-timeline>
|
|
874
|
+
* ```
|
|
875
|
+
*/
|
|
876
|
+
class OlTimelineComponent {
|
|
877
|
+
timeService = inject(OlTimeService);
|
|
878
|
+
/** Start bounds of time-series (Epoch ms) */
|
|
879
|
+
startTime = input.required(...(ngDevMode ? [{ debugName: "startTime" }] : /* istanbul ignore next */ []));
|
|
880
|
+
/** End bounds of time-series (Epoch ms) */
|
|
881
|
+
endTime = input.required(...(ngDevMode ? [{ debugName: "endTime" }] : /* istanbul ignore next */ []));
|
|
882
|
+
/** Default speed multiplier (e.g. 1, 5, 10, 60, 3600) */
|
|
883
|
+
playSpeed = input(1, ...(ngDevMode ? [{ debugName: "playSpeed" }] : /* istanbul ignore next */ []));
|
|
884
|
+
/** Loop playback when reaching endTime */
|
|
885
|
+
loop = input(false, ...(ngDevMode ? [{ debugName: "loop" }] : /* istanbul ignore next */ []));
|
|
886
|
+
/** Position overlay alignment */
|
|
887
|
+
position = input('bottom-center', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
|
|
888
|
+
/** Custom label formatter */
|
|
889
|
+
formatLabel = input((t) => new Date(t).toLocaleString(), ...(ngDevMode ? [{ debugName: "formatLabel" }] : /* istanbul ignore next */ []));
|
|
890
|
+
/** Outputs */
|
|
891
|
+
timeChange = output();
|
|
892
|
+
playStateChange = output();
|
|
893
|
+
/** Computeds binding directly to OlTimeService */
|
|
894
|
+
currentTime = computed(() => this.timeService.currentTime(), ...(ngDevMode ? [{ debugName: "currentTime" }] : /* istanbul ignore next */ []));
|
|
895
|
+
isPlaying = computed(() => this.timeService.isPlaying(), ...(ngDevMode ? [{ debugName: "isPlaying" }] : /* istanbul ignore next */ []));
|
|
896
|
+
speed = computed(() => this.timeService.speed(), ...(ngDevMode ? [{ debugName: "speed" }] : /* istanbul ignore next */ []));
|
|
897
|
+
formattedTime = computed(() => this.formatLabel()(this.currentTime()), ...(ngDevMode ? [{ debugName: "formattedTime" }] : /* istanbul ignore next */ []));
|
|
898
|
+
constructor() {
|
|
899
|
+
// Sync default configuration settings when the inputs initialize
|
|
900
|
+
effect(() => {
|
|
901
|
+
this.timeService.setSpeed(this.playSpeed());
|
|
902
|
+
});
|
|
903
|
+
// Make sure initial time starts at the startTime bounds
|
|
904
|
+
effect(() => {
|
|
905
|
+
this.timeService.setTime(this.startTime());
|
|
906
|
+
});
|
|
907
|
+
// Reactive time-based loop boundaries check
|
|
908
|
+
effect(() => {
|
|
909
|
+
const current = this.currentTime();
|
|
910
|
+
const end = this.endTime();
|
|
911
|
+
const isAnimPlaying = this.isPlaying();
|
|
912
|
+
if (current >= end && isAnimPlaying) {
|
|
913
|
+
if (this.loop()) {
|
|
914
|
+
this.timeService.setTime(this.startTime());
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
this.timeService.pause();
|
|
918
|
+
this.timeService.setTime(end);
|
|
919
|
+
this.playStateChange.emit(false);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
// Emit reactive output when current time advances
|
|
924
|
+
effect(() => {
|
|
925
|
+
this.timeChange.emit(this.currentTime());
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
togglePlay() {
|
|
929
|
+
if (this.isPlaying()) {
|
|
930
|
+
this.timeService.pause();
|
|
931
|
+
this.playStateChange.emit(false);
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
// If we are at the end, reset to start before playing
|
|
935
|
+
if (this.currentTime() >= this.endTime()) {
|
|
936
|
+
this.timeService.setTime(this.startTime());
|
|
937
|
+
}
|
|
938
|
+
this.timeService.play();
|
|
939
|
+
this.playStateChange.emit(true);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
onScrub(event) {
|
|
943
|
+
const target = event.target;
|
|
944
|
+
const value = Number(target.value);
|
|
945
|
+
this.timeService.setTime(value);
|
|
946
|
+
}
|
|
947
|
+
onSpeedChange(event) {
|
|
948
|
+
const target = event.target;
|
|
949
|
+
const value = Number(target.value);
|
|
950
|
+
this.timeService.setSpeed(value);
|
|
951
|
+
}
|
|
952
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTimelineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
953
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: OlTimelineComponent, isStandalone: true, selector: "ol-timeline", inputs: { startTime: { classPropertyName: "startTime", publicName: "startTime", isSignal: true, isRequired: true, transformFunction: null }, endTime: { classPropertyName: "endTime", publicName: "endTime", isSignal: true, isRequired: true, transformFunction: null }, playSpeed: { classPropertyName: "playSpeed", publicName: "playSpeed", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, formatLabel: { classPropertyName: "formatLabel", publicName: "formatLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { timeChange: "timeChange", playStateChange: "playStateChange" }, ngImport: i0, template: `
|
|
954
|
+
<div
|
|
955
|
+
class="ol-timeline"
|
|
956
|
+
[class.ol-timeline--top-left]="position() === 'top-left'"
|
|
957
|
+
[class.ol-timeline--top-center]="position() === 'top-center'"
|
|
958
|
+
[class.ol-timeline--top-right]="position() === 'top-right'"
|
|
959
|
+
[class.ol-timeline--bottom-left]="position() === 'bottom-left'"
|
|
960
|
+
[class.ol-timeline--bottom-center]="position() === 'bottom-center'"
|
|
961
|
+
[class.ol-timeline--bottom-right]="position() === 'bottom-right'"
|
|
962
|
+
>
|
|
963
|
+
<div class="ol-timeline__controls">
|
|
964
|
+
<button
|
|
965
|
+
type="button"
|
|
966
|
+
class="ol-timeline__btn ol-timeline__btn--play"
|
|
967
|
+
(click)="togglePlay()"
|
|
968
|
+
[attr.aria-label]="isPlaying() ? 'Pause animation' : 'Play animation'"
|
|
969
|
+
>
|
|
970
|
+
@if (isPlaying()) {
|
|
971
|
+
<!-- Pause Icon -->
|
|
972
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
973
|
+
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
|
|
974
|
+
</svg>
|
|
975
|
+
} @else {
|
|
976
|
+
<!-- Play Icon -->
|
|
977
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
978
|
+
<path d="M8 5v14l11-7z" />
|
|
979
|
+
</svg>
|
|
980
|
+
}
|
|
981
|
+
</button>
|
|
982
|
+
|
|
983
|
+
<span class="ol-timeline__time-display" aria-live="polite">
|
|
984
|
+
{{ formattedTime() }}
|
|
985
|
+
</span>
|
|
986
|
+
</div>
|
|
987
|
+
|
|
988
|
+
<div class="ol-timeline__slider-container">
|
|
989
|
+
<input
|
|
990
|
+
type="range"
|
|
991
|
+
class="ol-timeline__slider"
|
|
992
|
+
[min]="startTime()"
|
|
993
|
+
[max]="endTime()"
|
|
994
|
+
[value]="currentTime()"
|
|
995
|
+
(input)="onScrub($event)"
|
|
996
|
+
aria-label="Timeline progress slider"
|
|
997
|
+
/>
|
|
998
|
+
</div>
|
|
999
|
+
|
|
1000
|
+
<div class="ol-timeline__settings">
|
|
1001
|
+
<select
|
|
1002
|
+
class="ol-timeline__speed-select"
|
|
1003
|
+
[value]="speed()"
|
|
1004
|
+
(change)="onSpeedChange($event)"
|
|
1005
|
+
aria-label="Playback speed multiplier"
|
|
1006
|
+
>
|
|
1007
|
+
<option [value]="1">1x (Real)</option>
|
|
1008
|
+
<option [value]="5">5x</option>
|
|
1009
|
+
<option [value]="10">10x</option>
|
|
1010
|
+
<option [value]="30">30x</option>
|
|
1011
|
+
<option [value]="60">60x (1m/s)</option>
|
|
1012
|
+
<option [value]="300">300x (5m/s)</option>
|
|
1013
|
+
<option [value]="3600">3600x (1h/s)</option>
|
|
1014
|
+
</select>
|
|
1015
|
+
</div>
|
|
1016
|
+
</div>
|
|
1017
|
+
`, isInline: true, styles: [":host{display:block}.ol-timeline{position:absolute;display:flex;align-items:center;gap:16px;padding:8px 16px;background:#1e1e1ebf;backdrop-filter:blur(12px) saturate(160%);-webkit-backdrop-filter:blur(12px) saturate(160%);border:1px solid rgba(255,255,255,.08);border-radius:24px;box-shadow:0 8px 32px #0000004d;color:#f3f3f3;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;z-index:10;width:calc(100% - 32px);max-width:600px;box-sizing:border-box}.ol-timeline--top-left{top:12px;left:12px}.ol-timeline--top-center{top:12px;left:50%;transform:translate(-50%)}.ol-timeline--top-right{top:12px;right:12px}.ol-timeline--bottom-left{bottom:12px;left:12px}.ol-timeline--bottom-center{bottom:24px;left:50%;transform:translate(-50%)}.ol-timeline--bottom-right{bottom:12px;right:12px}.ol-timeline__controls{display:flex;align-items:center;gap:12px;flex-shrink:0}.ol-timeline__btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;border-radius:50%;background:#ffffff1a;color:#fff;cursor:pointer;transition:background .15s ease,transform .15s ease}.ol-timeline__btn:hover{background:#fff3;transform:scale(1.05)}.ol-timeline__btn:active{transform:scale(.95)}.ol-timeline__time-display{font-variant-numeric:tabular-nums;font-weight:500;color:#e0e0e0;min-width:140px;text-align:center}.ol-timeline__slider-container{flex-grow:1;display:flex;align-items:center}.ol-timeline__slider{-webkit-appearance:none;width:100%;height:4px;border-radius:2px;background:#fff3;outline:none;cursor:pointer;transition:background .15s ease}.ol-timeline__slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#1a73e8;box-shadow:0 1px 4px #0006;cursor:pointer;transition:background .15s ease,transform .15s ease}.ol-timeline__slider::-webkit-slider-thumb:hover{background:#2b84f0;transform:scale(1.15)}.ol-timeline__slider::-moz-range-thumb{width:14px;height:14px;border:none;border-radius:50%;background:#1a73e8;box-shadow:0 1px 4px #0006;cursor:pointer;transition:background .15s ease,transform .15s ease}.ol-timeline__slider::-moz-range-thumb:hover{background:#2b84f0;transform:scale(1.15)}.ol-timeline__settings{flex-shrink:0}.ol-timeline__speed-select{background:#ffffff1a;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:12px;padding:4px 8px;font-size:11px;font-weight:500;outline:none;cursor:pointer;transition:background .15s ease}.ol-timeline__speed-select:hover{background:#ffffff26}.ol-timeline__speed-select option{background:#1e1e1e;color:#fff}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1018
|
+
}
|
|
1019
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTimelineComponent, decorators: [{
|
|
1020
|
+
type: Component,
|
|
1021
|
+
args: [{ selector: 'ol-timeline', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1022
|
+
<div
|
|
1023
|
+
class="ol-timeline"
|
|
1024
|
+
[class.ol-timeline--top-left]="position() === 'top-left'"
|
|
1025
|
+
[class.ol-timeline--top-center]="position() === 'top-center'"
|
|
1026
|
+
[class.ol-timeline--top-right]="position() === 'top-right'"
|
|
1027
|
+
[class.ol-timeline--bottom-left]="position() === 'bottom-left'"
|
|
1028
|
+
[class.ol-timeline--bottom-center]="position() === 'bottom-center'"
|
|
1029
|
+
[class.ol-timeline--bottom-right]="position() === 'bottom-right'"
|
|
1030
|
+
>
|
|
1031
|
+
<div class="ol-timeline__controls">
|
|
1032
|
+
<button
|
|
1033
|
+
type="button"
|
|
1034
|
+
class="ol-timeline__btn ol-timeline__btn--play"
|
|
1035
|
+
(click)="togglePlay()"
|
|
1036
|
+
[attr.aria-label]="isPlaying() ? 'Pause animation' : 'Play animation'"
|
|
1037
|
+
>
|
|
1038
|
+
@if (isPlaying()) {
|
|
1039
|
+
<!-- Pause Icon -->
|
|
1040
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
1041
|
+
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
|
|
1042
|
+
</svg>
|
|
1043
|
+
} @else {
|
|
1044
|
+
<!-- Play Icon -->
|
|
1045
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
1046
|
+
<path d="M8 5v14l11-7z" />
|
|
1047
|
+
</svg>
|
|
1048
|
+
}
|
|
1049
|
+
</button>
|
|
1050
|
+
|
|
1051
|
+
<span class="ol-timeline__time-display" aria-live="polite">
|
|
1052
|
+
{{ formattedTime() }}
|
|
1053
|
+
</span>
|
|
1054
|
+
</div>
|
|
1055
|
+
|
|
1056
|
+
<div class="ol-timeline__slider-container">
|
|
1057
|
+
<input
|
|
1058
|
+
type="range"
|
|
1059
|
+
class="ol-timeline__slider"
|
|
1060
|
+
[min]="startTime()"
|
|
1061
|
+
[max]="endTime()"
|
|
1062
|
+
[value]="currentTime()"
|
|
1063
|
+
(input)="onScrub($event)"
|
|
1064
|
+
aria-label="Timeline progress slider"
|
|
1065
|
+
/>
|
|
1066
|
+
</div>
|
|
1067
|
+
|
|
1068
|
+
<div class="ol-timeline__settings">
|
|
1069
|
+
<select
|
|
1070
|
+
class="ol-timeline__speed-select"
|
|
1071
|
+
[value]="speed()"
|
|
1072
|
+
(change)="onSpeedChange($event)"
|
|
1073
|
+
aria-label="Playback speed multiplier"
|
|
1074
|
+
>
|
|
1075
|
+
<option [value]="1">1x (Real)</option>
|
|
1076
|
+
<option [value]="5">5x</option>
|
|
1077
|
+
<option [value]="10">10x</option>
|
|
1078
|
+
<option [value]="30">30x</option>
|
|
1079
|
+
<option [value]="60">60x (1m/s)</option>
|
|
1080
|
+
<option [value]="300">300x (5m/s)</option>
|
|
1081
|
+
<option [value]="3600">3600x (1h/s)</option>
|
|
1082
|
+
</select>
|
|
1083
|
+
</div>
|
|
1084
|
+
</div>
|
|
1085
|
+
`, styles: [":host{display:block}.ol-timeline{position:absolute;display:flex;align-items:center;gap:16px;padding:8px 16px;background:#1e1e1ebf;backdrop-filter:blur(12px) saturate(160%);-webkit-backdrop-filter:blur(12px) saturate(160%);border:1px solid rgba(255,255,255,.08);border-radius:24px;box-shadow:0 8px 32px #0000004d;color:#f3f3f3;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:13px;z-index:10;width:calc(100% - 32px);max-width:600px;box-sizing:border-box}.ol-timeline--top-left{top:12px;left:12px}.ol-timeline--top-center{top:12px;left:50%;transform:translate(-50%)}.ol-timeline--top-right{top:12px;right:12px}.ol-timeline--bottom-left{bottom:12px;left:12px}.ol-timeline--bottom-center{bottom:24px;left:50%;transform:translate(-50%)}.ol-timeline--bottom-right{bottom:12px;right:12px}.ol-timeline__controls{display:flex;align-items:center;gap:12px;flex-shrink:0}.ol-timeline__btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;border-radius:50%;background:#ffffff1a;color:#fff;cursor:pointer;transition:background .15s ease,transform .15s ease}.ol-timeline__btn:hover{background:#fff3;transform:scale(1.05)}.ol-timeline__btn:active{transform:scale(.95)}.ol-timeline__time-display{font-variant-numeric:tabular-nums;font-weight:500;color:#e0e0e0;min-width:140px;text-align:center}.ol-timeline__slider-container{flex-grow:1;display:flex;align-items:center}.ol-timeline__slider{-webkit-appearance:none;width:100%;height:4px;border-radius:2px;background:#fff3;outline:none;cursor:pointer;transition:background .15s ease}.ol-timeline__slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#1a73e8;box-shadow:0 1px 4px #0006;cursor:pointer;transition:background .15s ease,transform .15s ease}.ol-timeline__slider::-webkit-slider-thumb:hover{background:#2b84f0;transform:scale(1.15)}.ol-timeline__slider::-moz-range-thumb{width:14px;height:14px;border:none;border-radius:50%;background:#1a73e8;box-shadow:0 1px 4px #0006;cursor:pointer;transition:background .15s ease,transform .15s ease}.ol-timeline__slider::-moz-range-thumb:hover{background:#2b84f0;transform:scale(1.15)}.ol-timeline__settings{flex-shrink:0}.ol-timeline__speed-select{background:#ffffff1a;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:12px;padding:4px 8px;font-size:11px;font-weight:500;outline:none;cursor:pointer;transition:background .15s ease}.ol-timeline__speed-select:hover{background:#ffffff26}.ol-timeline__speed-select option{background:#1e1e1e;color:#fff}\n"] }]
|
|
1086
|
+
}], ctorParameters: () => [], propDecorators: { startTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "startTime", required: true }] }], endTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "endTime", required: true }] }], playSpeed: [{ type: i0.Input, args: [{ isSignal: true, alias: "playSpeed", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], formatLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatLabel", required: false }] }], timeChange: [{ type: i0.Output, args: ["timeChange"] }], playStateChange: [{ type: i0.Output, args: ["playStateChange"] }] } });
|
|
1087
|
+
|
|
860
1088
|
// @angular-helpers/openlayers/controls
|
|
861
1089
|
|
|
862
1090
|
/**
|
|
863
1091
|
* Generated bundle index. Do not edit.
|
|
864
1092
|
*/
|
|
865
1093
|
|
|
866
|
-
export { OlAttributionControlComponent, OlBasemapSwitcherComponent, OlControlService, OlFullscreenControlComponent, OlGeolocationControlComponent, OlLayerSwitcherComponent, OlRotateControlComponent, OlScaleLineControlComponent, OlZoomControlComponent, ROTATE_CONTROL_MAP_SERVICE, provideControls, withControls };
|
|
1094
|
+
export { OlAttributionControlComponent, OlBasemapSwitcherComponent, OlControlService, OlFullscreenControlComponent, OlGeolocationControlComponent, OlLayerSwitcherComponent, OlRotateControlComponent, OlScaleLineControlComponent, OlTimelineComponent, OlZoomControlComponent, ROTATE_CONTROL_MAP_SERVICE, provideControls, withControls };
|
|
@@ -887,7 +887,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImpo
|
|
|
887
887
|
type: Component,
|
|
888
888
|
args: [{
|
|
889
889
|
selector: 'ol-draw-interaction',
|
|
890
|
-
standalone: true,
|
|
891
890
|
template: '',
|
|
892
891
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
893
892
|
}]
|
|
@@ -928,7 +927,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImpo
|
|
|
928
927
|
type: Component,
|
|
929
928
|
args: [{
|
|
930
929
|
selector: 'ol-modify-interaction',
|
|
931
|
-
standalone: true,
|
|
932
930
|
template: '',
|
|
933
931
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
934
932
|
}]
|
|
@@ -973,7 +971,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImpo
|
|
|
973
971
|
type: Component,
|
|
974
972
|
args: [{
|
|
975
973
|
selector: 'ol-select-interaction',
|
|
976
|
-
standalone: true,
|
|
977
974
|
template: '',
|
|
978
975
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
979
976
|
}]
|
|
@@ -161,7 +161,7 @@ function buildVectorLayer(config, source) {
|
|
|
161
161
|
visible: config.visible ?? true,
|
|
162
162
|
opacity: config.opacity ?? 1,
|
|
163
163
|
zIndex: config.zIndex,
|
|
164
|
-
style: clusterCfg?.enabled ? clusterStyleFn : styleFn,
|
|
164
|
+
style: (clusterCfg?.enabled ? clusterStyleFn : styleFn),
|
|
165
165
|
});
|
|
166
166
|
layer.set('id', config.id);
|
|
167
167
|
layer.set('cluster-config', clusterCfg);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MockComponent } from '@angular-helpers/testing';
|
|
2
|
+
|
|
3
|
+
// This provides a pre-configured mock for the complex OlMapComponent,
|
|
4
|
+
// avoiding the need to mock openlayers library internals or deal with ResizeObservers.
|
|
5
|
+
const MockOlMapComponent = MockComponent({
|
|
6
|
+
selector: 'ol-map',
|
|
7
|
+
inputs: ['center', 'zoom', 'rotation', 'projection', 'coordinateProjection'],
|
|
8
|
+
outputs: ['viewChange', 'mapClick', 'mapDblClick'],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generated bundle index. Do not edit.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export { MockOlMapComponent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-helpers/openlayers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Modern Angular wrapper for OpenLayers with modular architecture, standalone components, and hybrid template/programmatic API",
|
|
5
5
|
"homepage": "https://gaspar1992.github.io/angular-helpers/docs/openlayers",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
+
"@angular-helpers/testing": "^21.14.0",
|
|
33
34
|
"@angular/common": "^21.0.0",
|
|
34
35
|
"@angular/core": "^21.0.0",
|
|
35
36
|
"milsymbol": "^3.0.0",
|
|
@@ -76,6 +77,10 @@
|
|
|
76
77
|
"./overlays": {
|
|
77
78
|
"types": "./types/angular-helpers-openlayers-overlays.d.ts",
|
|
78
79
|
"default": "./fesm2022/angular-helpers-openlayers-overlays.mjs"
|
|
80
|
+
},
|
|
81
|
+
"./testing": {
|
|
82
|
+
"types": "./types/angular-helpers-openlayers-testing.d.ts",
|
|
83
|
+
"default": "./fesm2022/angular-helpers-openlayers-testing.mjs"
|
|
79
84
|
}
|
|
80
85
|
},
|
|
81
86
|
"type": "module",
|
|
@@ -40,7 +40,7 @@ declare class OlScaleLineControlComponent {
|
|
|
40
40
|
declare class OlFullscreenControlComponent {
|
|
41
41
|
private mapService;
|
|
42
42
|
private zoneHelper;
|
|
43
|
-
source: _angular_core.InputSignal<HTMLElement>;
|
|
43
|
+
source: _angular_core.InputSignal<HTMLElement | undefined>;
|
|
44
44
|
label: _angular_core.InputSignal<string>;
|
|
45
45
|
labelActive: _angular_core.InputSignal<string>;
|
|
46
46
|
tipLabel: _angular_core.InputSignal<string>;
|
|
@@ -231,5 +231,55 @@ declare class OlControlService {
|
|
|
231
231
|
declare function withControls(): OlFeature<'controls'>;
|
|
232
232
|
declare function provideControls(): OlFeature<'controls'>;
|
|
233
233
|
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
type TimelinePosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* A premium, reactive visual timeline component for OpenLayers.
|
|
238
|
+
* Orchestrates time-series animations by binding directly to OlTimeService.
|
|
239
|
+
*
|
|
240
|
+
* Uses native controls, CSS grid/flex layouts, and a sleek glassmorphic theme.
|
|
241
|
+
* Completely free of CommonModule or FormsModule dependencies for maximum performance.
|
|
242
|
+
*
|
|
243
|
+
* @usageNotes
|
|
244
|
+
* ```html
|
|
245
|
+
* <ol-timeline
|
|
246
|
+
* [startTime]="1700000000000"
|
|
247
|
+
* [endTime]="1700086400000"
|
|
248
|
+
* [playSpeed]="60"
|
|
249
|
+
* [loop]="true"
|
|
250
|
+
* position="bottom-center">
|
|
251
|
+
* </ol-timeline>
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
declare class OlTimelineComponent {
|
|
255
|
+
private timeService;
|
|
256
|
+
/** Start bounds of time-series (Epoch ms) */
|
|
257
|
+
startTime: _angular_core.InputSignal<number>;
|
|
258
|
+
/** End bounds of time-series (Epoch ms) */
|
|
259
|
+
endTime: _angular_core.InputSignal<number>;
|
|
260
|
+
/** Default speed multiplier (e.g. 1, 5, 10, 60, 3600) */
|
|
261
|
+
playSpeed: _angular_core.InputSignal<number>;
|
|
262
|
+
/** Loop playback when reaching endTime */
|
|
263
|
+
loop: _angular_core.InputSignal<boolean>;
|
|
264
|
+
/** Position overlay alignment */
|
|
265
|
+
position: _angular_core.InputSignal<TimelinePosition>;
|
|
266
|
+
/** Custom label formatter */
|
|
267
|
+
formatLabel: _angular_core.InputSignal<(time: number) => string>;
|
|
268
|
+
/** Outputs */
|
|
269
|
+
timeChange: _angular_core.OutputEmitterRef<number>;
|
|
270
|
+
playStateChange: _angular_core.OutputEmitterRef<boolean>;
|
|
271
|
+
/** Computeds binding directly to OlTimeService */
|
|
272
|
+
currentTime: _angular_core.Signal<number>;
|
|
273
|
+
isPlaying: _angular_core.Signal<boolean>;
|
|
274
|
+
speed: _angular_core.Signal<number>;
|
|
275
|
+
formattedTime: _angular_core.Signal<string>;
|
|
276
|
+
constructor();
|
|
277
|
+
togglePlay(): void;
|
|
278
|
+
onScrub(event: Event): void;
|
|
279
|
+
onSpeedChange(event: Event): void;
|
|
280
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlTimelineComponent, never>;
|
|
281
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTimelineComponent, "ol-timeline", never, { "startTime": { "alias": "startTime"; "required": true; "isSignal": true; }; "endTime": { "alias": "endTime"; "required": true; "isSignal": true; }; "playSpeed": { "alias": "playSpeed"; "required": false; "isSignal": true; }; "loop": { "alias": "loop"; "required": false; "isSignal": true; }; "position": { "alias": "position"; "required": false; "isSignal": true; }; "formatLabel": { "alias": "formatLabel"; "required": false; "isSignal": true; }; }, { "timeChange": "timeChange"; "playStateChange": "playStateChange"; }, never, never, true, never>;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export { OlAttributionControlComponent, OlBasemapSwitcherComponent, OlControlService, OlFullscreenControlComponent, OlGeolocationControlComponent, OlLayerSwitcherComponent, OlRotateControlComponent, OlScaleLineControlComponent, OlTimelineComponent, OlZoomControlComponent, ROTATE_CONTROL_MAP_SERVICE, provideControls, withControls };
|
|
285
|
+
export type { BasemapConfig, BasemapSwitcherPosition, ControlConfig, ControlPosition, LayerSwitcherItem, LayerSwitcherPosition, RotateControlMapService, TimelinePosition };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
|
-
import { ElementRef, Signal,
|
|
2
|
+
import { ElementRef, Signal, ResourceRef, Provider, EnvironmentProviders } from '@angular/core';
|
|
3
3
|
import OLMap from 'ol/Map';
|
|
4
4
|
import { View, Feature as Feature$1 } from 'ol';
|
|
5
5
|
|
|
@@ -380,7 +380,7 @@ interface VectorResourceOptions {
|
|
|
380
380
|
* @param options Additional vector resource options
|
|
381
381
|
* @returns An Angular Resource containing an array of parsed Features
|
|
382
382
|
*/
|
|
383
|
-
declare function createVectorResource(url: Signal<string | undefined>, options?: VectorResourceOptions):
|
|
383
|
+
declare function createVectorResource(url: Signal<string | undefined>, options?: VectorResourceOptions): ResourceRef<Feature[] | undefined>;
|
|
384
384
|
|
|
385
385
|
interface ProjectionOptions {
|
|
386
386
|
sourceProjection?: string;
|
|
@@ -87,7 +87,7 @@ declare class OlInteractionService {
|
|
|
87
87
|
private drawService;
|
|
88
88
|
private modifyService;
|
|
89
89
|
readonly selectedFeatures: _angular_core.Signal<Feature[]>;
|
|
90
|
-
readonly hoveredFeature: _angular_core.Signal<Feature>;
|
|
90
|
+
readonly hoveredFeature: _angular_core.Signal<Feature | null>;
|
|
91
91
|
readonly selectionCount: _angular_core.Signal<number>;
|
|
92
92
|
readonly hasSelection: _angular_core.Signal<boolean>;
|
|
93
93
|
readonly activeInteractions: _angular_core.Signal<_angular_helpers_openlayers_interactions.ManagedInteraction[]>;
|
|
@@ -183,7 +183,7 @@ declare class InteractionStateService {
|
|
|
183
183
|
private modifySubject;
|
|
184
184
|
private selectSubject;
|
|
185
185
|
readonly selectedFeatures: _angular_core.Signal<Feature[]>;
|
|
186
|
-
readonly hoveredFeature: _angular_core.Signal<Feature>;
|
|
186
|
+
readonly hoveredFeature: _angular_core.Signal<Feature | null>;
|
|
187
187
|
readonly selectionCount: _angular_core.Signal<number>;
|
|
188
188
|
readonly hasSelection: _angular_core.Signal<boolean>;
|
|
189
189
|
readonly activeInteractions: _angular_core.Signal<ManagedInteraction[]>;
|
|
@@ -354,9 +354,9 @@ declare class OlDrawInteractionComponent {
|
|
|
354
354
|
private destroyRef;
|
|
355
355
|
id: _angular_core.InputSignal<string>;
|
|
356
356
|
type: _angular_core.InputSignal<"Point" | "LineString" | "Polygon" | "Circle">;
|
|
357
|
-
source: _angular_core.InputSignal<string>;
|
|
358
|
-
freehand: _angular_core.InputSignal<boolean>;
|
|
359
|
-
snapTolerance: _angular_core.InputSignal<number>;
|
|
357
|
+
source: _angular_core.InputSignal<string | undefined>;
|
|
358
|
+
freehand: _angular_core.InputSignal<boolean | undefined>;
|
|
359
|
+
snapTolerance: _angular_core.InputSignal<number | undefined>;
|
|
360
360
|
active: _angular_core.InputSignal<boolean>;
|
|
361
361
|
private drawStartFiltered$;
|
|
362
362
|
private drawEndFiltered$;
|
|
@@ -374,8 +374,8 @@ declare class OlModifyInteractionComponent {
|
|
|
374
374
|
private interactionService;
|
|
375
375
|
private destroyRef;
|
|
376
376
|
id: _angular_core.InputSignal<string>;
|
|
377
|
-
source: _angular_core.InputSignal<string>;
|
|
378
|
-
snapTolerance: _angular_core.InputSignal<number>;
|
|
377
|
+
source: _angular_core.InputSignal<string | undefined>;
|
|
378
|
+
snapTolerance: _angular_core.InputSignal<number | undefined>;
|
|
379
379
|
active: _angular_core.InputSignal<boolean>;
|
|
380
380
|
private modifyFiltered$;
|
|
381
381
|
modifyEvent: _angular_core.OutputRef<_angular_helpers_openlayers_interactions.ModifyEvent>;
|
|
@@ -391,10 +391,10 @@ declare class OlSelectInteractionComponent {
|
|
|
391
391
|
private interactionService;
|
|
392
392
|
private destroyRef;
|
|
393
393
|
id: _angular_core.InputSignal<string>;
|
|
394
|
-
layers: _angular_core.InputSignal<string[]>;
|
|
395
|
-
multi: _angular_core.InputSignal<boolean>;
|
|
396
|
-
hitTolerance: _angular_core.InputSignal<number>;
|
|
397
|
-
condition: _angular_core.InputSignal<"click" | "pointerMove">;
|
|
394
|
+
layers: _angular_core.InputSignal<string[] | undefined>;
|
|
395
|
+
multi: _angular_core.InputSignal<boolean | undefined>;
|
|
396
|
+
hitTolerance: _angular_core.InputSignal<number | undefined>;
|
|
397
|
+
condition: _angular_core.InputSignal<"click" | "pointerMove" | undefined>;
|
|
398
398
|
active: _angular_core.InputSignal<boolean>;
|
|
399
399
|
private selectFiltered$;
|
|
400
400
|
selectEvent: _angular_core.OutputRef<_angular_helpers_openlayers_interactions.SelectEvent>;
|
|
@@ -68,7 +68,7 @@ declare class OlClusterComponent {
|
|
|
68
68
|
distance: _angular_core.InputSignal<number>;
|
|
69
69
|
minDistance: _angular_core.InputSignal<number>;
|
|
70
70
|
showCount: _angular_core.InputSignal<boolean>;
|
|
71
|
-
featureStyle: _angular_core.InputSignal<Style>;
|
|
71
|
+
featureStyle: _angular_core.InputSignal<Style | undefined>;
|
|
72
72
|
spiderfyOnSelect: _angular_core.InputSignal<boolean>;
|
|
73
73
|
spiderfyClick: _angular_core.OutputEmitterRef<Feature>;
|
|
74
74
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlClusterComponent, never>;
|
|
@@ -80,14 +80,14 @@ declare class OlVectorLayerComponent {
|
|
|
80
80
|
private destroyRef;
|
|
81
81
|
id: _angular_core.InputSignal<string>;
|
|
82
82
|
features: _angular_core.InputSignal<Feature[]>;
|
|
83
|
-
url: _angular_core.InputSignal<string>;
|
|
84
|
-
format: _angular_core.InputSignal<"geojson" | "topojson" | "kml">;
|
|
83
|
+
url: _angular_core.InputSignal<string | undefined>;
|
|
84
|
+
format: _angular_core.InputSignal<"geojson" | "topojson" | "kml" | undefined>;
|
|
85
85
|
zIndex: _angular_core.InputSignal<number>;
|
|
86
86
|
opacity: _angular_core.InputSignal<number>;
|
|
87
87
|
visible: _angular_core.InputSignal<boolean>;
|
|
88
88
|
style: _angular_core.InputSignal<any>;
|
|
89
|
-
cluster: _angular_core.InputSignal<ClusterConfig>;
|
|
90
|
-
clusterComponent: _angular_core.Signal<OlClusterComponent>;
|
|
89
|
+
cluster: _angular_core.InputSignal<ClusterConfig | undefined>;
|
|
90
|
+
clusterComponent: _angular_core.Signal<OlClusterComponent | undefined>;
|
|
91
91
|
coordinateProjection: _angular_core.InputSignal<string>;
|
|
92
92
|
constructor();
|
|
93
93
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlVectorLayerComponent, never>;
|
|
@@ -99,9 +99,9 @@ declare class OlTileLayerComponent {
|
|
|
99
99
|
private destroyRef;
|
|
100
100
|
id: _angular_core.InputSignal<string>;
|
|
101
101
|
source: _angular_core.InputSignal<"osm" | "xyz" | "wms">;
|
|
102
|
-
url: _angular_core.InputSignal<string>;
|
|
103
|
-
attributions: _angular_core.InputSignal<string | string[]>;
|
|
104
|
-
params: _angular_core.InputSignal<Record<string, unknown
|
|
102
|
+
url: _angular_core.InputSignal<string | undefined>;
|
|
103
|
+
attributions: _angular_core.InputSignal<string | string[] | undefined>;
|
|
104
|
+
params: _angular_core.InputSignal<Record<string, unknown> | undefined>;
|
|
105
105
|
zIndex: _angular_core.InputSignal<number>;
|
|
106
106
|
opacity: _angular_core.InputSignal<number>;
|
|
107
107
|
visible: _angular_core.InputSignal<boolean>;
|
|
@@ -116,8 +116,8 @@ declare class OlImageLayerComponent {
|
|
|
116
116
|
id: _angular_core.InputSignal<string>;
|
|
117
117
|
sourceType: _angular_core.InputSignal<"wms" | "static">;
|
|
118
118
|
url: _angular_core.InputSignal<string>;
|
|
119
|
-
params: _angular_core.InputSignal<Record<string, unknown
|
|
120
|
-
imageExtent: _angular_core.InputSignal<[number, number, number, number]>;
|
|
119
|
+
params: _angular_core.InputSignal<Record<string, unknown> | undefined>;
|
|
120
|
+
imageExtent: _angular_core.InputSignal<[number, number, number, number] | undefined>;
|
|
121
121
|
zIndex: _angular_core.InputSignal<number>;
|
|
122
122
|
opacity: _angular_core.InputSignal<number>;
|
|
123
123
|
visible: _angular_core.InputSignal<boolean>;
|
|
@@ -139,7 +139,7 @@ declare class OlHeatmapLayerComponent {
|
|
|
139
139
|
radius: _angular_core.InputSignal<number>;
|
|
140
140
|
/** Unit for radius and blur: 'pixels' (default) or 'meters' */
|
|
141
141
|
radiusUnit: _angular_core.InputSignal<"pixels" | "meters">;
|
|
142
|
-
weight: _angular_core.InputSignal<string | ((feature: Feature) => number)>;
|
|
142
|
+
weight: _angular_core.InputSignal<string | ((feature: Feature) => number) | undefined>;
|
|
143
143
|
/** Computed radius in pixels based on current resolution if unit is 'meters' */
|
|
144
144
|
private scaledRadius;
|
|
145
145
|
/** Computed blur in pixels based on current resolution if unit is 'meters' */
|
|
@@ -189,7 +189,7 @@ declare class OlWebGLVectorLayerComponent {
|
|
|
189
189
|
/** Disable hit detection for better performance (default: true) */
|
|
190
190
|
disableHitDetection: _angular_core.InputSignal<boolean>;
|
|
191
191
|
/** Style variables for dynamic expressions (e.g. `['var', 'threshold']`) */
|
|
192
|
-
variables: _angular_core.InputSignal<Record<string, string | number | boolean | number[]
|
|
192
|
+
variables: _angular_core.InputSignal<Record<string, string | number | boolean | number[]> | undefined>;
|
|
193
193
|
private layer;
|
|
194
194
|
private vectorSource;
|
|
195
195
|
constructor();
|
|
@@ -228,11 +228,11 @@ declare class OlWebGLTileLayerComponent {
|
|
|
228
228
|
/** Tile source type */
|
|
229
229
|
source: _angular_core.InputSignal<"osm" | "xyz" | "mvt">;
|
|
230
230
|
/** Tile URL template (required for 'xyz' and 'mvt') */
|
|
231
|
-
url: _angular_core.InputSignal<string>;
|
|
231
|
+
url: _angular_core.InputSignal<string | undefined>;
|
|
232
232
|
/** Attribution text */
|
|
233
|
-
attributions: _angular_core.InputSignal<string | string[]>;
|
|
233
|
+
attributions: _angular_core.InputSignal<string | string[] | undefined>;
|
|
234
234
|
/** WebGL tile style (raster expressions) or flat style (MVT) */
|
|
235
|
-
tileStyle: _angular_core.InputSignal<FlatStyleLike | Style$1>;
|
|
235
|
+
tileStyle: _angular_core.InputSignal<FlatStyleLike | Style$1 | undefined>;
|
|
236
236
|
/** Z-index for layer ordering */
|
|
237
237
|
zIndex: _angular_core.InputSignal<number>;
|
|
238
238
|
/** Opacity (0–1) */
|
|
@@ -99,7 +99,7 @@ declare class OlPopupComponent {
|
|
|
99
99
|
private readonly mapService;
|
|
100
100
|
private readonly zoneHelper;
|
|
101
101
|
/** Map coordinate where the popup is anchored. `null` hides the popup. */
|
|
102
|
-
readonly position: _angular_core.InputSignal<Coordinate>;
|
|
102
|
+
readonly position: _angular_core.InputSignal<Coordinate | null>;
|
|
103
103
|
/** Pixel offset relative to `position`. */
|
|
104
104
|
readonly offset: _angular_core.InputSignal<[number, number]>;
|
|
105
105
|
/** Anchor of the popup element relative to `position`. */
|
|
@@ -146,7 +146,7 @@ declare class OlTooltipDirective {
|
|
|
146
146
|
/** Property key to read from the hovered feature. */
|
|
147
147
|
readonly olTooltip: _angular_core.InputSignal<string>;
|
|
148
148
|
/** Optional layer id; when set, only features on that layer trigger the tooltip. */
|
|
149
|
-
readonly olTooltipLayer: _angular_core.InputSignal<string>;
|
|
149
|
+
readonly olTooltipLayer: _angular_core.InputSignal<string | null>;
|
|
150
150
|
private element;
|
|
151
151
|
private listener;
|
|
152
152
|
private currentMap;
|