@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.
- package/README.md +14 -6
- package/fesm2022/angular-helpers-openlayers-controls.mjs +289 -36
- package/fesm2022/angular-helpers-openlayers-core.mjs +197 -16
- package/fesm2022/angular-helpers-openlayers-interactions.mjs +414 -23
- package/fesm2022/angular-helpers-openlayers-layers.mjs +634 -83
- package/fesm2022/angular-helpers-openlayers-military.mjs +244 -144
- package/fesm2022/angular-helpers-openlayers-overlays.mjs +9 -9
- package/package.json +2 -2
- package/types/angular-helpers-openlayers-controls.d.ts +24 -4
- package/types/angular-helpers-openlayers-core.d.ts +126 -4
- package/types/angular-helpers-openlayers-interactions.d.ts +120 -23
- package/types/angular-helpers-openlayers-layers.d.ts +152 -7
- package/types/angular-helpers-openlayers-military.d.ts +84 -94
|
@@ -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
|
|
|
@@ -201,7 +212,12 @@ class OlMapComponent {
|
|
|
201
212
|
this.resizeObserver.observe(container);
|
|
202
213
|
}
|
|
203
214
|
view.on('change:center', () => this.zoneHelper.runInsideAngular(() => this.emitViewChange()));
|
|
204
|
-
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
|
+
});
|
|
205
221
|
this.map.on('click', (e) => this.zoneHelper.runInsideAngular(() => this.mapClick.emit({
|
|
206
222
|
coordinate: toLonLat(e.coordinate, this.projection()),
|
|
207
223
|
pixel: e.pixel,
|
|
@@ -272,16 +288,148 @@ class OlMapComponent {
|
|
|
272
288
|
});
|
|
273
289
|
}
|
|
274
290
|
}
|
|
275
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
276
|
-
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>
|
|
277
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 });
|
|
278
294
|
}
|
|
279
|
-
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: [{
|
|
280
296
|
type: Component,
|
|
281
297
|
args: [{ selector: 'ol-map', template: `<div class="ol-map-container" #mapContainer></div>
|
|
282
298
|
<ng-content />`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;width:100%;height:100%;position:relative}.ol-map-container{width:100%;height:100%}\n"] }]
|
|
283
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 }] }] } });
|
|
284
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
|
+
|
|
285
433
|
// Provider functions
|
|
286
434
|
function provideOpenLayers(...features) {
|
|
287
435
|
return makeEnvironmentProviders([
|
|
@@ -291,10 +439,43 @@ function provideOpenLayers(...features) {
|
|
|
291
439
|
]);
|
|
292
440
|
}
|
|
293
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
|
+
|
|
294
475
|
// @angular-helpers/openlayers/core
|
|
295
476
|
|
|
296
477
|
/**
|
|
297
478
|
* Generated bundle index. Do not edit.
|
|
298
479
|
*/
|
|
299
480
|
|
|
300
|
-
export { OlMapComponent, OlMapService, OlZoneHelper, provideOpenLayers };
|
|
481
|
+
export { OlGeometryService, OlMapComponent, OlMapService, OlZoneHelper, provideOpenLayers, withProjections };
|