@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,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
|
-
*
|
|
15
|
-
* - `createMilSymbol` uses the milsymbol library via
|
|
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
|
-
|
|
20
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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).
|
|
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
|
-
|
|
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
|
|
130
|
-
*
|
|
131
|
-
*
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
*
|
|
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
|
|
153
|
-
|
|
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`
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
236
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
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.
|
|
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 {
|
|
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.
|
|
111
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
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.
|
|
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.
|
|
236
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.
|
|
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.
|
|
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.
|
|
414
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
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.
|
|
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.
|
|
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 };
|