@angular-helpers/openlayers 0.4.0 → 0.5.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.
@@ -1,201 +1,102 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable } from '@angular/core';
2
+ import { inject, resource, Injectable } from '@angular/core';
3
+ import { OlGeometryService } from '@angular-helpers/openlayers/core';
4
+ import { Style, Icon, Stroke, RegularShape, Fill } from 'ol/style';
5
+ import { LineString, Point } from 'ol/geom';
3
6
 
4
- // @angular-helpers/openlayers/military — service implementation
5
- /**
6
- * Meters per degree of latitude on a spherical Earth approximation.
7
- * Used by the local tangent-plane projection in the geometry helpers.
8
- */
9
- const METERS_PER_DEGREE_LAT = 111_320;
10
7
  /**
11
8
  * Service exposing geometry helpers and MIL-STD-2525 symbology rendering.
12
9
  *
13
10
  * - `createEllipse`, `createSector`, `createDonut` are **pure math** and
14
- * have no runtime dependencies beyond the bundled types.
15
- * - `createMilSymbol` uses the milsymbol library via dynamic ESM import.
11
+ * delegate to {@link OlGeometryService} in core.
12
+ * - `createMilSymbol` uses the milsymbol library via Angular resource loading.
16
13
  */
17
14
  class OlMilitaryService {
18
15
  idCounter = 0;
19
- mlLoader = null;
20
- msModule = null;
16
+ geometryService = inject(OlGeometryService);
17
+ /**
18
+ * Resource managing the lazy-loading of the milsymbol library.
19
+ * Angular resource provides native signals for value, loading state, and errors.
20
+ */
21
+ msResource = resource({ ...(ngDevMode ? { debugName: "msResource" } : /* istanbul ignore next */ {}), loader: () => import('milsymbol') });
22
+ /** Signal indicating if the milsymbol library is currently being loaded. */
23
+ isLoading = this.msResource.isLoading;
24
+ constructor() { }
21
25
  // ---------------------------------------------------------------------------
22
- // Geometry helpers (pure math, no deps)
26
+ // Geometry helpers (delegated to core)
23
27
  // ---------------------------------------------------------------------------
24
28
  /**
25
29
  * Build a `Feature<Polygon>` approximating an ellipse centered at
26
30
  * `config.center`. See {@link EllipseConfig} for parameter semantics.
27
31
  */
28
32
  createEllipse(config) {
29
- const { center, semiMajor, semiMinor, rotation = 0, segments = 64, properties } = config;
30
- if (semiMajor <= 0 || semiMinor <= 0) {
31
- throw new RangeError('semiMajor and semiMinor must be positive');
32
- }
33
- if (segments < 8) {
34
- throw new RangeError('segments must be >= 8');
35
- }
36
- const cosR = Math.cos(rotation);
37
- const sinR = Math.sin(rotation);
38
- const ring = [];
39
- for (let i = 0; i < segments; i++) {
40
- const theta = (i / segments) * Math.PI * 2;
41
- // Ellipse in local axis-aligned frame, then rotated by `rotation`.
42
- const ax = Math.cos(theta) * semiMajor;
43
- const ay = Math.sin(theta) * semiMinor;
44
- const dx = ax * cosR - ay * sinR;
45
- const dy = ax * sinR + ay * cosR;
46
- ring.push(this.offsetMetersToLonLat(center, dx, dy));
47
- }
48
- ring.push(ring[0]); // close the ring
49
- return {
50
- id: this.nextId('ellipse'),
51
- geometry: { type: 'Polygon', coordinates: [ring] },
52
- properties,
53
- };
33
+ return this.geometryService.createEllipse(config);
54
34
  }
55
35
  /**
56
36
  * Build a `Feature<Polygon>` for a circular sector (pie slice).
57
37
  * See {@link SectorConfig} for parameter semantics.
58
38
  */
59
39
  createSector(config) {
60
- const { center, radius, startAngle, endAngle, segments = 32, properties } = config;
61
- if (radius <= 0) {
62
- throw new RangeError('radius must be positive');
63
- }
64
- if (endAngle <= startAngle) {
65
- throw new RangeError('endAngle must be greater than startAngle');
66
- }
67
- if (endAngle - startAngle > Math.PI * 2) {
68
- throw new RangeError('sector cannot exceed full circle');
69
- }
70
- if (segments < 4) {
71
- throw new RangeError('segments must be >= 4');
72
- }
73
- const ring = [center];
74
- const span = endAngle - startAngle;
75
- for (let i = 0; i <= segments; i++) {
76
- const theta = startAngle + (i / segments) * span;
77
- const dx = Math.cos(theta) * radius;
78
- const dy = Math.sin(theta) * radius;
79
- ring.push(this.offsetMetersToLonLat(center, dx, dy));
80
- }
81
- ring.push(center); // close back to apex
82
- return {
83
- id: this.nextId('sector'),
84
- geometry: { type: 'Polygon', coordinates: [ring] },
85
- properties,
86
- };
40
+ return this.geometryService.createSector(config);
87
41
  }
88
42
  /**
89
- * Build a `Feature<Polygon>` for a donut (annular ring). The output has
90
- * two rings: an outer ring wound counter-clockwise and an inner ring
91
- * wound clockwise so the GeoJSON right-hand rule renders the hole.
43
+ * Build a `Feature<Polygon>` for a donut (annular ring).
92
44
  */
93
45
  createDonut(config) {
94
- const { center, outerRadius, innerRadius, segments = 64, properties } = config;
95
- if (outerRadius <= 0 || innerRadius <= 0) {
96
- throw new RangeError('radii must be positive');
97
- }
98
- if (outerRadius <= innerRadius) {
99
- throw new RangeError('outerRadius must be greater than innerRadius');
100
- }
101
- if (segments < 8) {
102
- throw new RangeError('segments must be >= 8');
103
- }
104
- const outer = [];
105
- const inner = [];
106
- for (let i = 0; i < segments; i++) {
107
- const theta = (i / segments) * Math.PI * 2;
108
- const cosT = Math.cos(theta);
109
- const sinT = Math.sin(theta);
110
- // Outer ring: CCW (theta increasing)
111
- outer.push(this.offsetMetersToLonLat(center, cosT * outerRadius, sinT * outerRadius));
112
- // Inner ring: CW — sample the SAME thetas but we'll reverse the
113
- // accumulator below so the ring is traversed in the opposite sense.
114
- inner.push(this.offsetMetersToLonLat(center, cosT * innerRadius, sinT * innerRadius));
115
- }
116
- inner.reverse();
117
- outer.push(outer[0]);
118
- inner.push(inner[0]);
119
- return {
120
- id: this.nextId('donut'),
121
- geometry: { type: 'Polygon', coordinates: [outer, inner] },
122
- properties,
123
- };
46
+ return this.geometryService.createDonut(config);
124
47
  }
125
48
  // ---------------------------------------------------------------------------
126
49
  // MIL-STD-2525 symbology (lazy `milsymbol` load)
127
50
  // ---------------------------------------------------------------------------
128
51
  /**
129
- * Pre-load the optional `milsymbol` peer dependency so subsequent calls
130
- * to `createMilSymbol` / `createMilSymbolSync` resolve immediately.
131
- * Idempotent multiple calls share the same promise.
52
+ * Pre-load the optional `milsymbol` peer dependency.
53
+ * Since resource() starts loading immediately, this simply returns a promise
54
+ * that resolves when the resource is ready.
132
55
  */
133
- preloadMilsymbol() {
56
+ async preloadMilsymbol() {
134
57
  this.assertBrowser();
135
- if (!this.mlLoader) {
136
- this.mlLoader = import('milsymbol').then((m) => {
137
- this.msModule = m;
138
- return m;
139
- });
140
- }
141
- return this.mlLoader.then(() => {
142
- // Void return for the public API
143
- });
58
+ // We can just await the dynamic import again; the browser/bundler will
59
+ // return the same module instantly if already loaded by the resource.
60
+ await import('milsymbol');
144
61
  }
145
62
  /**
146
63
  * Build a MIL-STD-2525 symbol feature asynchronously.
147
- * Lazy-loads `milsymbol` on the first call.
64
+ * Waits for the milsymbol resource to resolve.
148
65
  */
149
66
  async createMilSymbol(config) {
150
67
  this.assertBrowser();
151
68
  this.assertSidc(config.sidc);
152
- if (!this.msModule) {
153
- await this.preloadMilsymbol();
154
- }
155
- return this.buildSymbolFeature(config);
69
+ // Dynamic import ensures we have the module reference even if called
70
+ // before the resource signal has propagated.
71
+ const msModule = await import('milsymbol');
72
+ return this.buildSymbolFeature(config, msModule);
156
73
  }
157
74
  /**
158
75
  * Build a MIL-STD-2525 symbol feature synchronously.
159
- * Throws if `milsymbol` has not been preloaded via `preloadMilsymbol()`
160
- * or a previous `createMilSymbol()` call.
76
+ * Throws if `milsymbol` resource is not ready.
161
77
  */
162
78
  createMilSymbolSync(config) {
163
79
  this.assertBrowser();
164
80
  this.assertSidc(config.sidc);
165
- if (!this.msModule) {
81
+ const msModule = this.msResource.value();
82
+ if (!msModule) {
166
83
  throw new Error('milsymbol is not loaded yet. Call preloadMilsymbol() or use the async createMilSymbol().');
167
84
  }
168
- return this.buildSymbolFeature(config);
85
+ return this.buildSymbolFeature(config, msModule);
169
86
  }
170
87
  // ---------------------------------------------------------------------------
171
88
  // Internals
172
89
  // ---------------------------------------------------------------------------
173
- /**
174
- * Project an `(dx, dy)` meter offset from `center` to lon/lat using a
175
- * local tangent-plane (equirectangular) approximation. Acceptable for
176
- * the radii typical in military symbology (<100 km from center).
177
- */
178
- offsetMetersToLonLat(center, dx, dy) {
179
- const [lon, lat] = center;
180
- const latRad = (lat * Math.PI) / 180;
181
- const dLat = dy / METERS_PER_DEGREE_LAT;
182
- const dLon = dx / (METERS_PER_DEGREE_LAT * Math.cos(latRad));
183
- return [lon + dLon, lat + dLat];
184
- }
185
90
  nextId(kind) {
186
91
  return `${kind}-${++this.idCounter}`;
187
92
  }
188
- buildSymbolFeature(config) {
93
+ buildSymbolFeature(config, msModule) {
189
94
  const { sidc, position, properties, quantity, ...rest } = config;
190
- // `milsymbol` types `quantity` as a string, but a number is the
191
- // ergonomic shape; coerce here.
192
95
  const milOptions = {
193
96
  ...rest,
194
97
  ...(quantity !== undefined ? { quantity: String(quantity) } : {}),
195
98
  };
196
- // We asserted this.msModule exists before calling this
197
- const ms = this.msModule;
198
- // default import might be wrapped depending on bundler
99
+ const ms = msModule;
199
100
  const SymbolClass = ms.default?.Symbol || ms.Symbol;
200
101
  const symbol = new SymbolClass(sidc, milOptions);
201
102
  const style = this.symbolToStyleResult(symbol);
@@ -218,10 +119,33 @@ class OlMilitaryService {
218
119
  };
219
120
  }
220
121
  encodeBase64Utf8(input) {
221
- // `btoa` only handles Latin-1; this round-trip preserves non-ASCII
222
- // characters (e.g. unit designators with accents).
223
122
  return btoa(unescape(encodeURIComponent(input)));
224
123
  }
124
+ /**
125
+ * Generates a canvas and anchor for a MIL-STD-2525 symbol.
126
+ * Requires milsymbol to be loaded (synchronous).
127
+ */
128
+ createUnitStyle(sidc, selected = false, size = 30) {
129
+ const msModule = this.msResource.value();
130
+ if (!msModule)
131
+ return null;
132
+ const ms = msModule;
133
+ const SymbolClass = ms.default?.Symbol || ms.Symbol;
134
+ const symbol = new SymbolClass(sidc, {
135
+ size,
136
+ strokeWidth: selected ? 6 : 4,
137
+ });
138
+ const canvas = symbol.asCanvas();
139
+ const anchor = symbol.getAnchor();
140
+ return {
141
+ image: {
142
+ img: canvas,
143
+ size: [canvas.width, canvas.height],
144
+ anchor: [anchor.x / canvas.width, anchor.y / canvas.height],
145
+ },
146
+ zIndex: selected ? 100 : 10,
147
+ };
148
+ }
225
149
  assertSidc(sidc) {
226
150
  if (typeof sidc !== 'string' || sidc.length < 10) {
227
151
  throw new TypeError('sidc must be a non-empty MIL-STD-2525 SIDC string');
@@ -232,15 +156,191 @@ class OlMilitaryService {
232
156
  throw new Error('createMilSymbol requires a browser environment');
233
157
  }
234
158
  }
235
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlMilitaryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
236
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlMilitaryService });
159
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlMilitaryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
160
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlMilitaryService });
161
+ }
162
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlMilitaryService, decorators: [{
163
+ type: Injectable
164
+ }], ctorParameters: () => [] });
165
+
166
+ // Tactical Graphics Service — investigation and prototype for military line drawing
167
+ /**
168
+ * Service providing styles and geometry generators for military tactical graphics
169
+ * such as battle fronts, axes of attack, and boundaries.
170
+ *
171
+ * NOTE: For point symbols (units), it delegates to {@link OlMilitaryService}
172
+ * for lazy-loading `milsymbol`.
173
+ */
174
+ class OlTacticalGraphicsService {
175
+ idCounter = 0;
176
+ militaryService = inject(OlMilitaryService);
177
+ /**
178
+ * Creates a LineString feature representing a battle front.
179
+ */
180
+ createFrontLine(coordinates, direction = 'friendly') {
181
+ return {
182
+ id: this.nextId('front-line'),
183
+ geometry: { type: 'LineString', coordinates },
184
+ properties: { tacticalType: 'front-line', direction },
185
+ };
186
+ }
187
+ /**
188
+ * Creates a LineString feature representing an attack arrow.
189
+ */
190
+ createAttackArrow(coordinates, direction = 'friendly') {
191
+ return {
192
+ id: this.nextId('attack-arrow'),
193
+ geometry: { type: 'LineString', coordinates },
194
+ properties: { tacticalType: 'attack-arrow', direction },
195
+ };
196
+ }
197
+ /**
198
+ * Creates a Polygon feature representing a control zone.
199
+ */
200
+ createControlZone(coordinates, direction = 'friendly') {
201
+ return {
202
+ id: this.nextId('control-zone'),
203
+ geometry: { type: 'Polygon', coordinates },
204
+ properties: { tacticalType: 'control-zone', direction },
205
+ };
206
+ }
207
+ /**
208
+ * Creates a Point feature representing a military unit.
209
+ * @param sidc - Symbol Identification Code (MIL-STD-2525 / APP-6)
210
+ */
211
+ createUnit(coordinate, sidc, name) {
212
+ return {
213
+ id: this.nextId('unit'),
214
+ geometry: { type: 'Point', coordinates: coordinate },
215
+ properties: { tacticalType: 'unit', sidc, name },
216
+ };
217
+ }
218
+ nextId(kind) {
219
+ return `${kind}-${++this.idCounter}`;
220
+ }
221
+ /**
222
+ * Style for Troop Units using milsymbol.
223
+ * NOTE: Requires milsymbol to be pre-loaded via OlMilitaryService.
224
+ */
225
+ createUnitStyle(sidc, selected = false) {
226
+ // We delegate the style calculation to militaryService which handles the lazy-load state.
227
+ const styleProperties = this.militaryService.createUnitStyle(sidc, selected);
228
+ if (!styleProperties)
229
+ return null;
230
+ return new Style({
231
+ image: new Icon({
232
+ img: styleProperties.image.img,
233
+ size: styleProperties.image.size,
234
+ anchor: styleProperties.image.anchor,
235
+ }),
236
+ zIndex: styleProperties.zIndex,
237
+ });
238
+ }
239
+ /**
240
+ * Style for Battle Fronts (Complex LineString with teeth).
241
+ */
242
+ createFrontLineStyle(color, direction = 'friendly') {
243
+ return (feature, resolution) => {
244
+ const geometry = feature.getGeometry();
245
+ if (!(geometry instanceof LineString))
246
+ return [];
247
+ const styles = [
248
+ new Style({
249
+ stroke: new Stroke({
250
+ color,
251
+ width: 3,
252
+ lineDash: direction === 'hostile' ? [10, 10] : undefined,
253
+ }),
254
+ }),
255
+ ];
256
+ const coords = geometry.getCoordinates();
257
+ const stride = 40 * resolution; // Spacing between markers
258
+ let distSoFar = 0;
259
+ for (let i = 0; i < coords.length - 1; i++) {
260
+ const p1 = coords[i];
261
+ const p2 = coords[i + 1];
262
+ const dx = p2[0] - p1[0];
263
+ const dy = p2[1] - p1[1];
264
+ const len = Math.sqrt(dx * dx + dy * dy);
265
+ const angle = Math.atan2(dy, dx);
266
+ while (distSoFar < len) {
267
+ const ratio = distSoFar / len;
268
+ const pos = [p1[0] + dx * ratio, p1[1] + dy * ratio];
269
+ // Add "teeth" or spikes along the line
270
+ styles.push(new Style({
271
+ geometry: new Point(pos),
272
+ image: new RegularShape({
273
+ fill: new Fill({ color }),
274
+ points: direction === 'friendly' ? 20 : 3, // Semicircle vs Triangle
275
+ radius: 6,
276
+ rotation: -angle + (direction === 'friendly' ? 0 : Math.PI / 2),
277
+ angle: direction === 'friendly' ? angle + Math.PI / 2 : 0,
278
+ }),
279
+ }));
280
+ distSoFar += stride;
281
+ }
282
+ distSoFar -= len;
283
+ }
284
+ return styles;
285
+ };
286
+ }
287
+ /**
288
+ * Style for Troop Movements (Arrows).
289
+ */
290
+ createAttackArrowStyle(color) {
291
+ return (feature) => {
292
+ const geometry = feature.getGeometry();
293
+ if (!(geometry instanceof LineString))
294
+ return [];
295
+ const coords = geometry.getCoordinates();
296
+ if (coords.length < 2)
297
+ return [];
298
+ const last = coords[coords.length - 1];
299
+ const prev = coords[coords.length - 2];
300
+ const angle = Math.atan2(last[1] - prev[1], last[0] - prev[0]);
301
+ return [
302
+ new Style({
303
+ stroke: new Stroke({ color, width: 4, lineDash: [8, 4] }),
304
+ }),
305
+ new Style({
306
+ geometry: new Point(last),
307
+ image: new RegularShape({
308
+ fill: new Fill({ color }),
309
+ points: 3,
310
+ radius: 14,
311
+ rotation: -angle + Math.PI / 2,
312
+ }),
313
+ }),
314
+ ];
315
+ };
316
+ }
317
+ /**
318
+ * Style for Control Zones (Polygons).
319
+ */
320
+ createZoneStyle(color, opacity = 0.2) {
321
+ return new Style({
322
+ fill: new Fill({ color: this.hexToRgba(color, opacity) }),
323
+ stroke: new Stroke({ color, width: 2 }),
324
+ });
325
+ }
326
+ hexToRgba(hex, alpha) {
327
+ const r = parseInt(hex.slice(1, 3), 16);
328
+ const g = parseInt(hex.slice(3, 5), 16);
329
+ const b = parseInt(hex.slice(5, 7), 16);
330
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
331
+ }
332
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTacticalGraphicsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
333
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTacticalGraphicsService });
237
334
  }
238
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlMilitaryService, decorators: [{
335
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTacticalGraphicsService, decorators: [{
239
336
  type: Injectable
240
337
  }] });
241
338
 
242
339
  function withMilitary() {
243
- return { kind: 'military', providers: [OlMilitaryService] };
340
+ return {
341
+ kind: 'military',
342
+ providers: [OlMilitaryService, OlTacticalGraphicsService],
343
+ };
244
344
  }
245
345
  function provideMilitary() {
246
346
  return withMilitary();
@@ -252,4 +352,4 @@ function provideMilitary() {
252
352
  * Generated bundle index. Do not edit.
253
353
  */
254
354
 
255
- export { OlMilitaryService, provideMilitary, withMilitary };
355
+ export { OlMilitaryService, OlTacticalGraphicsService, provideMilitary, withMilitary };
@@ -107,8 +107,8 @@ class OlPopupComponent {
107
107
  this.overlay = null;
108
108
  this.currentMap = null;
109
109
  }
110
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlPopupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
111
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: OlPopupComponent, isStandalone: true, selector: "ol-popup", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, positioning: { classPropertyName: "positioning", publicName: "positioning", isSignal: true, isRequired: false, transformFunction: null }, autoPan: { classPropertyName: "autoPan", publicName: "autoPan", isSignal: true, isRequired: false, transformFunction: null }, closeButton: { classPropertyName: "closeButton", publicName: "closeButton", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { attributes: { "role": "dialog" } }, ngImport: i0, template: `
110
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlPopupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
111
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: OlPopupComponent, isStandalone: true, selector: "ol-popup", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, positioning: { classPropertyName: "positioning", publicName: "positioning", isSignal: true, isRequired: false, transformFunction: null }, autoPan: { classPropertyName: "autoPan", publicName: "autoPan", isSignal: true, isRequired: false, transformFunction: null }, closeButton: { classPropertyName: "closeButton", publicName: "closeButton", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { attributes: { "role": "dialog" } }, ngImport: i0, template: `
112
112
  @if (closeButton()) {
113
113
  <button type="button" class="ol-popup-close" aria-label="Close" (click)="onCloseClick()">
114
114
  ×
@@ -117,7 +117,7 @@ class OlPopupComponent {
117
117
  <ng-content />
118
118
  `, isInline: true, styles: [":host{display:block;position:relative}.ol-popup-close{position:absolute;top:4px;right:4px;width:20px;height:20px;padding:0;border:none;background:transparent;font-size:16px;line-height:1;cursor:pointer;color:inherit}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
119
119
  }
120
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlPopupComponent, decorators: [{
120
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlPopupComponent, decorators: [{
121
121
  type: Component,
122
122
  args: [{ selector: 'ol-popup', changeDetection: ChangeDetectionStrategy.OnPush, template: `
123
123
  @if (closeButton()) {
@@ -232,10 +232,10 @@ class OlTooltipDirective {
232
232
  this.listener = null;
233
233
  this.currentMap = null;
234
234
  }
235
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
236
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.4", type: OlTooltipDirective, isStandalone: true, selector: "[olTooltip]", inputs: { olTooltip: { classPropertyName: "olTooltip", publicName: "olTooltip", isSignal: true, isRequired: true, transformFunction: null }, olTooltipLayer: { classPropertyName: "olTooltipLayer", publicName: "olTooltipLayer", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
235
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
236
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.13", type: OlTooltipDirective, isStandalone: true, selector: "[olTooltip]", inputs: { olTooltip: { classPropertyName: "olTooltip", publicName: "olTooltip", isSignal: true, isRequired: true, transformFunction: null }, olTooltipLayer: { classPropertyName: "olTooltipLayer", publicName: "olTooltipLayer", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
237
237
  }
238
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlTooltipDirective, decorators: [{
238
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTooltipDirective, decorators: [{
239
239
  type: Directive,
240
240
  args: [{
241
241
  selector: '[olTooltip]',
@@ -410,10 +410,10 @@ class OlPopupService {
410
410
  this.popups.set(id, { id, overlay, componentRef: ref, dispose });
411
411
  });
412
412
  }
413
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlPopupService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
414
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlPopupService });
413
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlPopupService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
414
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlPopupService });
415
415
  }
416
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlPopupService, decorators: [{
416
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlPopupService, decorators: [{
417
417
  type: Injectable
418
418
  }], ctorParameters: () => [] });
419
419
  // -----------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-helpers/openlayers",
3
- "version": "0.4.0",
3
+ "version": "0.5.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": {
@@ -82,4 +82,4 @@
82
82
  "dependencies": {
83
83
  "tslib": "^2.3.0"
84
84
  }
85
- }
85
+ }
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { InjectionToken } from '@angular/core';
2
+ import { InjectionToken, OnInit, ElementRef } from '@angular/core';
3
3
  import OLMap from 'ol/Map';
4
4
  import { OlFeature } from '@angular-helpers/openlayers/core';
5
5
 
@@ -87,7 +87,7 @@ interface LayerSwitcherItem {
87
87
  /** Display name for the layer */
88
88
  name: string;
89
89
  /** Type of the layer */
90
- type: 'vector' | 'tile' | 'image';
90
+ type: 'vector' | 'tile' | 'image' | 'heatmap';
91
91
  /** Whether the layer is currently visible */
92
92
  visible: boolean;
93
93
  /** Opacity of the layer (0-1) */
@@ -114,7 +114,7 @@ type LayerSwitcherPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-
114
114
  * </ol-layer-switcher>
115
115
  * ```
116
116
  */
117
- declare class OlLayerSwitcherComponent {
117
+ declare class OlLayerSwitcherComponent implements OnInit {
118
118
  position: _angular_core.InputSignal<"top-left" | "top-right" | "bottom-left" | "bottom-right">;
119
119
  layers: _angular_core.InputSignal<LayerSwitcherItem[]>;
120
120
  collapsible: _angular_core.InputSignal<boolean>;
@@ -129,6 +129,7 @@ declare class OlLayerSwitcherComponent {
129
129
  opacity: number;
130
130
  }>;
131
131
  protected isCollapsed: _angular_core.WritableSignal<boolean>;
132
+ ngOnInit(): void;
132
133
  toggleCollapsed(): void;
133
134
  toggleLayer(id: string): void;
134
135
  setOpacity(id: string, event: Event): void;
@@ -197,6 +198,25 @@ interface ControlConfig {
197
198
  className?: string;
198
199
  }
199
200
 
201
+ declare class OlGeolocationControlComponent {
202
+ private mapService;
203
+ private zoneHelper;
204
+ private destroyRef;
205
+ position: _angular_core.InputSignal<ControlPosition>;
206
+ trackingChange: _angular_core.OutputEmitterRef<boolean>;
207
+ controlElement: ElementRef<HTMLElement>;
208
+ protected tracking: _angular_core.WritableSignal<boolean>;
209
+ private control?;
210
+ private geolocation?;
211
+ private positionFeature?;
212
+ private accuracyFeature?;
213
+ private layer?;
214
+ constructor();
215
+ toggleTracking(): void;
216
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlGeolocationControlComponent, never>;
217
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlGeolocationControlComponent, "ol-geolocation-control", never, { "position": { "alias": "position"; "required": false; "isSignal": true; }; }, { "trackingChange": "trackingChange"; }, never, never, true, never>;
218
+ }
219
+
200
220
  declare class OlControlService {
201
221
  addCustomControl(element: HTMLElement, position: ControlPosition): void;
202
222
  removeCustomControl(element: HTMLElement): void;
@@ -211,5 +231,5 @@ declare class OlControlService {
211
231
  declare function withControls(): OlFeature<'controls'>;
212
232
  declare function provideControls(): OlFeature<'controls'>;
213
233
 
214
- export { OlAttributionControlComponent, OlBasemapSwitcherComponent, OlControlService, OlFullscreenControlComponent, OlLayerSwitcherComponent, OlRotateControlComponent, OlScaleLineControlComponent, OlZoomControlComponent, ROTATE_CONTROL_MAP_SERVICE, provideControls, withControls };
234
+ export { OlAttributionControlComponent, OlBasemapSwitcherComponent, OlControlService, OlFullscreenControlComponent, OlGeolocationControlComponent, OlLayerSwitcherComponent, OlRotateControlComponent, OlScaleLineControlComponent, OlZoomControlComponent, ROTATE_CONTROL_MAP_SERVICE, provideControls, withControls };
215
235
  export type { BasemapConfig, BasemapSwitcherPosition, ControlConfig, ControlPosition, LayerSwitcherItem, LayerSwitcherPosition, RotateControlMapService };