@angular-helpers/openlayers 0.6.0 → 0.7.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
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,6 +1,6 @@
|
|
|
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';
|
|
@@ -857,10 +857,240 @@ function provideControls() {
|
|
|
857
857
|
return withControls();
|
|
858
858
|
}
|
|
859
859
|
|
|
860
|
+
/**
|
|
861
|
+
* A premium, reactive visual timeline component for OpenLayers.
|
|
862
|
+
* Orchestrates time-series animations by binding directly to OlTimeService.
|
|
863
|
+
*
|
|
864
|
+
* Uses native controls, CSS grid/flex layouts, and a sleek glassmorphic theme.
|
|
865
|
+
* Completely free of CommonModule or FormsModule dependencies for maximum performance.
|
|
866
|
+
*
|
|
867
|
+
* @usageNotes
|
|
868
|
+
* ```html
|
|
869
|
+
* <ol-timeline
|
|
870
|
+
* [startTime]="1700000000000"
|
|
871
|
+
* [endTime]="1700086400000"
|
|
872
|
+
* [playSpeed]="60"
|
|
873
|
+
* [loop]="true"
|
|
874
|
+
* position="bottom-center">
|
|
875
|
+
* </ol-timeline>
|
|
876
|
+
* ```
|
|
877
|
+
*/
|
|
878
|
+
class OlTimelineComponent {
|
|
879
|
+
timeService = inject(OlTimeService);
|
|
880
|
+
/** Start bounds of time-series (Epoch ms) */
|
|
881
|
+
startTime = input.required(...(ngDevMode ? [{ debugName: "startTime" }] : /* istanbul ignore next */ []));
|
|
882
|
+
/** End bounds of time-series (Epoch ms) */
|
|
883
|
+
endTime = input.required(...(ngDevMode ? [{ debugName: "endTime" }] : /* istanbul ignore next */ []));
|
|
884
|
+
/** Default speed multiplier (e.g. 1, 5, 10, 60, 3600) */
|
|
885
|
+
playSpeed = input(1, ...(ngDevMode ? [{ debugName: "playSpeed" }] : /* istanbul ignore next */ []));
|
|
886
|
+
/** Loop playback when reaching endTime */
|
|
887
|
+
loop = input(false, ...(ngDevMode ? [{ debugName: "loop" }] : /* istanbul ignore next */ []));
|
|
888
|
+
/** Position overlay alignment */
|
|
889
|
+
position = input('bottom-center', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
|
|
890
|
+
/** Custom label formatter */
|
|
891
|
+
formatLabel = input((t) => new Date(t).toLocaleString(), ...(ngDevMode ? [{ debugName: "formatLabel" }] : /* istanbul ignore next */ []));
|
|
892
|
+
/** Outputs */
|
|
893
|
+
timeChange = output();
|
|
894
|
+
playStateChange = output();
|
|
895
|
+
/** Computeds binding directly to OlTimeService */
|
|
896
|
+
currentTime = computed(() => this.timeService.currentTime(), ...(ngDevMode ? [{ debugName: "currentTime" }] : /* istanbul ignore next */ []));
|
|
897
|
+
isPlaying = computed(() => this.timeService.isPlaying(), ...(ngDevMode ? [{ debugName: "isPlaying" }] : /* istanbul ignore next */ []));
|
|
898
|
+
speed = computed(() => this.timeService.speed(), ...(ngDevMode ? [{ debugName: "speed" }] : /* istanbul ignore next */ []));
|
|
899
|
+
formattedTime = computed(() => this.formatLabel()(this.currentTime()), ...(ngDevMode ? [{ debugName: "formattedTime" }] : /* istanbul ignore next */ []));
|
|
900
|
+
constructor() {
|
|
901
|
+
// Sync default configuration settings when the inputs initialize
|
|
902
|
+
effect(() => {
|
|
903
|
+
this.timeService.setSpeed(this.playSpeed());
|
|
904
|
+
});
|
|
905
|
+
// Make sure initial time starts at the startTime bounds
|
|
906
|
+
effect(() => {
|
|
907
|
+
this.timeService.setTime(this.startTime());
|
|
908
|
+
});
|
|
909
|
+
// Reactive time-based loop boundaries check
|
|
910
|
+
effect(() => {
|
|
911
|
+
const current = this.currentTime();
|
|
912
|
+
const end = this.endTime();
|
|
913
|
+
const isAnimPlaying = this.isPlaying();
|
|
914
|
+
if (current >= end && isAnimPlaying) {
|
|
915
|
+
if (this.loop()) {
|
|
916
|
+
this.timeService.setTime(this.startTime());
|
|
917
|
+
}
|
|
918
|
+
else {
|
|
919
|
+
this.timeService.pause();
|
|
920
|
+
this.timeService.setTime(end);
|
|
921
|
+
this.playStateChange.emit(false);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
// Emit reactive output when current time advances
|
|
926
|
+
effect(() => {
|
|
927
|
+
this.timeChange.emit(this.currentTime());
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
togglePlay() {
|
|
931
|
+
if (this.isPlaying()) {
|
|
932
|
+
this.timeService.pause();
|
|
933
|
+
this.playStateChange.emit(false);
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
// If we are at the end, reset to start before playing
|
|
937
|
+
if (this.currentTime() >= this.endTime()) {
|
|
938
|
+
this.timeService.setTime(this.startTime());
|
|
939
|
+
}
|
|
940
|
+
this.timeService.play();
|
|
941
|
+
this.playStateChange.emit(true);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
onScrub(event) {
|
|
945
|
+
const target = event.target;
|
|
946
|
+
const value = Number(target.value);
|
|
947
|
+
this.timeService.setTime(value);
|
|
948
|
+
}
|
|
949
|
+
onSpeedChange(event) {
|
|
950
|
+
const target = event.target;
|
|
951
|
+
const value = Number(target.value);
|
|
952
|
+
this.timeService.setSpeed(value);
|
|
953
|
+
}
|
|
954
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTimelineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
955
|
+
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: `
|
|
956
|
+
<div
|
|
957
|
+
class="ol-timeline"
|
|
958
|
+
[class.ol-timeline--top-left]="position() === 'top-left'"
|
|
959
|
+
[class.ol-timeline--top-center]="position() === 'top-center'"
|
|
960
|
+
[class.ol-timeline--top-right]="position() === 'top-right'"
|
|
961
|
+
[class.ol-timeline--bottom-left]="position() === 'bottom-left'"
|
|
962
|
+
[class.ol-timeline--bottom-center]="position() === 'bottom-center'"
|
|
963
|
+
[class.ol-timeline--bottom-right]="position() === 'bottom-right'"
|
|
964
|
+
>
|
|
965
|
+
<div class="ol-timeline__controls">
|
|
966
|
+
<button
|
|
967
|
+
type="button"
|
|
968
|
+
class="ol-timeline__btn ol-timeline__btn--play"
|
|
969
|
+
(click)="togglePlay()"
|
|
970
|
+
[attr.aria-label]="isPlaying() ? 'Pause animation' : 'Play animation'"
|
|
971
|
+
>
|
|
972
|
+
@if (isPlaying()) {
|
|
973
|
+
<!-- Pause Icon -->
|
|
974
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
975
|
+
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
|
|
976
|
+
</svg>
|
|
977
|
+
} @else {
|
|
978
|
+
<!-- Play Icon -->
|
|
979
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
980
|
+
<path d="M8 5v14l11-7z" />
|
|
981
|
+
</svg>
|
|
982
|
+
}
|
|
983
|
+
</button>
|
|
984
|
+
|
|
985
|
+
<span class="ol-timeline__time-display" aria-live="polite">
|
|
986
|
+
{{ formattedTime() }}
|
|
987
|
+
</span>
|
|
988
|
+
</div>
|
|
989
|
+
|
|
990
|
+
<div class="ol-timeline__slider-container">
|
|
991
|
+
<input
|
|
992
|
+
type="range"
|
|
993
|
+
class="ol-timeline__slider"
|
|
994
|
+
[min]="startTime()"
|
|
995
|
+
[max]="endTime()"
|
|
996
|
+
[value]="currentTime()"
|
|
997
|
+
(input)="onScrub($event)"
|
|
998
|
+
aria-label="Timeline progress slider"
|
|
999
|
+
/>
|
|
1000
|
+
</div>
|
|
1001
|
+
|
|
1002
|
+
<div class="ol-timeline__settings">
|
|
1003
|
+
<select
|
|
1004
|
+
class="ol-timeline__speed-select"
|
|
1005
|
+
[value]="speed()"
|
|
1006
|
+
(change)="onSpeedChange($event)"
|
|
1007
|
+
aria-label="Playback speed multiplier"
|
|
1008
|
+
>
|
|
1009
|
+
<option [value]="1">1x (Real)</option>
|
|
1010
|
+
<option [value]="5">5x</option>
|
|
1011
|
+
<option [value]="10">10x</option>
|
|
1012
|
+
<option [value]="30">30x</option>
|
|
1013
|
+
<option [value]="60">60x (1m/s)</option>
|
|
1014
|
+
<option [value]="300">300x (5m/s)</option>
|
|
1015
|
+
<option [value]="3600">3600x (1h/s)</option>
|
|
1016
|
+
</select>
|
|
1017
|
+
</div>
|
|
1018
|
+
</div>
|
|
1019
|
+
`, 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 });
|
|
1020
|
+
}
|
|
1021
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTimelineComponent, decorators: [{
|
|
1022
|
+
type: Component,
|
|
1023
|
+
args: [{ selector: 'ol-timeline', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1024
|
+
<div
|
|
1025
|
+
class="ol-timeline"
|
|
1026
|
+
[class.ol-timeline--top-left]="position() === 'top-left'"
|
|
1027
|
+
[class.ol-timeline--top-center]="position() === 'top-center'"
|
|
1028
|
+
[class.ol-timeline--top-right]="position() === 'top-right'"
|
|
1029
|
+
[class.ol-timeline--bottom-left]="position() === 'bottom-left'"
|
|
1030
|
+
[class.ol-timeline--bottom-center]="position() === 'bottom-center'"
|
|
1031
|
+
[class.ol-timeline--bottom-right]="position() === 'bottom-right'"
|
|
1032
|
+
>
|
|
1033
|
+
<div class="ol-timeline__controls">
|
|
1034
|
+
<button
|
|
1035
|
+
type="button"
|
|
1036
|
+
class="ol-timeline__btn ol-timeline__btn--play"
|
|
1037
|
+
(click)="togglePlay()"
|
|
1038
|
+
[attr.aria-label]="isPlaying() ? 'Pause animation' : 'Play animation'"
|
|
1039
|
+
>
|
|
1040
|
+
@if (isPlaying()) {
|
|
1041
|
+
<!-- Pause Icon -->
|
|
1042
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
1043
|
+
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
|
|
1044
|
+
</svg>
|
|
1045
|
+
} @else {
|
|
1046
|
+
<!-- Play Icon -->
|
|
1047
|
+
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
1048
|
+
<path d="M8 5v14l11-7z" />
|
|
1049
|
+
</svg>
|
|
1050
|
+
}
|
|
1051
|
+
</button>
|
|
1052
|
+
|
|
1053
|
+
<span class="ol-timeline__time-display" aria-live="polite">
|
|
1054
|
+
{{ formattedTime() }}
|
|
1055
|
+
</span>
|
|
1056
|
+
</div>
|
|
1057
|
+
|
|
1058
|
+
<div class="ol-timeline__slider-container">
|
|
1059
|
+
<input
|
|
1060
|
+
type="range"
|
|
1061
|
+
class="ol-timeline__slider"
|
|
1062
|
+
[min]="startTime()"
|
|
1063
|
+
[max]="endTime()"
|
|
1064
|
+
[value]="currentTime()"
|
|
1065
|
+
(input)="onScrub($event)"
|
|
1066
|
+
aria-label="Timeline progress slider"
|
|
1067
|
+
/>
|
|
1068
|
+
</div>
|
|
1069
|
+
|
|
1070
|
+
<div class="ol-timeline__settings">
|
|
1071
|
+
<select
|
|
1072
|
+
class="ol-timeline__speed-select"
|
|
1073
|
+
[value]="speed()"
|
|
1074
|
+
(change)="onSpeedChange($event)"
|
|
1075
|
+
aria-label="Playback speed multiplier"
|
|
1076
|
+
>
|
|
1077
|
+
<option [value]="1">1x (Real)</option>
|
|
1078
|
+
<option [value]="5">5x</option>
|
|
1079
|
+
<option [value]="10">10x</option>
|
|
1080
|
+
<option [value]="30">30x</option>
|
|
1081
|
+
<option [value]="60">60x (1m/s)</option>
|
|
1082
|
+
<option [value]="300">300x (5m/s)</option>
|
|
1083
|
+
<option [value]="3600">3600x (1h/s)</option>
|
|
1084
|
+
</select>
|
|
1085
|
+
</div>
|
|
1086
|
+
</div>
|
|
1087
|
+
`, 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"] }]
|
|
1088
|
+
}], 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"] }] } });
|
|
1089
|
+
|
|
860
1090
|
// @angular-helpers/openlayers/controls
|
|
861
1091
|
|
|
862
1092
|
/**
|
|
863
1093
|
* Generated bundle index. Do not edit.
|
|
864
1094
|
*/
|
|
865
1095
|
|
|
866
|
-
export { OlAttributionControlComponent, OlBasemapSwitcherComponent, OlControlService, OlFullscreenControlComponent, OlGeolocationControlComponent, OlLayerSwitcherComponent, OlRotateControlComponent, OlScaleLineControlComponent, OlZoomControlComponent, ROTATE_CONTROL_MAP_SERVICE, provideControls, withControls };
|
|
1096
|
+
export { OlAttributionControlComponent, OlBasemapSwitcherComponent, OlControlService, OlFullscreenControlComponent, OlGeolocationControlComponent, OlLayerSwitcherComponent, OlRotateControlComponent, OlScaleLineControlComponent, OlTimelineComponent, OlZoomControlComponent, ROTATE_CONTROL_MAP_SERVICE, provideControls, withControls };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-helpers/openlayers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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": {
|
|
@@ -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 };
|