@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.
@@ -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
- this.zoneHelper.runInsideAngular(() => {
233
- const allSelected = select
234
- .getFeatures()
235
- .getArray()
236
- .map((f) => olFeatureToFeature(f));
237
- this.stateService.setSelectedFeatures(allSelected);
238
- this.stateService.emitSelect({
239
- interactionId: id,
240
- selected: e.selected.map((f) => olFeatureToFeature(f)),
241
- deselected: e.deselected.map((f) => olFeatureToFeature(f)),
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, olFeatureToFeature, provideInteractions, withDrawInteraction, withInteractions, withMeasurementInteraction, withModifyInteraction, withSelectInteraction };
1077
+ export { DrawInteractionService, InteractionStateService, MeasurementInteractionService, ModifyInteractionService, OlDrawInteractionComponent, OlInteractionService, OlModifyInteractionComponent, OlSelectInteractionComponent, SelectInteractionService, provideInteractions, withDrawInteraction, withInteractions, withMeasurementInteraction, withModifyInteraction, withSelectInteraction };