@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.
@@ -1,6 +1,7 @@
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';
@@ -16,7 +17,15 @@ import XYZ from 'ol/source/XYZ';
16
17
  import TileWMS from 'ol/source/TileWMS';
17
18
  import ImageWMS from 'ol/source/ImageWMS';
18
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';
19
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';
20
29
 
21
30
  // OlLayerService
22
31
  /**
@@ -58,6 +67,8 @@ class OlLayerService {
58
67
  switch (config.type) {
59
68
  case 'vector':
60
69
  return this.createVectorLayer(config, map);
70
+ case 'heatmap':
71
+ return this.createHeatmapLayer(config, map);
61
72
  case 'tile':
62
73
  return this.createTileLayer(config, map);
63
74
  case 'image':
@@ -93,6 +104,12 @@ class OlLayerService {
93
104
  layer.setVisible(visible);
94
105
  this.updateLayerState();
95
106
  }
107
+ else {
108
+ const pending = this.pendingConfigs.find((c) => c.id === id);
109
+ if (pending) {
110
+ pending.visible = visible;
111
+ }
112
+ }
96
113
  }
97
114
  toggleVisibility(id) {
98
115
  const layer = this.layerCache.get(id);
@@ -110,6 +127,12 @@ class OlLayerService {
110
127
  layer.setOpacity(opacity);
111
128
  this.updateLayerState();
112
129
  }
130
+ else {
131
+ const pending = this.pendingConfigs.find((c) => c.id === id);
132
+ if (pending) {
133
+ pending.opacity = opacity;
134
+ }
135
+ }
113
136
  }
114
137
  setZIndex(id, zIndex) {
115
138
  const layer = this.layerCache.get(id);
@@ -117,6 +140,35 @@ class OlLayerService {
117
140
  layer.setZIndex(zIndex);
118
141
  this.updateLayerState();
119
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
+ }
120
172
  }
121
173
  isVisible(id) {
122
174
  return this.layerCache.get(id)?.getVisible() ?? false;
@@ -166,19 +218,27 @@ class OlLayerService {
166
218
  : source;
167
219
  if (!(vectorSource instanceof VectorSource))
168
220
  return;
169
- // Get existing feature IDs from source
170
- const existingIds = new Set(vectorSource
171
- .getFeatures()
172
- .map((f) => f.getId())
173
- .filter((id) => id !== undefined));
174
- // Only add features that don't already exist in the source
175
- if (features && features.length > 0) {
176
- const newFeatures = features.filter((f) => !existingIds.has(f.id));
177
- if (newFeatures.length > 0) {
178
- const olFeatures = newFeatures.map((feature) => {
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) => {
179
240
  const geom = feature.geometry;
180
241
  let geometry;
181
- // Validate coordinates exist before processing
182
242
  if (!geom.coordinates) {
183
243
  geometry = new Point([0, 0]);
184
244
  }
@@ -196,7 +256,6 @@ class OlLayerService {
196
256
  }
197
257
  else if (geom.type === 'Circle') {
198
258
  const center = fromLonLat(geom.coordinates);
199
- // Approximate radius in meters - use 1000m as default if not specified
200
259
  geometry = new Circle(center, geom.radius ?? 1000);
201
260
  }
202
261
  else {
@@ -219,7 +278,13 @@ class OlLayerService {
219
278
  updateLayerState() {
220
279
  const layers = [];
221
280
  this.layerCache.forEach((layer, id) => {
222
- 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';
223
288
  layers.push({
224
289
  id,
225
290
  type: type,
@@ -231,7 +296,17 @@ class OlLayerService {
231
296
  this.layerState.set(layers.sort((a, b) => a.zIndex - b.zIndex));
232
297
  }
233
298
  createVectorLayer(config, map) {
234
- const vectorSource = 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);
235
310
  // Add features if provided
236
311
  if (config.features && config.features.length > 0) {
237
312
  const olFeatures = config.features.map((feature) => {
@@ -305,58 +380,10 @@ class OlLayerService {
305
380
  stroke: new Stroke({ color: '#d32f2f', width: 2 }),
306
381
  }),
307
382
  });
308
- // Cluster style: shows count badge when features are clustered
309
- const clusterStyleFn = (olFeature) => {
310
- const features = olFeature.get('features');
311
- const size = features?.length ?? 1;
312
- if (size > 1) {
313
- const showCount = clusterCfg?.showCount ?? true;
314
- return new Style({
315
- image: new Circle$1({
316
- radius: 15 + Math.min(size * 2, 15),
317
- fill: new Fill({ color: 'rgba(255, 100, 100, 0.8)' }),
318
- stroke: new Stroke({ color: '#fff', width: 2 }),
319
- }),
320
- text: showCount
321
- ? new Text({
322
- text: String(size),
323
- fill: new Fill({ color: '#fff' }),
324
- })
325
- : undefined,
326
- });
327
- }
328
- // Single feature: get the original feature from the cluster and use its style
329
- const originalFeatures = olFeature.get('features');
330
- const originalFeature = originalFeatures?.[0];
331
- if (originalFeature) {
332
- const abstractStyle = originalFeature.get(STYLE_PROP);
333
- if (abstractStyle) {
334
- const style = new Style();
335
- const { icon, fill, stroke } = abstractStyle;
336
- if (icon?.src) {
337
- style.setImage(new Icon({
338
- src: icon.src,
339
- ...(icon.size ? { size: icon.size } : {}),
340
- ...(icon.anchor ? { anchor: icon.anchor } : {}),
341
- }));
342
- }
343
- if (fill) {
344
- style.setFill(new Fill({ color: fill.color }));
345
- }
346
- if (stroke) {
347
- style.setStroke(new Stroke({ color: stroke.color, width: stroke.width }));
348
- }
349
- // If we mapped at least one property, return it, otherwise fallback
350
- if (icon?.src || fill || stroke)
351
- return style;
352
- }
353
- }
354
- return defaultStyle;
355
- };
356
- // Per-feature style resolver: features carrying `style` render it.
357
- // Structural type avoids importing `FeatureLike` from `ol/Feature`;
358
- // tooling has been observed to auto-remove the unused-looking import.
359
- const styleFn = (olFeature) => {
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)
360
387
  const abstractStyle = olFeature.get(STYLE_PROP);
361
388
  if (abstractStyle) {
362
389
  const style = new Style();
@@ -377,8 +404,59 @@ class OlLayerService {
377
404
  if (icon?.src || fill || stroke)
378
405
  return style;
379
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
+ }
380
424
  return defaultStyle;
381
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
+ };
382
460
  const layer = new VectorLayer({
383
461
  source,
384
462
  visible: config.visible ?? true,
@@ -392,6 +470,56 @@ class OlLayerService {
392
470
  this.updateLayerState();
393
471
  return { id: config.id };
394
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
+ }),
516
+ });
517
+ layer.set('id', config.id);
518
+ map.addLayer(layer);
519
+ this.layerCache.set(config.id, layer);
520
+ this.updateLayerState();
521
+ return { id: config.id };
522
+ }
395
523
  createTileLayer(config, map) {
396
524
  let source;
397
525
  switch (config.source.type) {
@@ -404,7 +532,7 @@ class OlLayerService {
404
532
  case 'wms':
405
533
  source = new TileWMS({
406
534
  url: config.source.url,
407
- params: config.source.params,
535
+ params: config.source.params ?? {},
408
536
  attributions: config.source.attributions,
409
537
  });
410
538
  break;
@@ -446,36 +574,69 @@ class OlLayerService {
446
574
  this.updateLayerState();
447
575
  return { id: config.id };
448
576
  }
449
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlLayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
450
- 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 });
451
579
  }
452
- 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: [{
453
581
  type: Injectable
454
582
  }] });
455
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
+
456
601
  // OlVectorLayerComponent
457
602
  class OlVectorLayerComponent {
458
603
  layerService = inject(OlLayerService);
459
604
  destroyRef = inject(DestroyRef);
460
605
  id = input.required(...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
461
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 */ []));
462
609
  zIndex = input(0, ...(ngDevMode ? [{ debugName: "zIndex" }] : /* istanbul ignore next */ []));
463
610
  opacity = input(1, ...(ngDevMode ? [{ debugName: "opacity" }] : /* istanbul ignore next */ []));
464
611
  visible = input(true, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
465
612
  style = input(...(ngDevMode ? [undefined, { debugName: "style" }] : /* istanbul ignore next */ []));
466
613
  cluster = input(...(ngDevMode ? [undefined, { debugName: "cluster" }] : /* istanbul ignore next */ []));
614
+ clusterComponent = contentChild(OlClusterComponent, ...(ngDevMode ? [{ debugName: "clusterComponent" }] : /* istanbul ignore next */ []));
467
615
  constructor() {
468
616
  // Initialize layer after DOM is ready
469
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);
470
629
  this.layerService.addLayer({
471
630
  id: this.id(),
472
631
  type: 'vector',
473
632
  features: this.features(),
633
+ url: this.url(),
634
+ format: this.format(),
474
635
  zIndex: this.zIndex(),
475
636
  opacity: this.opacity(),
476
637
  visible: this.visible(),
477
638
  style: this.style(),
478
- cluster: this.cluster(),
639
+ cluster: resolvedClusterConfig,
479
640
  });
480
641
  });
481
642
  // Effect to sync features when input changes
@@ -486,22 +647,31 @@ class OlVectorLayerComponent {
486
647
  this.layerService.updateFeatures(this.id(), currentFeatures);
487
648
  }
488
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
+ });
489
659
  // Cleanup when component is destroyed
490
660
  this.destroyRef.onDestroy(() => {
491
661
  this.layerService.removeLayer(this.id());
492
662
  });
493
663
  }
494
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlVectorLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
495
- 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 }, cluster: { classPropertyName: "cluster", publicName: "cluster", 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 });
496
666
  }
497
- 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: [{
498
668
  type: Component,
499
669
  args: [{
500
670
  selector: 'ol-vector-layer',
501
671
  template: '',
502
672
  changeDetection: ChangeDetectionStrategy.OnPush,
503
673
  }]
504
- }], 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 }] }], cluster: [{ type: i0.Input, args: [{ isSignal: true, alias: "cluster", 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 }] }] } });
505
675
 
506
676
  // OlTileLayerComponent
507
677
  class OlTileLayerComponent {
@@ -532,15 +702,24 @@ class OlTileLayerComponent {
532
702
  visible: this.visible(),
533
703
  });
534
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
+ });
535
714
  // Cleanup when component is destroyed
536
715
  this.destroyRef.onDestroy(() => {
537
716
  this.layerService.removeLayer(this.id());
538
717
  });
539
718
  }
540
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlTileLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
541
- 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 });
542
721
  }
543
- 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: [{
544
723
  type: Component,
545
724
  args: [{
546
725
  selector: 'ol-tile-layer',
@@ -583,10 +762,10 @@ class OlImageLayerComponent {
583
762
  this.layerService.removeLayer(this.id());
584
763
  });
585
764
  }
586
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: OlImageLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
587
- 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 });
588
767
  }
589
- 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: [{
590
769
  type: Component,
591
770
  args: [{
592
771
  selector: 'ol-image-layer',
@@ -595,6 +774,378 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
595
774
  }]
596
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 }] }] } });
597
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
+
598
1149
  // Provider functions
599
1150
  function withLayers() {
600
1151
  return { kind: 'layers', providers: [OlLayerService] };
@@ -609,4 +1160,4 @@ function provideLayers() {
609
1160
  * Generated bundle index. Do not edit.
610
1161
  */
611
1162
 
612
- export { OlImageLayerComponent, OlLayerService, OlTileLayerComponent, OlVectorLayerComponent, provideLayers, withLayers };
1163
+ export { OlClusterComponent, OlHeatmapLayerComponent, OlImageLayerComponent, OlLayerService, OlTileLayerComponent, OlVectorLayerComponent, OlWebGLTileLayerComponent, OlWebGLVectorLayerComponent, provideLayers, withLayers };