@angular-helpers/openlayers 0.5.0 → 0.5.1
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 +119 -34
- package/core/README.md +43 -0
- package/fesm2022/angular-helpers-openlayers-core.mjs +215 -2
- package/fesm2022/angular-helpers-openlayers-interactions.mjs +121 -54
- package/fesm2022/angular-helpers-openlayers-layers.mjs +450 -365
- package/fesm2022/angular-helpers-openlayers-overlays.mjs +26 -2
- package/package.json +1 -1
- package/types/angular-helpers-openlayers-core.d.ts +73 -4
- package/types/angular-helpers-openlayers-interactions.d.ts +10 -13
- package/types/angular-helpers-openlayers-layers.d.ts +16 -16
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { signal, computed, Injectable, inject, DestroyRef, input, effect, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
-
import { OlMapService, OlZoneHelper } from '@angular-helpers/openlayers/core';
|
|
3
|
+
import { OlMapService, OlZoneHelper, olFeatureToFeature } from '@angular-helpers/openlayers/core';
|
|
4
4
|
import { Subject } from 'rxjs';
|
|
5
5
|
import Select from 'ol/interaction/Select';
|
|
6
6
|
import { pointerMove, click } from 'ol/events/condition';
|
|
7
|
+
import { Style, Icon, Fill, Stroke, Circle } from 'ol/style';
|
|
7
8
|
import Draw from 'ol/interaction/Draw';
|
|
8
9
|
import Snap from 'ol/interaction/Snap';
|
|
9
10
|
import VectorSource from 'ol/source/Vector';
|
|
@@ -14,7 +15,6 @@ import VectorLayer from 'ol/layer/Vector';
|
|
|
14
15
|
import Overlay from 'ol/Overlay';
|
|
15
16
|
import { getLength, getArea } from 'ol/sphere';
|
|
16
17
|
import { unByKey } from 'ol/Observable';
|
|
17
|
-
import { Style, Circle, Fill, Stroke } from 'ol/style';
|
|
18
18
|
import { outputFromObservable } from '@angular/core/rxjs-interop';
|
|
19
19
|
import { filter } from 'rxjs/operators';
|
|
20
20
|
|
|
@@ -27,6 +27,7 @@ class InteractionStateService {
|
|
|
27
27
|
// Internal signals
|
|
28
28
|
interactions = signal([], ...(ngDevMode ? [{ debugName: "interactions" }] : /* istanbul ignore next */ []));
|
|
29
29
|
selectedFeaturesInternal = signal([], ...(ngDevMode ? [{ debugName: "selectedFeaturesInternal" }] : /* istanbul ignore next */ []));
|
|
30
|
+
hoveredFeatureInternal = signal(null, ...(ngDevMode ? [{ debugName: "hoveredFeatureInternal" }] : /* istanbul ignore next */ []));
|
|
30
31
|
// Event subjects
|
|
31
32
|
drawStartSubject = new Subject();
|
|
32
33
|
drawEndSubject = new Subject();
|
|
@@ -34,6 +35,7 @@ class InteractionStateService {
|
|
|
34
35
|
selectSubject = new Subject();
|
|
35
36
|
// Public readonly signals
|
|
36
37
|
selectedFeatures = computed(() => this.selectedFeaturesInternal(), ...(ngDevMode ? [{ debugName: "selectedFeatures" }] : /* istanbul ignore next */ []));
|
|
38
|
+
hoveredFeature = computed(() => this.hoveredFeatureInternal(), ...(ngDevMode ? [{ debugName: "hoveredFeature" }] : /* istanbul ignore next */ []));
|
|
37
39
|
selectionCount = computed(() => this.selectedFeaturesInternal().length, ...(ngDevMode ? [{ debugName: "selectionCount" }] : /* istanbul ignore next */ []));
|
|
38
40
|
hasSelection = computed(() => this.selectedFeaturesInternal().length > 0, ...(ngDevMode ? [{ debugName: "hasSelection" }] : /* istanbul ignore next */ []));
|
|
39
41
|
activeInteractions = computed(() => this.interactions().filter((i) => i.config.active !== false), ...(ngDevMode ? [{ debugName: "activeInteractions" }] : /* istanbul ignore next */ []));
|
|
@@ -95,6 +97,13 @@ class InteractionStateService {
|
|
|
95
97
|
clearSelection() {
|
|
96
98
|
this.selectedFeaturesInternal.set([]);
|
|
97
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Sets the currently hovered feature.
|
|
102
|
+
* @param feature - The hovered feature or null
|
|
103
|
+
*/
|
|
104
|
+
setHoveredFeature(feature) {
|
|
105
|
+
this.hoveredFeatureInternal.set(feature);
|
|
106
|
+
}
|
|
98
107
|
/**
|
|
99
108
|
* Emits a draw start event.
|
|
100
109
|
* @param event - The draw start event data
|
|
@@ -150,6 +159,7 @@ class InteractionStateService {
|
|
|
150
159
|
clearAll() {
|
|
151
160
|
this.interactions.set([]);
|
|
152
161
|
this.selectedFeaturesInternal.set([]);
|
|
162
|
+
this.hoveredFeatureInternal.set(null);
|
|
153
163
|
}
|
|
154
164
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: InteractionStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
155
165
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: InteractionStateService });
|
|
@@ -158,46 +168,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImpo
|
|
|
158
168
|
type: Injectable
|
|
159
169
|
}] });
|
|
160
170
|
|
|
161
|
-
// Feature conversion utilities for OpenLayers interactions
|
|
162
|
-
/**
|
|
163
|
-
* Converts an OpenLayers feature to the internal Feature format.
|
|
164
|
-
* Handles coordinate extraction and geometry type mapping.
|
|
165
|
-
*
|
|
166
|
-
* @param olFeature - The OpenLayers feature to convert
|
|
167
|
-
* @returns The converted Feature with normalized structure
|
|
168
|
-
*/
|
|
169
|
-
function olFeatureToFeature(olFeature) {
|
|
170
|
-
const geometry = olFeature.getGeometry();
|
|
171
|
-
const geomType = geometry?.getType() ?? 'Point';
|
|
172
|
-
// Convert coordinates based on geometry type
|
|
173
|
-
let coordinates;
|
|
174
|
-
if (geomType === 'Circle') {
|
|
175
|
-
// ol/geom/Circle has no getCoordinates() — use getCenter() instead
|
|
176
|
-
const circle = geometry;
|
|
177
|
-
coordinates = circle.getCenter();
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
// oxlint-disable-next-line no-explicit-any
|
|
181
|
-
const olCoords = geometry.getCoordinates();
|
|
182
|
-
if (Array.isArray(olCoords) && Array.isArray(olCoords[0])) {
|
|
183
|
-
// Multi-coordinate structures (LineString, Polygon, etc.)
|
|
184
|
-
coordinates = olCoords;
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
// Single point
|
|
188
|
-
coordinates = olCoords;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
id: olFeature.getId()?.toString() ?? `feature-${Math.random().toString(36).slice(2)}`,
|
|
193
|
-
geometry: {
|
|
194
|
-
type: geomType,
|
|
195
|
-
coordinates,
|
|
196
|
-
},
|
|
197
|
-
properties: olFeature.getProperties(),
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
171
|
// Select interaction creation service
|
|
202
172
|
/**
|
|
203
173
|
* Service responsible for creating and managing Select interactions.
|
|
@@ -225,22 +195,98 @@ class SelectInteractionService {
|
|
|
225
195
|
multi: config.multi ?? false,
|
|
226
196
|
hitTolerance: config.hitTolerance ?? 0,
|
|
227
197
|
condition: config.condition === 'pointerMove' ? pointerMove : click,
|
|
198
|
+
style: (olFeature, resolution) => {
|
|
199
|
+
const styles = [];
|
|
200
|
+
// 1. Resolve feature's own custom style (same as layer style Fn)
|
|
201
|
+
const abstractStyle = olFeature.get('__angular_helpers_style__');
|
|
202
|
+
const featureStyle = new Style();
|
|
203
|
+
let hasCustom = false;
|
|
204
|
+
if (abstractStyle) {
|
|
205
|
+
const { icon, fill, stroke } = abstractStyle;
|
|
206
|
+
if (icon?.src) {
|
|
207
|
+
featureStyle.setImage(new Icon({
|
|
208
|
+
src: icon.src,
|
|
209
|
+
...(icon.size ? { size: icon.size } : {}),
|
|
210
|
+
...(icon.anchor ? { anchor: icon.anchor } : {}),
|
|
211
|
+
}));
|
|
212
|
+
hasCustom = true;
|
|
213
|
+
}
|
|
214
|
+
if (fill) {
|
|
215
|
+
featureStyle.setFill(new Fill({ color: fill.color }));
|
|
216
|
+
hasCustom = true;
|
|
217
|
+
}
|
|
218
|
+
if (stroke) {
|
|
219
|
+
featureStyle.setStroke(new Stroke({ color: stroke.color, width: stroke.width }));
|
|
220
|
+
hasCustom = true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!hasCustom) {
|
|
224
|
+
// Fallback to standard vector default style
|
|
225
|
+
featureStyle.setFill(new Fill({ color: 'rgba(25, 118, 210, 0.3)' }));
|
|
226
|
+
featureStyle.setStroke(new Stroke({ color: '#1976d2', width: 2 }));
|
|
227
|
+
featureStyle.setImage(new Circle({
|
|
228
|
+
radius: 8,
|
|
229
|
+
fill: new Fill({ color: '#1976d2' }),
|
|
230
|
+
stroke: new Stroke({ color: '#d32f2f', width: 2 }),
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
styles.push(featureStyle);
|
|
234
|
+
// 2. Add high-fidelity premium selection highlight overlay
|
|
235
|
+
const geometry = olFeature.getGeometry();
|
|
236
|
+
if (geometry) {
|
|
237
|
+
const geomType = geometry.getType();
|
|
238
|
+
if (geomType === 'Point') {
|
|
239
|
+
// Outer glowing selection ring for Points
|
|
240
|
+
styles.push(new Style({
|
|
241
|
+
image: new Circle({
|
|
242
|
+
radius: 12,
|
|
243
|
+
stroke: new Stroke({
|
|
244
|
+
color: 'rgba(59, 130, 246, 0.8)',
|
|
245
|
+
width: 2,
|
|
246
|
+
lineDash: [4, 4],
|
|
247
|
+
}),
|
|
248
|
+
}),
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
// Outer thick dashed highlight for Polygons/LineStrings
|
|
253
|
+
styles.push(new Style({
|
|
254
|
+
stroke: new Stroke({
|
|
255
|
+
color: 'rgba(59, 130, 246, 0.9)',
|
|
256
|
+
width: 3,
|
|
257
|
+
lineDash: [6, 4],
|
|
258
|
+
}),
|
|
259
|
+
fill: new Fill({
|
|
260
|
+
color: 'rgba(59, 130, 246, 0.05)',
|
|
261
|
+
}),
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return styles;
|
|
266
|
+
},
|
|
228
267
|
});
|
|
229
268
|
// Listen to selection changes — use getFeatures().getArray() for the full
|
|
230
269
|
// accumulated collection, not e.selected which only contains newly added ones
|
|
231
270
|
select.on('select', (e) => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
this
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
271
|
+
const allFeatures = select
|
|
272
|
+
.getFeatures()
|
|
273
|
+
.getArray()
|
|
274
|
+
.map((f) => olFeatureToFeature(f));
|
|
275
|
+
if (config.condition === 'pointerMove') {
|
|
276
|
+
// Update signal outside Angular zone. Consumers of this signal will schedule
|
|
277
|
+
// targeted change detection automatically.
|
|
278
|
+
this.stateService.setHoveredFeature(allFeatures.length > 0 ? allFeatures[0] : null);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
this.zoneHelper.runInsideAngular(() => {
|
|
282
|
+
this.stateService.setSelectedFeatures(allFeatures);
|
|
283
|
+
this.stateService.emitSelect({
|
|
284
|
+
interactionId: id,
|
|
285
|
+
selected: e.selected.map((f) => olFeatureToFeature(f)),
|
|
286
|
+
deselected: e.deselected.map((f) => olFeatureToFeature(f)),
|
|
287
|
+
});
|
|
242
288
|
});
|
|
243
|
-
}
|
|
289
|
+
}
|
|
244
290
|
});
|
|
245
291
|
map.addInteraction(select);
|
|
246
292
|
const managed = {
|
|
@@ -368,7 +414,27 @@ class DrawInteractionService {
|
|
|
368
414
|
// Handle draw end event
|
|
369
415
|
draw.on('drawend', (e) => {
|
|
370
416
|
this.zoneHelper.runInsideAngular(() => {
|
|
417
|
+
const uniqueId = `drawn-${Date.now()}`;
|
|
418
|
+
e.feature.setId(uniqueId);
|
|
419
|
+
// Set default properties and styling on raw OL feature
|
|
420
|
+
e.feature.set('name', 'Sketch');
|
|
421
|
+
const defaultStyle = {
|
|
422
|
+
fill: { color: 'rgba(59, 130, 246, 0.2)' },
|
|
423
|
+
stroke: { color: '#3b82f6', width: 2 },
|
|
424
|
+
};
|
|
425
|
+
e.feature.set('__angular_helpers_style__', defaultStyle);
|
|
426
|
+
// Convert to internal Feature representation
|
|
371
427
|
const feature = olFeatureToFeature(e.feature);
|
|
428
|
+
feature.id = uniqueId;
|
|
429
|
+
feature.properties = {
|
|
430
|
+
...feature.properties,
|
|
431
|
+
name: 'Sketch',
|
|
432
|
+
strokeColor: '#3b82f6',
|
|
433
|
+
strokeWidth: 2,
|
|
434
|
+
fillColor: '#3b82f6',
|
|
435
|
+
fillOpacity: 0.2,
|
|
436
|
+
};
|
|
437
|
+
feature.style = defaultStyle;
|
|
372
438
|
const event = { interactionId: id, feature, type: config.type };
|
|
373
439
|
this.stateService.emitDrawEnd(event);
|
|
374
440
|
});
|
|
@@ -497,6 +563,7 @@ class OlInteractionService {
|
|
|
497
563
|
modifyService = inject(ModifyInteractionService);
|
|
498
564
|
// Public signals and observables delegated to state service
|
|
499
565
|
selectedFeatures = this.stateService.selectedFeatures;
|
|
566
|
+
hoveredFeature = this.stateService.hoveredFeature;
|
|
500
567
|
selectionCount = this.stateService.selectionCount;
|
|
501
568
|
hasSelection = this.stateService.hasSelection;
|
|
502
569
|
activeInteractions = this.stateService.activeInteractions;
|
|
@@ -1007,4 +1074,4 @@ function withMeasurementInteraction() {
|
|
|
1007
1074
|
* Generated bundle index. Do not edit.
|
|
1008
1075
|
*/
|
|
1009
1076
|
|
|
1010
|
-
export { DrawInteractionService, InteractionStateService, MeasurementInteractionService, ModifyInteractionService, OlDrawInteractionComponent, OlInteractionService, OlModifyInteractionComponent, OlSelectInteractionComponent, SelectInteractionService,
|
|
1077
|
+
export { DrawInteractionService, InteractionStateService, MeasurementInteractionService, ModifyInteractionService, OlDrawInteractionComponent, OlInteractionService, OlModifyInteractionComponent, OlSelectInteractionComponent, SelectInteractionService, provideInteractions, withDrawInteraction, withInteractions, withMeasurementInteraction, withModifyInteraction, withSelectInteraction };
|