@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.
@@ -1,21 +1,39 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, signal, computed, Injectable, DestroyRef, input, afterNextRender, effect, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { inject, signal, computed, Injectable, input, ChangeDetectionStrategy, Component, DestroyRef, contentChild, afterNextRender, effect } from '@angular/core';
3
3
  import VectorLayer from 'ol/layer/Vector';
4
+ import HeatmapLayer from 'ol/layer/Heatmap';
4
5
  import TileLayer from 'ol/layer/Tile';
5
6
  import ImageLayer from 'ol/layer/Image';
6
7
  import VectorSource from 'ol/source/Vector';
7
- import { Feature } from 'ol';
8
+ import OLFeature from 'ol/Feature';
8
9
  import { Point, LineString, Polygon, Circle } from 'ol/geom';
9
10
  import { fromLonLat } from 'ol/proj';
10
- import { Style, Circle as Circle$1, Stroke, Fill } from 'ol/style';
11
+ import { getCenter } from 'ol/extent';
12
+ import { Style, Circle as Circle$1, Stroke, Fill, Icon } from 'ol/style';
13
+ import Text from 'ol/style/Text';
14
+ import ClusterSource from 'ol/source/Cluster';
11
15
  import OSM from 'ol/source/OSM';
12
16
  import XYZ from 'ol/source/XYZ';
13
17
  import TileWMS from 'ol/source/TileWMS';
14
18
  import ImageWMS from 'ol/source/ImageWMS';
15
19
  import ImageStatic from 'ol/source/ImageStatic';
20
+ import GeoJSON from 'ol/format/GeoJSON';
21
+ import TopoJSON from 'ol/format/TopoJSON';
22
+ import KML from 'ol/format/KML';
16
23
  import { OlMapService } from '@angular-helpers/openlayers/core';
24
+ import WebGLVectorLayer from 'ol/layer/WebGLVector';
25
+ import WebGLTileLayer from 'ol/layer/WebGLTile';
26
+ import WebGLVectorTileLayer from 'ol/layer/WebGLVectorTile';
27
+ import VectorTileSource from 'ol/source/VectorTile';
28
+ import MVT from 'ol/format/MVT';
17
29
 
18
30
  // OlLayerService
31
+ /**
32
+ * Internal property key used to stash the abstract style metadata on the
33
+ * underlying `ol/Feature` so the layer style function can resolve a
34
+ * per-feature visual without colliding with user `properties`.
35
+ */
36
+ const STYLE_PROP = '__angular_helpers_style__';
19
37
  class OlLayerService {
20
38
  mapService = inject(OlMapService);
21
39
  layerCache = new Map();
@@ -49,6 +67,8 @@ class OlLayerService {
49
67
  switch (config.type) {
50
68
  case 'vector':
51
69
  return this.createVectorLayer(config, map);
70
+ case 'heatmap':
71
+ return this.createHeatmapLayer(config, map);
52
72
  case 'tile':
53
73
  return this.createTileLayer(config, map);
54
74
  case 'image':
@@ -84,6 +104,12 @@ class OlLayerService {
84
104
  layer.setVisible(visible);
85
105
  this.updateLayerState();
86
106
  }
107
+ else {
108
+ const pending = this.pendingConfigs.find((c) => c.id === id);
109
+ if (pending) {
110
+ pending.visible = visible;
111
+ }
112
+ }
87
113
  }
88
114
  toggleVisibility(id) {
89
115
  const layer = this.layerCache.get(id);
@@ -101,6 +127,12 @@ class OlLayerService {
101
127
  layer.setOpacity(opacity);
102
128
  this.updateLayerState();
103
129
  }
130
+ else {
131
+ const pending = this.pendingConfigs.find((c) => c.id === id);
132
+ if (pending) {
133
+ pending.opacity = opacity;
134
+ }
135
+ }
104
136
  }
105
137
  setZIndex(id, zIndex) {
106
138
  const layer = this.layerCache.get(id);
@@ -108,6 +140,35 @@ class OlLayerService {
108
140
  layer.setZIndex(zIndex);
109
141
  this.updateLayerState();
110
142
  }
143
+ else {
144
+ const pending = this.pendingConfigs.find((c) => c.id === id);
145
+ if (pending) {
146
+ pending.zIndex = zIndex;
147
+ }
148
+ }
149
+ }
150
+ setHeatmapProperties(id, props) {
151
+ const layer = this.layerCache.get(id);
152
+ if (layer instanceof HeatmapLayer) {
153
+ if (props.blur !== undefined)
154
+ layer.setBlur(props.blur);
155
+ if (props.radius !== undefined)
156
+ layer.setRadius(props.radius);
157
+ if (props.weight !== undefined)
158
+ layer.setWeight(props.weight);
159
+ }
160
+ else {
161
+ const pending = this.pendingConfigs.find((c) => c.id === id);
162
+ if (pending && pending.type === 'heatmap') {
163
+ const heatmapConfig = pending;
164
+ if (props.blur !== undefined)
165
+ heatmapConfig.blur = props.blur;
166
+ if (props.radius !== undefined)
167
+ heatmapConfig.radius = props.radius;
168
+ if (props.weight !== undefined)
169
+ heatmapConfig.weight = props.weight;
170
+ }
171
+ }
111
172
  }
112
173
  isVisible(id) {
113
174
  return this.layerCache.get(id)?.getVisible() ?? false;
@@ -127,7 +188,15 @@ class OlLayerService {
127
188
  const layer = this.layerCache.get(id);
128
189
  if (!(layer instanceof VectorLayer))
129
190
  return;
130
- layer.getSource()?.clear();
191
+ const source = layer.getSource();
192
+ if (!source)
193
+ return;
194
+ // Handle Cluster source: clear the underlying VectorSource
195
+ const clusterSource = source;
196
+ const vectorSource = clusterSource.getSource
197
+ ? clusterSource.getSource()
198
+ : source;
199
+ vectorSource?.clear();
131
200
  }
132
201
  /**
133
202
  * Updates the features of a vector layer.
@@ -142,19 +211,34 @@ class OlLayerService {
142
211
  const source = layer.getSource();
143
212
  if (!source)
144
213
  return;
145
- // Get existing feature IDs from source
146
- const existingIds = new Set(source
147
- .getFeatures()
148
- .map((f) => f.getId())
149
- .filter((id) => id !== undefined));
150
- // Only add features that don't already exist in the source
151
- if (features && features.length > 0) {
152
- const newFeatures = features.filter((f) => !existingIds.has(f.id));
153
- if (newFeatures.length > 0) {
154
- const olFeatures = newFeatures.map((feature) => {
214
+ // Handle Cluster source: get the underlying VectorSource
215
+ const clusterSource = source;
216
+ const vectorSource = clusterSource.getSource
217
+ ? clusterSource.getSource()
218
+ : source;
219
+ if (!(vectorSource instanceof VectorSource))
220
+ return;
221
+ // Sync features: remove old ones, update existing ones, add new ones
222
+ if (features) {
223
+ const newFeatureIds = new Set(features.map((f) => f.id));
224
+ const sourceFeatures = vectorSource.getFeatures();
225
+ // 1. Remove features that are no longer in the input
226
+ sourceFeatures.forEach((f) => {
227
+ const id = f.getId();
228
+ if (id !== undefined && !newFeatureIds.has(id)) {
229
+ vectorSource.removeFeature(f);
230
+ }
231
+ });
232
+ // 2. Only add features that don't already exist in the source
233
+ const existingIds = new Set(vectorSource
234
+ .getFeatures()
235
+ .map((f) => f.getId())
236
+ .filter((id) => id !== undefined));
237
+ const featuresToAdd = features.filter((f) => !existingIds.has(f.id));
238
+ if (featuresToAdd.length > 0) {
239
+ const olFeatures = featuresToAdd.map((feature) => {
155
240
  const geom = feature.geometry;
156
241
  let geometry;
157
- // Validate coordinates exist before processing
158
242
  if (!geom.coordinates) {
159
243
  geometry = new Point([0, 0]);
160
244
  }
@@ -172,27 +256,35 @@ class OlLayerService {
172
256
  }
173
257
  else if (geom.type === 'Circle') {
174
258
  const center = fromLonLat(geom.coordinates);
175
- // Approximate radius in meters - use 1000m as default if not specified
176
259
  geometry = new Circle(center, geom.radius ?? 1000);
177
260
  }
178
261
  else {
179
262
  geometry = new Point([0, 0]);
180
263
  }
181
- const olFeature = new Feature({
264
+ const olFeature = new OLFeature({
182
265
  geometry,
183
266
  ...feature.properties,
184
267
  });
268
+ if (feature.style) {
269
+ olFeature.set(STYLE_PROP, feature.style);
270
+ }
185
271
  olFeature.setId(feature.id);
186
272
  return olFeature;
187
273
  });
188
- source.addFeatures(olFeatures);
274
+ vectorSource.addFeatures(olFeatures);
189
275
  }
190
276
  }
191
277
  }
192
278
  updateLayerState() {
193
279
  const layers = [];
194
280
  this.layerCache.forEach((layer, id) => {
195
- const type = layer instanceof VectorLayer ? 'vector' : layer instanceof TileLayer ? 'tile' : 'image';
281
+ let type = 'vector';
282
+ if (layer instanceof HeatmapLayer)
283
+ type = 'heatmap';
284
+ else if (layer instanceof TileLayer)
285
+ type = 'tile';
286
+ else if (layer instanceof ImageLayer)
287
+ type = 'image';
196
288
  layers.push({
197
289
  id,
198
290
  type: type,
@@ -204,7 +296,17 @@ class OlLayerService {
204
296
  this.layerState.set(layers.sort((a, b) => a.zIndex - b.zIndex));
205
297
  }
206
298
  createVectorLayer(config, map) {
207
- const source = new VectorSource();
299
+ const sourceOptions = {};
300
+ if (config.url && config.format) {
301
+ sourceOptions.url = config.url;
302
+ if (config.format === 'geojson')
303
+ sourceOptions.format = new GeoJSON();
304
+ else if (config.format === 'topojson')
305
+ sourceOptions.format = new TopoJSON();
306
+ else if (config.format === 'kml')
307
+ sourceOptions.format = new KML();
308
+ }
309
+ const vectorSource = new VectorSource(sourceOptions);
208
310
  // Add features if provided
209
311
  if (config.features && config.features.length > 0) {
210
312
  const olFeatures = config.features.map((feature) => {
@@ -234,15 +336,40 @@ class OlLayerService {
234
336
  else {
235
337
  geometry = new Point([0, 0]);
236
338
  }
237
- const olFeature = new Feature({
339
+ const olFeature = new OLFeature({
238
340
  geometry,
239
341
  ...feature.properties,
240
342
  });
343
+ if (feature.style) {
344
+ olFeature.set(STYLE_PROP, feature.style);
345
+ }
241
346
  olFeature.setId(feature.id);
242
347
  return olFeature;
243
348
  });
244
- source.addFeatures(olFeatures);
349
+ vectorSource.addFeatures(olFeatures);
245
350
  }
351
+ // Wrap in cluster source if enabled
352
+ const clusterCfg = config.cluster;
353
+ const source = clusterCfg?.enabled
354
+ ? new ClusterSource({
355
+ source: vectorSource,
356
+ distance: clusterCfg.distance ?? 40,
357
+ minDistance: clusterCfg.minDistance ?? 20,
358
+ geometryFunction: (feature) => {
359
+ const geometry = feature.getGeometry();
360
+ if (!geometry)
361
+ return null;
362
+ // For Point geometries, use as-is
363
+ if (geometry.getType() === 'Point') {
364
+ return geometry;
365
+ }
366
+ // For other geometries (Polygon, Circle, etc.), use center point
367
+ const extent = geometry.getExtent();
368
+ const center = getCenter(extent);
369
+ return new Point(center);
370
+ },
371
+ })
372
+ : vectorSource;
246
373
  // Default style for all geometry types (points, lines, polygons)
247
374
  const defaultStyle = new Style({
248
375
  fill: new Fill({ color: 'rgba(25, 118, 210, 0.3)' }),
@@ -253,12 +380,139 @@ class OlLayerService {
253
380
  stroke: new Stroke({ color: '#d32f2f', width: 2 }),
254
381
  }),
255
382
  });
383
+ // Resolved style: priority to stashed metadata, then config-level style, then default.
384
+ const userStyle = config.style;
385
+ const styleFn = (olFeature, resolution) => {
386
+ // 1. Per-feature style metadata (stashed via STYLE_PROP)
387
+ const abstractStyle = olFeature.get(STYLE_PROP);
388
+ if (abstractStyle) {
389
+ const style = new Style();
390
+ const { icon, fill, stroke } = abstractStyle;
391
+ if (icon?.src) {
392
+ style.setImage(new Icon({
393
+ src: icon.src,
394
+ ...(icon.size ? { size: icon.size } : {}),
395
+ ...(icon.anchor ? { anchor: icon.anchor } : {}),
396
+ }));
397
+ }
398
+ if (fill) {
399
+ style.setFill(new Fill({ color: fill.color }));
400
+ }
401
+ if (stroke) {
402
+ style.setStroke(new Stroke({ color: stroke.color, width: stroke.width }));
403
+ }
404
+ if (icon?.src || fill || stroke)
405
+ return style;
406
+ }
407
+ // 2. Layer-level style from config (supports functions or static styles)
408
+ if (userStyle) {
409
+ if (typeof userStyle === 'function') {
410
+ // Check if it's already an OL native feature or wrap if needed
411
+ // For simplicity in the demo, we pass the feature as-is or mapped
412
+ const feature = {
413
+ id: String(olFeature.getId() ?? ''),
414
+ geometry: {
415
+ type: olFeature.getGeometry()?.getType(),
416
+ coordinates: [], // coordinates not easily reversible without extra work
417
+ },
418
+ properties: olFeature.getProperties(),
419
+ };
420
+ return userStyle(feature, resolution);
421
+ }
422
+ return userStyle;
423
+ }
424
+ return defaultStyle;
425
+ };
426
+ // Cluster style: shows count badge when features are clustered, else delegates to styleFn
427
+ const clusterStyleFn = (olFeature, resolution) => {
428
+ const features = olFeature.get('features');
429
+ const size = features?.length ?? 1;
430
+ if (size > 1) {
431
+ const showCount = clusterCfg?.showCount ?? true;
432
+ return new Style({
433
+ image: new Circle$1({
434
+ radius: 15 + Math.min(size * 2, 15),
435
+ fill: new Fill({ color: 'rgba(255, 100, 100, 0.8)' }),
436
+ stroke: new Stroke({ color: '#fff', width: 2 }),
437
+ }),
438
+ text: showCount
439
+ ? new Text({
440
+ text: String(size),
441
+ fill: new Fill({ color: '#fff' }),
442
+ })
443
+ : undefined,
444
+ });
445
+ }
446
+ // Single feature in cluster: unwrap and call styleFn
447
+ const originalFeature = features?.[0];
448
+ if (originalFeature) {
449
+ // We MUST preserve the original geometry if it's a non-point ( spiderfication etc)
450
+ const style = styleFn(originalFeature, resolution);
451
+ if (style instanceof Style) {
452
+ const origGeom = originalFeature.getGeometry();
453
+ if (origGeom)
454
+ style.setGeometry(origGeom);
455
+ }
456
+ return style;
457
+ }
458
+ return styleFn(olFeature, resolution);
459
+ };
256
460
  const layer = new VectorLayer({
257
461
  source,
258
462
  visible: config.visible ?? true,
259
463
  opacity: config.opacity ?? 1,
260
464
  zIndex: config.zIndex,
261
- style: defaultStyle,
465
+ style: clusterCfg?.enabled ? clusterStyleFn : styleFn,
466
+ });
467
+ layer.set('id', config.id);
468
+ map.addLayer(layer);
469
+ this.layerCache.set(config.id, layer);
470
+ this.updateLayerState();
471
+ return { id: config.id };
472
+ }
473
+ createHeatmapLayer(config, map) {
474
+ const vectorSource = new VectorSource();
475
+ if (config.features && config.features.length > 0) {
476
+ const olFeatures = config.features.map((feature) => {
477
+ const geom = feature.geometry;
478
+ let geometry;
479
+ if (!geom.coordinates) {
480
+ geometry = new Point([0, 0]);
481
+ }
482
+ else if (geom.type === 'Point') {
483
+ const coords = geom.coordinates;
484
+ geometry = new Point(fromLonLat(coords));
485
+ }
486
+ else if (geom.type === 'LineString') {
487
+ const coords = geom.coordinates.map((c) => fromLonLat(c));
488
+ geometry = new LineString(coords);
489
+ }
490
+ else if (geom.type === 'Polygon') {
491
+ const rings = geom.coordinates.map((ring) => ring.map((c) => fromLonLat(c)));
492
+ geometry = new Polygon(rings);
493
+ }
494
+ else {
495
+ geometry = new Point([0, 0]);
496
+ }
497
+ const olFeature = new OLFeature({
498
+ geometry,
499
+ ...feature.properties,
500
+ });
501
+ olFeature.setId(feature.id);
502
+ return olFeature;
503
+ });
504
+ vectorSource.addFeatures(olFeatures);
505
+ }
506
+ const layer = new HeatmapLayer({
507
+ source: vectorSource,
508
+ visible: config.visible ?? true,
509
+ opacity: config.opacity ?? 1,
510
+ zIndex: config.zIndex,
511
+ ...(config.blur !== undefined && { blur: config.blur }),
512
+ ...(config.radius !== undefined && { radius: config.radius }),
513
+ ...(config.weight !== undefined && {
514
+ weight: config.weight,
515
+ }),
262
516
  });
263
517
  layer.set('id', config.id);
264
518
  map.addLayer(layer);
@@ -278,7 +532,7 @@ class OlLayerService {
278
532
  case 'wms':
279
533
  source = new TileWMS({
280
534
  url: config.source.url,
281
- params: config.source.params,
535
+ params: config.source.params ?? {},
282
536
  attributions: config.source.attributions,
283
537
  });
284
538
  break;
@@ -320,34 +574,69 @@ class OlLayerService {
320
574
  this.updateLayerState();
321
575
  return { id: config.id };
322
576
  }
323
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlLayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
324
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlLayerService });
577
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlLayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
578
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlLayerService });
325
579
  }
326
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlLayerService, decorators: [{
580
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlLayerService, decorators: [{
327
581
  type: Injectable
328
582
  }] });
329
583
 
584
+ class OlClusterComponent {
585
+ distance = input(40, ...(ngDevMode ? [{ debugName: "distance" }] : /* istanbul ignore next */ []));
586
+ minDistance = input(20, ...(ngDevMode ? [{ debugName: "minDistance" }] : /* istanbul ignore next */ []));
587
+ showCount = input(true, ...(ngDevMode ? [{ debugName: "showCount" }] : /* istanbul ignore next */ []));
588
+ featureStyle = input(...(ngDevMode ? [undefined, { debugName: "featureStyle" }] : /* istanbul ignore next */ []));
589
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlClusterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
590
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.13", type: OlClusterComponent, isStandalone: true, selector: "ol-cluster", inputs: { distance: { classPropertyName: "distance", publicName: "distance", isSignal: true, isRequired: false, transformFunction: null }, minDistance: { classPropertyName: "minDistance", publicName: "minDistance", isSignal: true, isRequired: false, transformFunction: null }, showCount: { classPropertyName: "showCount", publicName: "showCount", isSignal: true, isRequired: false, transformFunction: null }, featureStyle: { classPropertyName: "featureStyle", publicName: "featureStyle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
591
+ }
592
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlClusterComponent, decorators: [{
593
+ type: Component,
594
+ args: [{
595
+ selector: 'ol-cluster',
596
+ template: '',
597
+ changeDetection: ChangeDetectionStrategy.OnPush,
598
+ }]
599
+ }], propDecorators: { distance: [{ type: i0.Input, args: [{ isSignal: true, alias: "distance", required: false }] }], minDistance: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDistance", required: false }] }], showCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCount", required: false }] }], featureStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "featureStyle", required: false }] }] } });
600
+
330
601
  // OlVectorLayerComponent
331
602
  class OlVectorLayerComponent {
332
603
  layerService = inject(OlLayerService);
333
604
  destroyRef = inject(DestroyRef);
334
605
  id = input.required(...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
335
606
  features = input([], ...(ngDevMode ? [{ debugName: "features" }] : /* istanbul ignore next */ []));
607
+ url = input(...(ngDevMode ? [undefined, { debugName: "url" }] : /* istanbul ignore next */ []));
608
+ format = input(...(ngDevMode ? [undefined, { debugName: "format" }] : /* istanbul ignore next */ []));
336
609
  zIndex = input(0, ...(ngDevMode ? [{ debugName: "zIndex" }] : /* istanbul ignore next */ []));
337
610
  opacity = input(1, ...(ngDevMode ? [{ debugName: "opacity" }] : /* istanbul ignore next */ []));
338
611
  visible = input(true, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
339
612
  style = input(...(ngDevMode ? [undefined, { debugName: "style" }] : /* istanbul ignore next */ []));
613
+ cluster = input(...(ngDevMode ? [undefined, { debugName: "cluster" }] : /* istanbul ignore next */ []));
614
+ clusterComponent = contentChild(OlClusterComponent, ...(ngDevMode ? [{ debugName: "clusterComponent" }] : /* istanbul ignore next */ []));
340
615
  constructor() {
341
616
  // Initialize layer after DOM is ready
342
617
  afterNextRender(() => {
618
+ const clusterCmp = this.clusterComponent();
619
+ const resolvedClusterConfig = this.cluster() ??
620
+ (clusterCmp
621
+ ? {
622
+ enabled: true,
623
+ distance: clusterCmp.distance(),
624
+ minDistance: clusterCmp.minDistance(),
625
+ showCount: clusterCmp.showCount(),
626
+ featureStyle: clusterCmp.featureStyle(),
627
+ }
628
+ : undefined);
343
629
  this.layerService.addLayer({
344
630
  id: this.id(),
345
631
  type: 'vector',
346
632
  features: this.features(),
633
+ url: this.url(),
634
+ format: this.format(),
347
635
  zIndex: this.zIndex(),
348
636
  opacity: this.opacity(),
349
637
  visible: this.visible(),
350
638
  style: this.style(),
639
+ cluster: resolvedClusterConfig,
351
640
  });
352
641
  });
353
642
  // Effect to sync features when input changes
@@ -358,22 +647,31 @@ class OlVectorLayerComponent {
358
647
  this.layerService.updateFeatures(this.id(), currentFeatures);
359
648
  }
360
649
  });
650
+ effect(() => {
651
+ this.layerService.setOpacity(this.id(), this.opacity());
652
+ });
653
+ effect(() => {
654
+ this.layerService.setVisibility(this.id(), this.visible());
655
+ });
656
+ effect(() => {
657
+ this.layerService.setZIndex(this.id(), this.zIndex());
658
+ });
361
659
  // Cleanup when component is destroyed
362
660
  this.destroyRef.onDestroy(() => {
363
661
  this.layerService.removeLayer(this.id());
364
662
  });
365
663
  }
366
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlVectorLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
367
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.4", type: OlVectorLayerComponent, isStandalone: true, selector: "ol-vector-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, style: { classPropertyName: "style", publicName: "style", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
664
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlVectorLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
665
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.13", type: OlVectorLayerComponent, isStandalone: true, selector: "ol-vector-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: false, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, style: { classPropertyName: "style", publicName: "style", isSignal: true, isRequired: false, transformFunction: null }, cluster: { classPropertyName: "cluster", publicName: "cluster", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "clusterComponent", first: true, predicate: OlClusterComponent, descendants: true, isSignal: true }], ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
368
666
  }
369
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlVectorLayerComponent, decorators: [{
667
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlVectorLayerComponent, decorators: [{
370
668
  type: Component,
371
669
  args: [{
372
670
  selector: 'ol-vector-layer',
373
671
  template: '',
374
672
  changeDetection: ChangeDetectionStrategy.OnPush,
375
673
  }]
376
- }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], opacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "opacity", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], style: [{ type: i0.Input, args: [{ isSignal: true, alias: "style", required: false }] }] } });
674
+ }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: false }] }], url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], opacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "opacity", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], style: [{ type: i0.Input, args: [{ isSignal: true, alias: "style", required: false }] }], cluster: [{ type: i0.Input, args: [{ isSignal: true, alias: "cluster", required: false }] }], clusterComponent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => OlClusterComponent), { isSignal: true }] }] } });
377
675
 
378
676
  // OlTileLayerComponent
379
677
  class OlTileLayerComponent {
@@ -404,15 +702,24 @@ class OlTileLayerComponent {
404
702
  visible: this.visible(),
405
703
  });
406
704
  });
705
+ effect(() => {
706
+ this.layerService.setOpacity(this.id(), this.opacity());
707
+ });
708
+ effect(() => {
709
+ this.layerService.setVisibility(this.id(), this.visible());
710
+ });
711
+ effect(() => {
712
+ this.layerService.setZIndex(this.id(), this.zIndex());
713
+ });
407
714
  // Cleanup when component is destroyed
408
715
  this.destroyRef.onDestroy(() => {
409
716
  this.layerService.removeLayer(this.id());
410
717
  });
411
718
  }
412
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlTileLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
413
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.4", type: OlTileLayerComponent, isStandalone: true, selector: "ol-tile-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: true, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: false, transformFunction: null }, attributions: { classPropertyName: "attributions", publicName: "attributions", isSignal: true, isRequired: false, transformFunction: null }, params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
719
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTileLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
720
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.13", type: OlTileLayerComponent, isStandalone: true, selector: "ol-tile-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: true, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: false, transformFunction: null }, attributions: { classPropertyName: "attributions", publicName: "attributions", isSignal: true, isRequired: false, transformFunction: null }, params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
414
721
  }
415
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlTileLayerComponent, decorators: [{
722
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlTileLayerComponent, decorators: [{
416
723
  type: Component,
417
724
  args: [{
418
725
  selector: 'ol-tile-layer',
@@ -455,10 +762,10 @@ class OlImageLayerComponent {
455
762
  this.layerService.removeLayer(this.id());
456
763
  });
457
764
  }
458
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlImageLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
459
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.4", type: OlImageLayerComponent, isStandalone: true, selector: "ol-image-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, sourceType: { classPropertyName: "sourceType", publicName: "sourceType", isSignal: true, isRequired: true, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: true, transformFunction: null }, params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null }, imageExtent: { classPropertyName: "imageExtent", publicName: "imageExtent", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
765
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlImageLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
766
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.13", type: OlImageLayerComponent, isStandalone: true, selector: "ol-image-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, sourceType: { classPropertyName: "sourceType", publicName: "sourceType", isSignal: true, isRequired: true, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: true, transformFunction: null }, params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null }, imageExtent: { classPropertyName: "imageExtent", publicName: "imageExtent", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
460
767
  }
461
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlImageLayerComponent, decorators: [{
768
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlImageLayerComponent, decorators: [{
462
769
  type: Component,
463
770
  args: [{
464
771
  selector: 'ol-image-layer',
@@ -467,6 +774,378 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
467
774
  }]
468
775
  }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], sourceType: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceType", required: true }] }], url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: true }] }], params: [{ type: i0.Input, args: [{ isSignal: true, alias: "params", required: false }] }], imageExtent: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageExtent", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], opacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "opacity", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }] } });
469
776
 
777
+ class OlHeatmapLayerComponent {
778
+ layerService = inject(OlLayerService);
779
+ mapService = inject(OlMapService);
780
+ destroyRef = inject(DestroyRef);
781
+ id = input.required(...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
782
+ features = input([], ...(ngDevMode ? [{ debugName: "features" }] : /* istanbul ignore next */ []));
783
+ zIndex = input(0, ...(ngDevMode ? [{ debugName: "zIndex" }] : /* istanbul ignore next */ []));
784
+ opacity = input(1, ...(ngDevMode ? [{ debugName: "opacity" }] : /* istanbul ignore next */ []));
785
+ visible = input(true, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
786
+ blur = input(15, ...(ngDevMode ? [{ debugName: "blur" }] : /* istanbul ignore next */ []));
787
+ radius = input(8, ...(ngDevMode ? [{ debugName: "radius" }] : /* istanbul ignore next */ []));
788
+ /** Unit for radius and blur: 'pixels' (default) or 'meters' */
789
+ radiusUnit = input('pixels', ...(ngDevMode ? [{ debugName: "radiusUnit" }] : /* istanbul ignore next */ []));
790
+ weight = input(...(ngDevMode ? [undefined, { debugName: "weight" }] : /* istanbul ignore next */ []));
791
+ /** Computed radius in pixels based on current resolution if unit is 'meters' */
792
+ scaledRadius = computed(() => {
793
+ const r = this.radius();
794
+ if (this.radiusUnit() === 'pixels')
795
+ return r;
796
+ return r / this.mapService.resolution();
797
+ }, ...(ngDevMode ? [{ debugName: "scaledRadius" }] : /* istanbul ignore next */ []));
798
+ /** Computed blur in pixels based on current resolution if unit is 'meters' */
799
+ scaledBlur = computed(() => {
800
+ const b = this.blur();
801
+ if (this.radiusUnit() === 'pixels')
802
+ return b;
803
+ return b / this.mapService.resolution();
804
+ }, ...(ngDevMode ? [{ debugName: "scaledBlur" }] : /* istanbul ignore next */ []));
805
+ constructor() {
806
+ afterNextRender(() => {
807
+ this.layerService.addLayer({
808
+ id: this.id(),
809
+ type: 'heatmap',
810
+ features: this.features(),
811
+ zIndex: this.zIndex(),
812
+ opacity: this.opacity(),
813
+ visible: this.visible(),
814
+ blur: this.scaledBlur(),
815
+ radius: this.scaledRadius(),
816
+ weight: this.weight(),
817
+ });
818
+ });
819
+ effect(() => {
820
+ const currentFeatures = this.features();
821
+ if (this.layerService.getLayer(this.id())) {
822
+ this.layerService.updateFeatures(this.id(), currentFeatures);
823
+ }
824
+ });
825
+ effect(() => {
826
+ this.layerService.setOpacity(this.id(), this.opacity());
827
+ });
828
+ effect(() => {
829
+ this.layerService.setVisibility(this.id(), this.visible());
830
+ });
831
+ effect(() => {
832
+ this.layerService.setZIndex(this.id(), this.zIndex());
833
+ });
834
+ effect(() => {
835
+ this.layerService.setHeatmapProperties(this.id(), {
836
+ blur: this.scaledBlur(),
837
+ radius: this.scaledRadius(),
838
+ weight: this.weight(),
839
+ });
840
+ });
841
+ this.destroyRef.onDestroy(() => {
842
+ this.layerService.removeLayer(this.id());
843
+ });
844
+ }
845
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlHeatmapLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
846
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.13", type: OlHeatmapLayerComponent, isStandalone: true, selector: "ol-heatmap-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, blur: { classPropertyName: "blur", publicName: "blur", isSignal: true, isRequired: false, transformFunction: null }, radius: { classPropertyName: "radius", publicName: "radius", isSignal: true, isRequired: false, transformFunction: null }, radiusUnit: { classPropertyName: "radiusUnit", publicName: "radiusUnit", isSignal: true, isRequired: false, transformFunction: null }, weight: { classPropertyName: "weight", publicName: "weight", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
847
+ }
848
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlHeatmapLayerComponent, decorators: [{
849
+ type: Component,
850
+ args: [{
851
+ selector: 'ol-heatmap-layer',
852
+ template: '',
853
+ changeDetection: ChangeDetectionStrategy.OnPush,
854
+ }]
855
+ }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], opacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "opacity", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], blur: [{ type: i0.Input, args: [{ isSignal: true, alias: "blur", required: false }] }], radius: [{ type: i0.Input, args: [{ isSignal: true, alias: "radius", required: false }] }], radiusUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "radiusUnit", required: false }] }], weight: [{ type: i0.Input, args: [{ isSignal: true, alias: "weight", required: false }] }] } });
856
+
857
+ // OlWebGLVectorLayerComponent
858
+ // GPU-accelerated vector layer for large datasets (10k+ features).
859
+ // Uses FlatStyleLike instead of ol/style/Style. Must be manually disposed.
860
+ /**
861
+ * GPU-accelerated vector layer for rendering large datasets.
862
+ *
863
+ * Important: Uses WebGL 2 for rendering. Styles must be provided as
864
+ * `FlatStyleLike` objects (not `ol/style/Style` instances).
865
+ * Hit detection is disabled by default for performance.
866
+ *
867
+ * @usageNotes
868
+ * ```html
869
+ * <ol-webgl-vector-layer
870
+ * id="big-dataset"
871
+ * [features]="largeDataset()"
872
+ * [flatStyle]="{
873
+ * 'circle-radius': 6,
874
+ * 'circle-fill-color': ['match', ['get', 'type'], 'alert', 'red', 'blue'],
875
+ * 'stroke-color': '#333',
876
+ * 'stroke-width': 1
877
+ * }"
878
+ * [disableHitDetection]="true">
879
+ * </ol-webgl-vector-layer>
880
+ * ```
881
+ */
882
+ class OlWebGLVectorLayerComponent {
883
+ mapService = inject(OlMapService);
884
+ destroyRef = inject(DestroyRef);
885
+ /** Unique layer identifier */
886
+ id = input.required(...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
887
+ /** Features to render */
888
+ features = input([], ...(ngDevMode ? [{ debugName: "features" }] : /* istanbul ignore next */ []));
889
+ /** WebGL flat style (required — no default provided) */
890
+ flatStyle = input.required(...(ngDevMode ? [{ debugName: "flatStyle" }] : /* istanbul ignore next */ []));
891
+ /** Z-index for layer ordering */
892
+ zIndex = input(0, ...(ngDevMode ? [{ debugName: "zIndex" }] : /* istanbul ignore next */ []));
893
+ /** Opacity (0–1) */
894
+ opacity = input(1, ...(ngDevMode ? [{ debugName: "opacity" }] : /* istanbul ignore next */ []));
895
+ /** Layer visibility */
896
+ visible = input(true, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
897
+ /** Disable hit detection for better performance (default: true) */
898
+ disableHitDetection = input(true, ...(ngDevMode ? [{ debugName: "disableHitDetection" }] : /* istanbul ignore next */ []));
899
+ /** Style variables for dynamic expressions (e.g. `['var', 'threshold']`) */
900
+ variables = input(...(ngDevMode ? [undefined, { debugName: "variables" }] : /* istanbul ignore next */ []));
901
+ layer = null;
902
+ vectorSource = new VectorSource();
903
+ constructor() {
904
+ afterNextRender(() => {
905
+ const map = this.mapService.getMap();
906
+ if (!map)
907
+ return;
908
+ this.syncFeatures(this.features());
909
+ try {
910
+ this.layer = new WebGLVectorLayer({
911
+ source: this.vectorSource,
912
+ style: this.flatStyle(),
913
+ visible: this.visible(),
914
+ opacity: this.opacity(),
915
+ zIndex: this.zIndex(),
916
+ disableHitDetection: this.disableHitDetection(),
917
+ ...(this.variables() ? { variables: this.variables() } : {}),
918
+ });
919
+ this.layer.set('id', this.id());
920
+ map.addLayer(this.layer);
921
+ }
922
+ catch (err) {
923
+ // WebGL Vector Layer failed to initialize (e.g., not supported by browser)
924
+ }
925
+ });
926
+ // Reactive feature sync
927
+ effect(() => {
928
+ const currentFeatures = this.features();
929
+ if (this.layer) {
930
+ this.syncFeatures(currentFeatures);
931
+ }
932
+ });
933
+ // Reactive style updates
934
+ effect(() => {
935
+ this.layer?.setStyle(this.flatStyle());
936
+ });
937
+ effect(() => {
938
+ this.layer?.setOpacity(this.opacity());
939
+ });
940
+ effect(() => {
941
+ this.layer?.setVisible(this.visible());
942
+ });
943
+ effect(() => {
944
+ this.layer?.setZIndex(this.zIndex());
945
+ });
946
+ effect(() => {
947
+ const vars = this.variables();
948
+ if (vars && this.layer) {
949
+ this.layer.updateStyleVariables(vars);
950
+ }
951
+ });
952
+ // CRITICAL: WebGL layers must be manually disposed
953
+ this.destroyRef.onDestroy(() => {
954
+ const map = this.mapService.getMap();
955
+ if (map && this.layer) {
956
+ map.removeLayer(this.layer);
957
+ try {
958
+ this.layer.dispose();
959
+ }
960
+ catch {
961
+ // Ignore WebGL layer disposal errors (e.g., reading 'deleteBuffer' if renderer not fully initialized)
962
+ }
963
+ }
964
+ });
965
+ }
966
+ syncFeatures(features) {
967
+ this.vectorSource.clear();
968
+ if (!features?.length)
969
+ return;
970
+ const olFeatures = features.map((feature) => {
971
+ const geom = feature.geometry;
972
+ let geometry;
973
+ if (!geom.coordinates) {
974
+ geometry = new Point([0, 0]);
975
+ }
976
+ else if (geom.type === 'Point') {
977
+ geometry = new Point(fromLonLat(geom.coordinates));
978
+ }
979
+ else if (geom.type === 'LineString') {
980
+ geometry = new LineString(geom.coordinates.map((c) => fromLonLat(c)));
981
+ }
982
+ else if (geom.type === 'Polygon') {
983
+ geometry = new Polygon(geom.coordinates.map((ring) => ring.map((c) => fromLonLat(c))));
984
+ }
985
+ else if (geom.type === 'Circle') {
986
+ const center = fromLonLat(geom.coordinates);
987
+ geometry = new Circle(center, geom.radius ?? 1000);
988
+ }
989
+ else {
990
+ geometry = new Point([0, 0]);
991
+ }
992
+ const olFeature = new OLFeature({
993
+ geometry,
994
+ ...feature.properties,
995
+ });
996
+ olFeature.setId(feature.id);
997
+ return olFeature;
998
+ });
999
+ this.vectorSource.addFeatures(olFeatures);
1000
+ }
1001
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlWebGLVectorLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1002
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.13", type: OlWebGLVectorLayerComponent, isStandalone: true, selector: "ol-webgl-vector-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: false, transformFunction: null }, flatStyle: { classPropertyName: "flatStyle", publicName: "flatStyle", isSignal: true, isRequired: true, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, disableHitDetection: { classPropertyName: "disableHitDetection", publicName: "disableHitDetection", isSignal: true, isRequired: false, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1003
+ }
1004
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlWebGLVectorLayerComponent, decorators: [{
1005
+ type: Component,
1006
+ args: [{
1007
+ selector: 'ol-webgl-vector-layer',
1008
+ template: '',
1009
+ changeDetection: ChangeDetectionStrategy.OnPush,
1010
+ }]
1011
+ }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: false }] }], flatStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "flatStyle", required: true }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], opacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "opacity", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], disableHitDetection: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableHitDetection", required: false }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: false }] }] } });
1012
+
1013
+ // OlWebGLTileLayerComponent
1014
+ // GPU-accelerated tile layer with style expressions for color manipulation.
1015
+ /**
1016
+ * GPU-accelerated tile layer with color/brightness/contrast expressions.
1017
+ *
1018
+ * Supports the same tile sources as `ol-tile-layer` but renders via WebGL,
1019
+ * enabling GPU-powered color manipulation (brightness, contrast, saturation, gamma).
1020
+ *
1021
+ * @usageNotes
1022
+ * ```html
1023
+ * <ol-webgl-tile-layer
1024
+ * id="satellite-webgl"
1025
+ * source="xyz"
1026
+ * [url]="'https://server.arcgisonline.com/.../{z}/{y}/{x}'"
1027
+ * [tileStyle]="{ brightness: 0.1, contrast: 0.2 }">
1028
+ * </ol-webgl-tile-layer>
1029
+ * ```
1030
+ */
1031
+ class OlWebGLTileLayerComponent {
1032
+ mapService = inject(OlMapService);
1033
+ destroyRef = inject(DestroyRef);
1034
+ /** Unique layer identifier */
1035
+ id = input.required(...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
1036
+ /** Tile source type */
1037
+ source = input.required(...(ngDevMode ? [{ debugName: "source" }] : /* istanbul ignore next */ []));
1038
+ /** Tile URL template (required for 'xyz' and 'mvt') */
1039
+ url = input(...(ngDevMode ? [undefined, { debugName: "url" }] : /* istanbul ignore next */ []));
1040
+ /** Attribution text */
1041
+ attributions = input(...(ngDevMode ? [undefined, { debugName: "attributions" }] : /* istanbul ignore next */ []));
1042
+ /** WebGL tile style (raster expressions) or flat style (MVT) */
1043
+ tileStyle = input(...(ngDevMode ? [undefined, { debugName: "tileStyle" }] : /* istanbul ignore next */ []));
1044
+ /** Z-index for layer ordering */
1045
+ zIndex = input(0, ...(ngDevMode ? [{ debugName: "zIndex" }] : /* istanbul ignore next */ []));
1046
+ /** Opacity (0–1) */
1047
+ opacity = input(1, ...(ngDevMode ? [{ debugName: "opacity" }] : /* istanbul ignore next */ []));
1048
+ /** Layer visibility */
1049
+ visible = input(true, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
1050
+ /** Preload low-res tiles up to this many zoom levels */
1051
+ preload = input(0, ...(ngDevMode ? [{ debugName: "preload" }] : /* istanbul ignore next */ []));
1052
+ layer = null;
1053
+ constructor() {
1054
+ afterNextRender(() => {
1055
+ const map = this.mapService.getMap();
1056
+ if (!map)
1057
+ return;
1058
+ let tileSource;
1059
+ switch (this.source()) {
1060
+ case 'mvt':
1061
+ tileSource = new VectorTileSource({
1062
+ format: new MVT(),
1063
+ url: this.url(),
1064
+ attributions: this.attributions(),
1065
+ });
1066
+ this.layer = new WebGLVectorTileLayer({
1067
+ source: tileSource,
1068
+ visible: this.visible(),
1069
+ opacity: this.opacity(),
1070
+ zIndex: this.zIndex(),
1071
+ style: this.tileStyle() || {},
1072
+ });
1073
+ break;
1074
+ case 'xyz':
1075
+ tileSource = new XYZ({
1076
+ url: this.url(),
1077
+ attributions: this.attributions(),
1078
+ });
1079
+ this.layer = new WebGLTileLayer({
1080
+ source: tileSource,
1081
+ visible: this.visible(),
1082
+ opacity: this.opacity(),
1083
+ zIndex: this.zIndex(),
1084
+ preload: this.preload(),
1085
+ ...(this.tileStyle() ? { style: this.tileStyle() } : {}),
1086
+ });
1087
+ break;
1088
+ case 'osm':
1089
+ default:
1090
+ tileSource = new OSM({
1091
+ attributions: this.attributions(),
1092
+ });
1093
+ this.layer = new WebGLTileLayer({
1094
+ source: tileSource,
1095
+ visible: this.visible(),
1096
+ opacity: this.opacity(),
1097
+ zIndex: this.zIndex(),
1098
+ preload: this.preload(),
1099
+ ...(this.tileStyle() ? { style: this.tileStyle() } : {}),
1100
+ });
1101
+ break;
1102
+ }
1103
+ if (this.layer) {
1104
+ this.layer.set('id', this.id());
1105
+ map.addLayer(this.layer);
1106
+ }
1107
+ });
1108
+ effect(() => {
1109
+ this.layer?.setOpacity(this.opacity());
1110
+ });
1111
+ effect(() => {
1112
+ this.layer?.setVisible(this.visible());
1113
+ });
1114
+ effect(() => {
1115
+ this.layer?.setZIndex(this.zIndex());
1116
+ });
1117
+ effect(() => {
1118
+ const style = this.tileStyle();
1119
+ if (style && this.layer) {
1120
+ if (this.source() === 'mvt') {
1121
+ this.layer.setStyle(style);
1122
+ }
1123
+ else {
1124
+ this.layer.setStyle(style);
1125
+ }
1126
+ }
1127
+ });
1128
+ // WebGL tile layers also need manual dispose
1129
+ this.destroyRef.onDestroy(() => {
1130
+ const map = this.mapService.getMap();
1131
+ if (map && this.layer) {
1132
+ map.removeLayer(this.layer);
1133
+ this.layer.dispose();
1134
+ }
1135
+ });
1136
+ }
1137
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlWebGLTileLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1138
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.13", type: OlWebGLTileLayerComponent, isStandalone: true, selector: "ol-webgl-tile-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: true, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: false, transformFunction: null }, attributions: { classPropertyName: "attributions", publicName: "attributions", isSignal: true, isRequired: false, transformFunction: null }, tileStyle: { classPropertyName: "tileStyle", publicName: "tileStyle", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, preload: { classPropertyName: "preload", publicName: "preload", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1139
+ }
1140
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: OlWebGLTileLayerComponent, decorators: [{
1141
+ type: Component,
1142
+ args: [{
1143
+ selector: 'ol-webgl-tile-layer',
1144
+ template: '',
1145
+ changeDetection: ChangeDetectionStrategy.OnPush,
1146
+ }]
1147
+ }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: true }] }], url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: false }] }], attributions: [{ type: i0.Input, args: [{ isSignal: true, alias: "attributions", required: false }] }], tileStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "tileStyle", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], opacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "opacity", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], preload: [{ type: i0.Input, args: [{ isSignal: true, alias: "preload", required: false }] }] } });
1148
+
470
1149
  // Provider functions
471
1150
  function withLayers() {
472
1151
  return { kind: 'layers', providers: [OlLayerService] };
@@ -481,4 +1160,4 @@ function provideLayers() {
481
1160
  * Generated bundle index. Do not edit.
482
1161
  */
483
1162
 
484
- export { OlImageLayerComponent, OlLayerService, OlTileLayerComponent, OlVectorLayerComponent, provideLayers, withLayers };
1163
+ export { OlClusterComponent, OlHeatmapLayerComponent, OlImageLayerComponent, OlLayerService, OlTileLayerComponent, OlVectorLayerComponent, OlWebGLTileLayerComponent, OlWebGLVectorLayerComponent, provideLayers, withLayers };