@angular-helpers/openlayers 0.3.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.
- package/README.md +102 -6
- package/fesm2022/angular-helpers-openlayers-controls.mjs +289 -36
- package/fesm2022/angular-helpers-openlayers-core.mjs +215 -16
- package/fesm2022/angular-helpers-openlayers-interactions.mjs +426 -23
- package/fesm2022/angular-helpers-openlayers-layers.mjs +717 -38
- package/fesm2022/angular-helpers-openlayers-military.mjs +329 -12
- package/fesm2022/angular-helpers-openlayers-overlays.mjs +11 -10
- package/package.json +6 -2
- package/types/angular-helpers-openlayers-controls.d.ts +24 -4
- package/types/angular-helpers-openlayers-core.d.ts +143 -4
- package/types/angular-helpers-openlayers-interactions.d.ts +127 -23
- package/types/angular-helpers-openlayers-layers.d.ts +194 -35
- package/types/angular-helpers-openlayers-military.d.ts +160 -16
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, NgZone, Injectable, DestroyRef, input, output, viewChild, afterNextRender, effect, ChangeDetectionStrategy, Component, makeEnvironmentProviders } from '@angular/core';
|
|
2
|
+
import { inject, NgZone, Injectable, signal, DestroyRef, input, output, viewChild, afterNextRender, effect, ChangeDetectionStrategy, Component, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
3
3
|
import OLMap from 'ol/Map';
|
|
4
4
|
import View from 'ol/View';
|
|
5
|
-
import { fromLonLat, toLonLat } from 'ol/proj';
|
|
5
|
+
import { fromLonLat, toLonLat, get } from 'ol/proj';
|
|
6
|
+
import { offset } from 'ol/sphere';
|
|
7
|
+
import { register } from 'ol/proj/proj4';
|
|
6
8
|
|
|
7
9
|
// ZoneHelperService - Handles NgZone compatibility for zoneless mode
|
|
8
10
|
/**
|
|
@@ -52,10 +54,10 @@ class OlZoneHelper {
|
|
|
52
54
|
}
|
|
53
55
|
return fn();
|
|
54
56
|
}
|
|
55
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
56
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
57
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlZoneHelper, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
58
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlZoneHelper });
|
|
57
59
|
}
|
|
58
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
60
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlZoneHelper, decorators: [{
|
|
59
61
|
type: Injectable
|
|
60
62
|
}] });
|
|
61
63
|
|
|
@@ -64,13 +66,22 @@ class OlMapService {
|
|
|
64
66
|
zoneHelper = inject(OlZoneHelper);
|
|
65
67
|
map = null;
|
|
66
68
|
readyCallbacks = [];
|
|
69
|
+
_resolution = signal(1, ...(ngDevMode ? [{ debugName: "_resolution" }] : /* istanbul ignore next */ []));
|
|
70
|
+
/** Signal that emits the current map resolution in meters per pixel */
|
|
71
|
+
resolution = this._resolution.asReadonly();
|
|
67
72
|
setMap(map) {
|
|
68
73
|
this.map = map;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
if (map) {
|
|
75
|
+
this._resolution.set(map.getView()?.getResolution() ?? 1);
|
|
76
|
+
const callbacks = this.readyCallbacks.splice(0);
|
|
77
|
+
for (const cb of callbacks) {
|
|
78
|
+
cb(map);
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
81
|
}
|
|
82
|
+
setResolution(resolution) {
|
|
83
|
+
this._resolution.set(resolution);
|
|
84
|
+
}
|
|
74
85
|
getMap() {
|
|
75
86
|
return this.map;
|
|
76
87
|
}
|
|
@@ -134,10 +145,10 @@ class OlMapService {
|
|
|
134
145
|
rotation: view.getRotation() ?? 0,
|
|
135
146
|
};
|
|
136
147
|
}
|
|
137
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
138
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
148
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlMapService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
149
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlMapService });
|
|
139
150
|
}
|
|
140
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
151
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlMapService, decorators: [{
|
|
141
152
|
type: Injectable
|
|
142
153
|
}] });
|
|
143
154
|
|
|
@@ -155,6 +166,7 @@ class OlMapComponent {
|
|
|
155
166
|
mapDblClick = output();
|
|
156
167
|
mapContainerRef = viewChild.required('mapContainer');
|
|
157
168
|
map;
|
|
169
|
+
resizeObserver;
|
|
158
170
|
constructor() {
|
|
159
171
|
afterNextRender(() => this.initMap());
|
|
160
172
|
effect(() => {
|
|
@@ -186,8 +198,26 @@ class OlMapComponent {
|
|
|
186
198
|
});
|
|
187
199
|
this.map = new OLMap({ target: container, view, layers: [] });
|
|
188
200
|
this.mapService.setMap(this.map);
|
|
201
|
+
// Add ResizeObserver to handle container size changes (e.g. sidebars, window resize)
|
|
202
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
203
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
204
|
+
if (this.map) {
|
|
205
|
+
// Using requestAnimationFrame prevents "ResizeObserver loop limit exceeded" errors
|
|
206
|
+
requestAnimationFrame(() => {
|
|
207
|
+
if (this.map)
|
|
208
|
+
this.map.updateSize();
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
this.resizeObserver.observe(container);
|
|
213
|
+
}
|
|
189
214
|
view.on('change:center', () => this.zoneHelper.runInsideAngular(() => this.emitViewChange()));
|
|
190
|
-
view.on('change:resolution', () =>
|
|
215
|
+
view.on('change:resolution', () => {
|
|
216
|
+
this.zoneHelper.runInsideAngular(() => {
|
|
217
|
+
this.mapService.setResolution(view.getResolution() ?? 1);
|
|
218
|
+
this.emitViewChange();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
191
221
|
this.map.on('click', (e) => this.zoneHelper.runInsideAngular(() => this.mapClick.emit({
|
|
192
222
|
coordinate: toLonLat(e.coordinate, this.projection()),
|
|
193
223
|
pixel: e.pixel,
|
|
@@ -200,6 +230,10 @@ class OlMapComponent {
|
|
|
200
230
|
this.emitViewChange();
|
|
201
231
|
}
|
|
202
232
|
destroyMap() {
|
|
233
|
+
if (this.resizeObserver) {
|
|
234
|
+
this.resizeObserver.disconnect();
|
|
235
|
+
this.resizeObserver = undefined;
|
|
236
|
+
}
|
|
203
237
|
if (this.map) {
|
|
204
238
|
this.zoneHelper.runOutsideAngular(() => {
|
|
205
239
|
this.map.setTarget(undefined);
|
|
@@ -254,16 +288,148 @@ class OlMapComponent {
|
|
|
254
288
|
});
|
|
255
289
|
}
|
|
256
290
|
}
|
|
257
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
258
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.
|
|
291
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlMapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
292
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.13", type: OlMapComponent, isStandalone: true, selector: "ol-map", inputs: { center: { classPropertyName: "center", publicName: "center", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, rotation: { classPropertyName: "rotation", publicName: "rotation", isSignal: true, isRequired: false, transformFunction: null }, projection: { classPropertyName: "projection", publicName: "projection", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { viewChange: "viewChange", mapClick: "mapClick", mapDblClick: "mapDblClick" }, viewQueries: [{ propertyName: "mapContainerRef", first: true, predicate: ["mapContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `<div class="ol-map-container" #mapContainer></div>
|
|
259
293
|
<ng-content />`, isInline: true, styles: [":host{display:block;width:100%;height:100%;position:relative}.ol-map-container{width:100%;height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
260
294
|
}
|
|
261
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
295
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlMapComponent, decorators: [{
|
|
262
296
|
type: Component,
|
|
263
297
|
args: [{ selector: 'ol-map', template: `<div class="ol-map-container" #mapContainer></div>
|
|
264
298
|
<ng-content />`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;width:100%;height:100%;position:relative}.ol-map-container{width:100%;height:100%}\n"] }]
|
|
265
299
|
}], ctorParameters: () => [], propDecorators: { center: [{ type: i0.Input, args: [{ isSignal: true, alias: "center", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], rotation: [{ type: i0.Input, args: [{ isSignal: true, alias: "rotation", required: false }] }], projection: [{ type: i0.Input, args: [{ isSignal: true, alias: "projection", required: false }] }], viewChange: [{ type: i0.Output, args: ["viewChange"] }], mapClick: [{ type: i0.Output, args: ["mapClick"] }], mapDblClick: [{ type: i0.Output, args: ["mapDblClick"] }], mapContainerRef: [{ type: i0.ViewChild, args: ['mapContainer', { isSignal: true }] }] } });
|
|
266
300
|
|
|
301
|
+
// OlGeometryService — general purpose geometry helpers
|
|
302
|
+
/**
|
|
303
|
+
* Service exposing general purpose geometry helpers for creating
|
|
304
|
+
* approximated polygons (ellipses, sectors, donuts) from metric parameters.
|
|
305
|
+
*/
|
|
306
|
+
class OlGeometryService {
|
|
307
|
+
idCounter = 0;
|
|
308
|
+
/**
|
|
309
|
+
* Build a `Feature<Polygon>` approximating an ellipse centered at
|
|
310
|
+
* `config.center`. See {@link EllipseConfig} for parameter semantics.
|
|
311
|
+
*/
|
|
312
|
+
createEllipse(config) {
|
|
313
|
+
const { center, semiMajor, semiMinor, rotation = 0, segments = 64, properties } = config;
|
|
314
|
+
if (semiMajor <= 0 || semiMinor <= 0) {
|
|
315
|
+
throw new RangeError('semiMajor and semiMinor must be positive');
|
|
316
|
+
}
|
|
317
|
+
if (segments < 8) {
|
|
318
|
+
throw new RangeError('segments must be >= 8');
|
|
319
|
+
}
|
|
320
|
+
const cosR = Math.cos(rotation);
|
|
321
|
+
const sinR = Math.sin(rotation);
|
|
322
|
+
const ring = [];
|
|
323
|
+
for (let i = 0; i < segments; i++) {
|
|
324
|
+
const theta = (i / segments) * Math.PI * 2;
|
|
325
|
+
// Ellipse in local axis-aligned frame, then rotated by `rotation`.
|
|
326
|
+
const ax = Math.cos(theta) * semiMajor;
|
|
327
|
+
const ay = Math.sin(theta) * semiMinor;
|
|
328
|
+
const dx = ax * cosR - ay * sinR;
|
|
329
|
+
const dy = ax * sinR + ay * cosR;
|
|
330
|
+
ring.push(this.offsetMetersToLonLat(center, dx, dy));
|
|
331
|
+
}
|
|
332
|
+
ring.push(ring[0]); // close the ring
|
|
333
|
+
return {
|
|
334
|
+
id: this.nextId('ellipse'),
|
|
335
|
+
geometry: { type: 'Polygon', coordinates: [ring] },
|
|
336
|
+
properties,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Build a `Feature<Polygon>` for a circular sector (pie slice).
|
|
341
|
+
* See {@link SectorConfig} for parameter semantics.
|
|
342
|
+
*/
|
|
343
|
+
createSector(config) {
|
|
344
|
+
const { center, radius, startAngle, endAngle, segments = 32, properties } = config;
|
|
345
|
+
if (radius <= 0) {
|
|
346
|
+
throw new RangeError('radius must be positive');
|
|
347
|
+
}
|
|
348
|
+
if (endAngle <= startAngle) {
|
|
349
|
+
throw new RangeError('endAngle must be greater than startAngle');
|
|
350
|
+
}
|
|
351
|
+
if (endAngle - startAngle > Math.PI * 2) {
|
|
352
|
+
throw new RangeError('sector cannot exceed full circle');
|
|
353
|
+
}
|
|
354
|
+
if (segments < 4) {
|
|
355
|
+
throw new RangeError('segments must be >= 4');
|
|
356
|
+
}
|
|
357
|
+
const ring = [center];
|
|
358
|
+
const span = endAngle - startAngle;
|
|
359
|
+
for (let i = 0; i <= segments; i++) {
|
|
360
|
+
const theta = startAngle + (i / segments) * span;
|
|
361
|
+
const dx = Math.cos(theta) * radius;
|
|
362
|
+
const dy = Math.sin(theta) * radius;
|
|
363
|
+
ring.push(this.offsetMetersToLonLat(center, dx, dy));
|
|
364
|
+
}
|
|
365
|
+
ring.push(center); // close back to apex
|
|
366
|
+
return {
|
|
367
|
+
id: this.nextId('sector'),
|
|
368
|
+
geometry: { type: 'Polygon', coordinates: [ring] },
|
|
369
|
+
properties,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Build a `Feature<Polygon>` for a donut (annular ring). The output has
|
|
374
|
+
* two rings: an outer ring wound counter-clockwise and an inner ring
|
|
375
|
+
* wound clockwise so the GeoJSON right-hand rule renders the hole.
|
|
376
|
+
*/
|
|
377
|
+
createDonut(config) {
|
|
378
|
+
const { center, outerRadius, innerRadius, segments = 64, properties } = config;
|
|
379
|
+
if (outerRadius <= 0 || innerRadius <= 0) {
|
|
380
|
+
throw new RangeError('radii must be positive');
|
|
381
|
+
}
|
|
382
|
+
if (outerRadius <= innerRadius) {
|
|
383
|
+
throw new RangeError('outerRadius must be greater than innerRadius');
|
|
384
|
+
}
|
|
385
|
+
if (segments < 8) {
|
|
386
|
+
throw new RangeError('segments must be >= 8');
|
|
387
|
+
}
|
|
388
|
+
const outer = [];
|
|
389
|
+
const inner = [];
|
|
390
|
+
for (let i = 0; i < segments; i++) {
|
|
391
|
+
const theta = (i / segments) * Math.PI * 2;
|
|
392
|
+
const cosT = Math.cos(theta);
|
|
393
|
+
const sinT = Math.sin(theta);
|
|
394
|
+
// Outer ring: CCW (theta increasing)
|
|
395
|
+
outer.push(this.offsetMetersToLonLat(center, cosT * outerRadius, sinT * outerRadius));
|
|
396
|
+
// Inner ring: CW — sample the SAME thetas but we'll reverse the
|
|
397
|
+
// accumulator below so the ring is traversed in the opposite sense.
|
|
398
|
+
inner.push(this.offsetMetersToLonLat(center, cosT * innerRadius, sinT * innerRadius));
|
|
399
|
+
}
|
|
400
|
+
inner.reverse();
|
|
401
|
+
outer.push(outer[0]);
|
|
402
|
+
inner.push(inner[0]);
|
|
403
|
+
return {
|
|
404
|
+
id: this.nextId('donut'),
|
|
405
|
+
geometry: { type: 'Polygon', coordinates: [outer, inner] },
|
|
406
|
+
properties,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Project an `(dx, dy)` meter offset from `center` to lon/lat using true
|
|
411
|
+
* geodesic math (Vincenty's formulae) via ol/sphere.
|
|
412
|
+
*/
|
|
413
|
+
offsetMetersToLonLat(center, dx, dy) {
|
|
414
|
+
if (dx === 0 && dy === 0)
|
|
415
|
+
return [...center];
|
|
416
|
+
const distance = Math.hypot(dx, dy);
|
|
417
|
+
const bearing = Math.atan2(dx, dy);
|
|
418
|
+
return offset(center, distance, bearing);
|
|
419
|
+
}
|
|
420
|
+
nextId(kind) {
|
|
421
|
+
return `${kind}-${++this.idCounter}`;
|
|
422
|
+
}
|
|
423
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlGeometryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
424
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlGeometryService, providedIn: 'root' });
|
|
425
|
+
}
|
|
426
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlGeometryService, decorators: [{
|
|
427
|
+
type: Injectable,
|
|
428
|
+
args: [{
|
|
429
|
+
providedIn: 'root',
|
|
430
|
+
}]
|
|
431
|
+
}] });
|
|
432
|
+
|
|
267
433
|
// Provider functions
|
|
268
434
|
function provideOpenLayers(...features) {
|
|
269
435
|
return makeEnvironmentProviders([
|
|
@@ -273,10 +439,43 @@ function provideOpenLayers(...features) {
|
|
|
273
439
|
]);
|
|
274
440
|
}
|
|
275
441
|
|
|
442
|
+
/**
|
|
443
|
+
* Registers custom projections using proj4.
|
|
444
|
+
*
|
|
445
|
+
* @param proj4 - The proj4 instance (must be passed to avoid strong dependency on proj4 package)
|
|
446
|
+
* @param definitions - Array of projection definitions
|
|
447
|
+
* @returns OlFeature for projections
|
|
448
|
+
*/
|
|
449
|
+
function withProjections(proj4, definitions) {
|
|
450
|
+
return {
|
|
451
|
+
kind: 'projections',
|
|
452
|
+
providers: [
|
|
453
|
+
{
|
|
454
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
455
|
+
multi: true,
|
|
456
|
+
useValue: () => {
|
|
457
|
+
definitions.forEach((d) => {
|
|
458
|
+
proj4.defs(d.code, d.def);
|
|
459
|
+
});
|
|
460
|
+
register(proj4);
|
|
461
|
+
definitions.forEach((d) => {
|
|
462
|
+
if (d.extent) {
|
|
463
|
+
const proj = get(d.code);
|
|
464
|
+
if (proj) {
|
|
465
|
+
proj.setExtent(d.extent);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
276
475
|
// @angular-helpers/openlayers/core
|
|
277
476
|
|
|
278
477
|
/**
|
|
279
478
|
* Generated bundle index. Do not edit.
|
|
280
479
|
*/
|
|
281
480
|
|
|
282
|
-
export { OlMapComponent, OlMapService, OlZoneHelper, provideOpenLayers };
|
|
481
|
+
export { OlGeometryService, OlMapComponent, OlMapService, OlZoneHelper, provideOpenLayers, withProjections };
|