@ndwnu/map 0.0.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # map
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Running unit tests
6
+
7
+ Run `nx test map` to execute the unit tests.
@@ -0,0 +1,503 @@
1
+ import { BehaviorSubject, map, Subject, takeUntil } from 'rxjs';
2
+ import * as i0 from '@angular/core';
3
+ import { viewChild, Component, ChangeDetectionStrategy, Injectable } from '@angular/core';
4
+ import { Map } from 'maplibre-gl';
5
+
6
+ const BOUNDS_NL = [
7
+ [3.079667, 50.587611],
8
+ [7.572028, 53.636667],
9
+ ];
10
+ const BOUNDS_AMERSFOORT = [
11
+ [5.34458238242172, 52.11623605695118],
12
+ [5.446205917577942, 52.21132028216525],
13
+ ];
14
+
15
+ /**
16
+ * Repository for managing map elements.
17
+ * Provides methods to add, remove, show, hide, and toggle map elements.
18
+ * Also tracks element visibility state and provides observable streams for elements.
19
+ *
20
+ * @typeparam TElementType - The type of ID used for the map elements
21
+ */
22
+ class MapElementRepository {
23
+ /** Subject that holds the current array of map elements */
24
+ mapElementsSubject = new BehaviorSubject([]);
25
+ // mapElements are not directly exposed to prevent circular reference errors when
26
+ // they are used in a template. Because the mapElements$ observable contains MapLibre
27
+ // GL JS map objects, which have circular references.
28
+ /** Observable stream of all map elements */
29
+ mapElements$ = this.mapElementsSubject.asObservable();
30
+ /** Observable stream of only visible map elements */
31
+ visibleMapElements$ = this.mapElements$.pipe(map((elements) => elements.filter((element) => element.isVisible)));
32
+ /**
33
+ * Gets the ids (TElementType) as an array of all map element
34
+ * @returns Array of TElementType
35
+ */
36
+ mapElementIds$ = this.mapElements$.pipe(map((elements) => elements.map((element) => element.id)));
37
+ /**
38
+ * Gets the ids (TElementType) as an array of all visible map element
39
+ * @returns Array of TElementType
40
+ */
41
+ visibleMapElementIds$ = this.visibleMapElements$.pipe(map((elements) => elements.map((element) => element.id)));
42
+ /**
43
+ * Gets the current array of all map elements
44
+ * @returns Array of map elements
45
+ */
46
+ get mapElements() {
47
+ return this.mapElementsSubject.getValue();
48
+ }
49
+ /**
50
+ * Gets only the currently visible map elements
51
+ * @returns Array of visible map elements
52
+ */
53
+ get visibleMapElements() {
54
+ return this.mapElements.filter((element) => element.isVisible);
55
+ }
56
+ /**
57
+ * Finds a map element by its ID
58
+ *
59
+ * @param elementId - The ID of the element to find
60
+ * @returns The map element if found, undefined otherwise
61
+ */
62
+ getMapElementById(elementId) {
63
+ return this.mapElements.find((element) => element.id === elementId);
64
+ }
65
+ /**
66
+ * Checks if a map element is currently visible
67
+ *
68
+ * @param elementId - The ID of the element to check
69
+ * @returns True if the element is visible, false otherwise
70
+ */
71
+ isMapElementVisible(elementId) {
72
+ return this.visibleMapElements.some((element) => element.id === elementId);
73
+ }
74
+ /**
75
+ * Adds a new map element to the repository and initializes it.
76
+ * Sets the element's visibility based on its configuration.
77
+ *
78
+ * @param mapElement - The map element to add
79
+ */
80
+ addMapElement(mapElement) {
81
+ const currentElements = this.mapElements;
82
+ this.mapElementsSubject.next([...currentElements, mapElement]);
83
+ mapElement.onInit();
84
+ if (mapElement.alwaysVisible || this.isMapElementVisible(mapElement.id)) {
85
+ this.showMapElement(mapElement.id);
86
+ }
87
+ else {
88
+ this.hideMapElement(mapElement.id);
89
+ }
90
+ }
91
+ /**
92
+ * Removes a map element from the repository and destroys it.
93
+ *
94
+ * @param mapElement - The map element to remove
95
+ */
96
+ removeMapElement(mapElement) {
97
+ const filteredElements = this.mapElements.filter((element) => element.id !== mapElement.id);
98
+ this.mapElementsSubject.next(filteredElements);
99
+ mapElement.onDestroy();
100
+ }
101
+ /**
102
+ * Removes all map elements from the repository and destroys them.
103
+ */
104
+ removeAllMapElements() {
105
+ this.mapElements.forEach((element) => {
106
+ this.removeMapElement(element);
107
+ });
108
+ }
109
+ /**
110
+ * Sets the visibility of a map element by its ID and updates the subject with the new state.
111
+ *
112
+ * @param elementId - The ID of the element to update
113
+ * @param visible - Whether the element should be visible or hidden
114
+ */
115
+ setMapElementVisibility(elementId, visible) {
116
+ const element = this.getMapElementById(elementId);
117
+ if (element) {
118
+ element.setVisible(visible);
119
+ const elements = this.mapElements.filter((el) => el.id !== elementId);
120
+ this.mapElementsSubject.next([...elements, element]);
121
+ }
122
+ }
123
+ /**
124
+ * Shows a map element by its ID and updates the subject with the new state.
125
+ *
126
+ * @param elementId - The ID of the element to show
127
+ */
128
+ showMapElement(elementId) {
129
+ this.setMapElementVisibility(elementId, true);
130
+ }
131
+ /**
132
+ * Hides a map element by its ID and updates the subject with the new state.
133
+ *
134
+ * @param elementId - The ID of the element to hide
135
+ */
136
+ hideMapElement(elementId) {
137
+ this.setMapElementVisibility(elementId, false);
138
+ }
139
+ /**
140
+ * Toggles the visibility of a map element by its ID.
141
+ * If the element is currently visible, it will be hidden, and vice versa.
142
+ *
143
+ * @param elementId - The ID of the element to toggle visibility
144
+ */
145
+ toggleMapElement(elementId) {
146
+ if (this.visibleMapElements.some((element) => element.id === elementId)) {
147
+ this.hideMapElement(elementId);
148
+ }
149
+ else {
150
+ this.showMapElement(elementId);
151
+ }
152
+ }
153
+ /**
154
+ * Gets the ID of the layer that should come before the specified element's layers
155
+ * in the map's layer stack. This is used for maintaining proper layer ordering.
156
+ *
157
+ * @param elementId - The ID of the element to find a "before" reference for
158
+ * @returns The ID of the first layer of the next element in order, or undefined if none exists
159
+ */
160
+ getBeforeId(elementId) {
161
+ const currentElement = this.getMapElementById(elementId);
162
+ if (!currentElement) {
163
+ return undefined;
164
+ }
165
+ const nextElement = this.mapElements
166
+ .filter((item) => item.elementOrder > currentElement.elementOrder)
167
+ .sort((a, b) => a.elementOrder - b.elementOrder)[0];
168
+ const nextElementLayerIds = nextElement?.sources
169
+ .flatMap((source) => source.layers)
170
+ .filter((layer) => layer.initialized)
171
+ .map((layer) => layer.id);
172
+ return nextElement && nextElementLayerIds.length > 0 ? nextElementLayerIds[0] : undefined;
173
+ }
174
+ }
175
+
176
+ class MapElement {
177
+ config;
178
+ id;
179
+ elementOrder;
180
+ sources = [];
181
+ alwaysVisible = false;
182
+ isVisible = false;
183
+ unsubscribe = new Subject();
184
+ constructor(config) {
185
+ this.config = config;
186
+ this.id = config.elementId;
187
+ this.elementOrder = config.elementOrder;
188
+ }
189
+ onInit() {
190
+ this.sources.forEach((source) => source.onInit());
191
+ }
192
+ onDestroy() {
193
+ this.sources.forEach((source) => source.onDestroy());
194
+ this.unsubscribe.next();
195
+ this.unsubscribe.complete();
196
+ }
197
+ setVisible(visible) {
198
+ this.isVisible = visible;
199
+ this.sources.forEach((source) => source.setVisible(visible));
200
+ }
201
+ }
202
+
203
+ class MapLayer {
204
+ config;
205
+ sourceId;
206
+ layerIdSuffix;
207
+ initialized = false;
208
+ unsubscribe = new Subject();
209
+ constructor(config, sourceId, layerIdSuffix) {
210
+ this.config = config;
211
+ this.sourceId = sourceId;
212
+ this.layerIdSuffix = layerIdSuffix;
213
+ }
214
+ get id() {
215
+ const suffix = this.layerIdSuffix ? `-${this.layerIdSuffix}` : '';
216
+ return `${this.sourceId}${suffix}`;
217
+ }
218
+ get styleLayer() {
219
+ return this.config.map.getLayer(this.id);
220
+ }
221
+ onInit() {
222
+ // Add the layer to the map, with the correct ordering (beforeId)
223
+ const beforeId = this.config.mapElementRepository.getBeforeId(this.config.elementId);
224
+ this.config.map.addLayer(this.getSpecification(), beforeId);
225
+ // Keeping track of which layers have been added to the map to allow for beforeId determination
226
+ this.#setupClickHandlers();
227
+ this.initialized = true;
228
+ }
229
+ onDestroy() {
230
+ this.initialized = false;
231
+ this.#removeClickHandlers();
232
+ this.config.map.removeLayer(this.id);
233
+ this.unsubscribe.next();
234
+ this.unsubscribe.complete();
235
+ }
236
+ setVisible(visible) {
237
+ this.config.map.setLayoutProperty(this.id, 'visibility', visible ? 'visible' : 'none');
238
+ }
239
+ #setupClickHandlers() {
240
+ if (!this.onClick)
241
+ return;
242
+ this.config.map.on('click', this.id, (event) => {
243
+ this.onClick?.(this.#getFeatures(event));
244
+ });
245
+ this.config.maplibreCursorService.setMouseCursor(this.config.map, this.id);
246
+ }
247
+ #removeClickHandlers() {
248
+ if (!this.onClick)
249
+ return;
250
+ this.config.map.off('click', this.id, (event) => {
251
+ this.onClick?.(this.#getFeatures(event));
252
+ });
253
+ }
254
+ #getFeatures(event) {
255
+ if (event.features && event.features.length > 0) {
256
+ return this.#distinctFeatures(event.features);
257
+ }
258
+ return [];
259
+ }
260
+ /**
261
+ * Filters an array of features to remove duplicates based on their properties.
262
+ * Two features are considered duplicates if they have identical properties.
263
+ * Particularly vector sources can yield duplicate features due to the way they
264
+ * are rendered in tiles.
265
+ *
266
+ * @param features - Array of GeoJSON features to filter
267
+ * @returns Array of unique features with no property duplicates
268
+ * @private
269
+ */
270
+ #distinctFeatures(features) {
271
+ const seen = new Set();
272
+ const uniqueFeatures = [];
273
+ features.forEach((feature) => {
274
+ const propertiesString = JSON.stringify(feature.properties);
275
+ if (!seen.has(propertiesString)) {
276
+ seen.add(propertiesString);
277
+ uniqueFeatures.push(feature);
278
+ }
279
+ });
280
+ return uniqueFeatures;
281
+ }
282
+ }
283
+
284
+ class MapSource {
285
+ id;
286
+ config;
287
+ layers = [];
288
+ unsubscribe = new Subject();
289
+ #isInitialized = false;
290
+ #featureCollection$;
291
+ constructor(id, config) {
292
+ this.id = id;
293
+ this.config = config;
294
+ }
295
+ onInit() {
296
+ if (!this.config.map.getSource(this.id)) {
297
+ this.config.map.addSource(this.id, this.getSpecification());
298
+ }
299
+ this.layers.forEach((layer) => layer.onInit());
300
+ this.isInitialized = true;
301
+ }
302
+ onDestroy() {
303
+ this.isInitialized = false;
304
+ this.layers.forEach((layer) => layer.onDestroy());
305
+ this.unsubscribe.next();
306
+ this.unsubscribe.complete();
307
+ }
308
+ get featureCollection$() {
309
+ return this.#featureCollection$;
310
+ }
311
+ set featureCollection$(value) {
312
+ this.#featureCollection$ = value;
313
+ this.#subscribeToFeatureCollection();
314
+ }
315
+ get isInitialized() {
316
+ return this.#isInitialized;
317
+ }
318
+ set isInitialized(value) {
319
+ this.#isInitialized = value;
320
+ this.#subscribeToFeatureCollection();
321
+ }
322
+ setVisible(visible) {
323
+ this.layers.forEach((layer) => layer.setVisible(visible));
324
+ }
325
+ #subscribeToFeatureCollection() {
326
+ if (this.isInitialized && this.featureCollection$) {
327
+ this.featureCollection$.pipe(takeUntil(this.unsubscribe)).subscribe({
328
+ next: (featureCollection) => {
329
+ const source = this.config.map.getSource(this.id);
330
+ if (source && source.type === 'geojson') {
331
+ source.setData(featureCollection);
332
+ }
333
+ else {
334
+ // not needed because source is always specified as geojson with empty collection
335
+ this.config.map.addSource(this.id, {
336
+ type: 'geojson',
337
+ data: featureCollection,
338
+ });
339
+ }
340
+ },
341
+ error: (error) => {
342
+ console.error('Error updating feature collection', error);
343
+ },
344
+ });
345
+ }
346
+ }
347
+ }
348
+
349
+ class MapComponent {
350
+ mapContainer = viewChild.required('mapContainer');
351
+ map;
352
+ ngAfterViewInit() {
353
+ this.map = this.#createMap(this.mapContainer().nativeElement);
354
+ this.map.once('load', () => this.#initiateMapLoading());
355
+ }
356
+ ngOnDestroy() {
357
+ // Note: Using `this.map.once('remove', this.onRemoveMap())` does not work correctly because,
358
+ // by the time the `onRemoveMap` event is triggered, it is no longer possible to interact with the map for cleanup.
359
+ // To address this we call `onRemoveMap` explicitly before destroying the MapLibre instance. This
360
+ // ensures that our map elements are cleaned up properly before the map is removed.
361
+ this.onRemoveMap();
362
+ this.map?.remove();
363
+ }
364
+ resizeMap() {
365
+ this.map?.resize();
366
+ }
367
+ zoomToLevel(zoomLevel, options) {
368
+ this.map?.zoomTo(zoomLevel, options);
369
+ }
370
+ #createMap(container) {
371
+ const map = new Map({
372
+ container,
373
+ style: {
374
+ version: 8,
375
+ sources: {},
376
+ layers: [],
377
+ },
378
+ interactive: true,
379
+ maxZoom: 18,
380
+ minZoom: 6,
381
+ bounds: [
382
+ [5.34458238242172, 52.11623605695118],
383
+ [5.446205917577942, 52.21132028216525],
384
+ ],
385
+ });
386
+ // Prevent map rotation
387
+ map.dragRotate.disable();
388
+ return map;
389
+ }
390
+ #initiateMapLoading() {
391
+ this.onLoadMap();
392
+ this.resizeMap();
393
+ }
394
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: MapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
395
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.0.7", type: MapComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "mapContainer", first: true, predicate: ["mapContainer"], descendants: true, isSignal: true }], ngImport: i0, template: '<div #mapContainer class="map"></div>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
396
+ }
397
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: MapComponent, decorators: [{
398
+ type: Component,
399
+ args: [{
400
+ standalone: true,
401
+ template: '<div #mapContainer class="map"></div>',
402
+ changeDetection: ChangeDetectionStrategy.OnPush,
403
+ }]
404
+ }] });
405
+
406
+ /**
407
+ * Service for managing MapLibre map cursor interactions.
408
+ *
409
+ * Maplibre has no automatic cursor handling for layers, so when you add a clickHandler
410
+ * to a layer, the user can click on it, but the cursor will not change to a pointer.
411
+ * This service provides methods to set the cursor to pointer when hovering over a layer,
412
+ * and to enable crosshair mode which takes precedence over all other cursor types.
413
+ */
414
+ class MaplibreCursorService {
415
+ /** Counter to track how many layers currently need a pointer cursor */
416
+ #cursorPointerCount = 0;
417
+ /** Flag indicating if crosshair mode is active */
418
+ #crosshairMode = false;
419
+ /**
420
+ * Sets up mouse cursor handling for a map layer.
421
+ * Changes cursor to pointer when mouse enters the layer and resets when leaving.
422
+ *
423
+ * @param map - The MapLibre map instance
424
+ * @param layerId - ID of the layer to apply cursor behavior to
425
+ */
426
+ setMouseCursor(map, layerId) {
427
+ const updateCursor = (cursor) => {
428
+ if (!this.#crosshairMode)
429
+ this.#setMouseCursor(map, cursor);
430
+ };
431
+ map.on('mouseenter', layerId, () => updateCursor('pointer'));
432
+ map.on('mouseleave', layerId, () => updateCursor(''));
433
+ }
434
+ /**
435
+ * Enables or disables crosshair cursor mode for the map.
436
+ * When enabled, crosshair cursor will take precedence over all other cursor types.
437
+ *
438
+ * @param value - True to enable crosshair mode, false to disable
439
+ * @param map - The MapLibre map instance to apply the cursor to
440
+ */
441
+ /**
442
+ * Enables or disables crosshair cursor mode for the map.
443
+ * When enabled, crosshair cursor will take precedence over all other cursor types.
444
+ *
445
+ * @param value - True to enable crosshair mode, false to disable
446
+ * @param map - The MapLibre map instance to apply the cursor to
447
+ */
448
+ setCrosshairMode(value, map) {
449
+ this.#crosshairMode = value;
450
+ let cursorType = '';
451
+ if (value) {
452
+ cursorType = 'crosshair';
453
+ }
454
+ else if (this.#cursorPointerCount > 0) {
455
+ cursorType = 'pointer';
456
+ }
457
+ map.getCanvas().style.cursor = cursorType;
458
+ }
459
+ /**
460
+ * Internal method to handle mouse cursor state changes.
461
+ * Manages the cursor pointer reference counter to ensure cursor displays correctly
462
+ * when hovering over multiple interactive layers.
463
+ *
464
+ * @param map - The MapLibre map instance
465
+ * @param cursor - Cursor type to set ('pointer' or '' to reset)
466
+ * @private
467
+ */
468
+ #setMouseCursor(map, cursor) {
469
+ if (this.#crosshairMode) {
470
+ // Crosshair mode takes precedence, ignore other cursor settings
471
+ map.getCanvas().style.cursor = 'crosshair';
472
+ return;
473
+ }
474
+ if (cursor === 'pointer') {
475
+ this.#cursorPointerCount++;
476
+ map.getCanvas().style.cursor = cursor;
477
+ }
478
+ else if (cursor === '') {
479
+ this.#cursorPointerCount--;
480
+ if (this.#cursorPointerCount < 1) {
481
+ map.getCanvas().style.cursor = '';
482
+ }
483
+ }
484
+ else {
485
+ console.warn(`Cursor type '${cursor}' is not supported.`);
486
+ }
487
+ }
488
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: MaplibreCursorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
489
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: MaplibreCursorService, providedIn: 'root' });
490
+ }
491
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: MaplibreCursorService, decorators: [{
492
+ type: Injectable,
493
+ args: [{
494
+ providedIn: 'root',
495
+ }]
496
+ }] });
497
+
498
+ /**
499
+ * Generated bundle index. Do not edit.
500
+ */
501
+
502
+ export { BOUNDS_AMERSFOORT, BOUNDS_NL, MapComponent, MapElement, MapElementRepository, MapLayer, MapSource, MaplibreCursorService };
503
+ //# sourceMappingURL=ndwnu-map.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ndwnu-map.mjs","sources":["../../../../libs/map/src/lib/map/map-constants.ts","../../../../libs/map/src/lib/map/map-element.repository.ts","../../../../libs/map/src/lib/map/map-element.ts","../../../../libs/map/src/lib/map/map-layer.ts","../../../../libs/map/src/lib/map/map-source.ts","../../../../libs/map/src/lib/map/map.component.ts","../../../../libs/map/src/lib/map/maplibre-cursor.service.ts","../../../../libs/map/src/ndwnu-map.ts"],"sourcesContent":["import { LngLatLike } from 'maplibre-gl';\n\nexport const BOUNDS_NL: [LngLatLike, LngLatLike] = [\n [3.079667, 50.587611],\n [7.572028, 53.636667],\n];\nexport const BOUNDS_AMERSFOORT: [LngLatLike, LngLatLike] = [\n [5.34458238242172, 52.11623605695118],\n [5.446205917577942, 52.21132028216525],\n];\n","import { BehaviorSubject, map } from 'rxjs';\n\nimport { MapElement } from './map-element';\n\n/**\n * Repository for managing map elements.\n * Provides methods to add, remove, show, hide, and toggle map elements.\n * Also tracks element visibility state and provides observable streams for elements.\n *\n * @typeparam TElementType - The type of ID used for the map elements\n */\nexport abstract class MapElementRepository<TElementType> {\n /** Subject that holds the current array of map elements */\n private readonly mapElementsSubject = new BehaviorSubject<MapElement<TElementType>[]>([]);\n\n // mapElements are not directly exposed to prevent circular reference errors when\n // they are used in a template. Because the mapElements$ observable contains MapLibre\n // GL JS map objects, which have circular references.\n\n /** Observable stream of all map elements */\n private readonly mapElements$ = this.mapElementsSubject.asObservable();\n\n /** Observable stream of only visible map elements */\n private readonly visibleMapElements$ = this.mapElements$.pipe(\n map((elements) => elements.filter((element) => element.isVisible)),\n );\n\n /**\n * Gets the ids (TElementType) as an array of all map element\n * @returns Array of TElementType\n */\n mapElementIds$ = this.mapElements$.pipe(map((elements) => elements.map((element) => element.id)));\n\n /**\n * Gets the ids (TElementType) as an array of all visible map element\n * @returns Array of TElementType\n */\n visibleMapElementIds$ = this.visibleMapElements$.pipe(\n map((elements) => elements.map((element) => element.id)),\n );\n\n /**\n * Gets the current array of all map elements\n * @returns Array of map elements\n */\n get mapElements(): MapElement<TElementType>[] {\n return this.mapElementsSubject.getValue();\n }\n\n /**\n * Gets only the currently visible map elements\n * @returns Array of visible map elements\n */\n get visibleMapElements(): MapElement<TElementType>[] {\n return this.mapElements.filter((element) => element.isVisible);\n }\n\n /**\n * Finds a map element by its ID\n *\n * @param elementId - The ID of the element to find\n * @returns The map element if found, undefined otherwise\n */\n getMapElementById(elementId: TElementType): MapElement<TElementType> | undefined {\n return this.mapElements.find((element) => element.id === elementId);\n }\n\n /**\n * Checks if a map element is currently visible\n *\n * @param elementId - The ID of the element to check\n * @returns True if the element is visible, false otherwise\n */\n isMapElementVisible(elementId: TElementType): boolean {\n return this.visibleMapElements.some((element) => element.id === elementId);\n }\n\n /**\n * Adds a new map element to the repository and initializes it.\n * Sets the element's visibility based on its configuration.\n *\n * @param mapElement - The map element to add\n */\n addMapElement(mapElement: MapElement<TElementType>) {\n const currentElements = this.mapElements;\n this.mapElementsSubject.next([...currentElements, mapElement]);\n mapElement.onInit();\n if (mapElement.alwaysVisible || this.isMapElementVisible(mapElement.id)) {\n this.showMapElement(mapElement.id);\n } else {\n this.hideMapElement(mapElement.id);\n }\n }\n\n /**\n * Removes a map element from the repository and destroys it.\n *\n * @param mapElement - The map element to remove\n */\n removeMapElement(mapElement: MapElement<TElementType>) {\n const filteredElements = this.mapElements.filter((element) => element.id !== mapElement.id);\n this.mapElementsSubject.next(filteredElements);\n mapElement.onDestroy();\n }\n\n /**\n * Removes all map elements from the repository and destroys them.\n */\n removeAllMapElements() {\n this.mapElements.forEach((element) => {\n this.removeMapElement(element);\n });\n }\n\n /**\n * Sets the visibility of a map element by its ID and updates the subject with the new state.\n *\n * @param elementId - The ID of the element to update\n * @param visible - Whether the element should be visible or hidden\n */\n private setMapElementVisibility(elementId: TElementType, visible: boolean) {\n const element = this.getMapElementById(elementId);\n if (element) {\n element.setVisible(visible);\n const elements = this.mapElements.filter((el) => el.id !== elementId);\n this.mapElementsSubject.next([...elements, element]);\n }\n }\n\n /**\n * Shows a map element by its ID and updates the subject with the new state.\n *\n * @param elementId - The ID of the element to show\n */\n showMapElement(elementId: TElementType) {\n this.setMapElementVisibility(elementId, true);\n }\n\n /**\n * Hides a map element by its ID and updates the subject with the new state.\n *\n * @param elementId - The ID of the element to hide\n */\n hideMapElement(elementId: TElementType) {\n this.setMapElementVisibility(elementId, false);\n }\n\n /**\n * Toggles the visibility of a map element by its ID.\n * If the element is currently visible, it will be hidden, and vice versa.\n *\n * @param elementId - The ID of the element to toggle visibility\n */\n toggleMapElement(elementId: TElementType) {\n if (this.visibleMapElements.some((element) => element.id === elementId)) {\n this.hideMapElement(elementId);\n } else {\n this.showMapElement(elementId);\n }\n }\n\n /**\n * Gets the ID of the layer that should come before the specified element's layers\n * in the map's layer stack. This is used for maintaining proper layer ordering.\n *\n * @param elementId - The ID of the element to find a \"before\" reference for\n * @returns The ID of the first layer of the next element in order, or undefined if none exists\n */\n getBeforeId(elementId: TElementType): string | undefined {\n const currentElement = this.getMapElementById(elementId);\n if (!currentElement) {\n return undefined;\n }\n\n const nextElement = this.mapElements\n .filter((item) => item.elementOrder > currentElement.elementOrder)\n .sort((a, b) => a.elementOrder - b.elementOrder)[0];\n\n const nextElementLayerIds = nextElement?.sources\n .flatMap((source) => source.layers)\n .filter((layer) => layer.initialized)\n .map((layer) => layer.id);\n\n return nextElement && nextElementLayerIds.length > 0 ? nextElementLayerIds[0] : undefined;\n }\n}\n","import { Map } from 'maplibre-gl';\nimport { Subject } from 'rxjs';\n\nimport { MapElementRepository } from './map-element.repository';\nimport { MapSource } from './map-source';\nimport { MaplibreCursorService } from './maplibre-cursor.service';\n\nexport interface MapElementConfig<TElementType> {\n map: Map;\n mapElementRepository: MapElementRepository<TElementType>;\n maplibreCursorService: MaplibreCursorService;\n elementId: TElementType;\n elementOrder: number;\n}\n\nexport abstract class MapElement<TElementType> {\n id: TElementType;\n elementOrder: number;\n sources: MapSource<TElementType>[] = [];\n alwaysVisible = false;\n isVisible = false;\n\n protected unsubscribe = new Subject<void>();\n\n constructor(protected readonly config: MapElementConfig<TElementType>) {\n this.id = config.elementId;\n this.elementOrder = config.elementOrder;\n }\n\n onInit() {\n this.sources.forEach((source) => source.onInit());\n }\n\n onDestroy() {\n this.sources.forEach((source) => source.onDestroy());\n\n this.unsubscribe.next();\n this.unsubscribe.complete();\n }\n\n setVisible(visible: boolean) {\n this.isVisible = visible;\n this.sources.forEach((source) => source.setVisible(visible));\n }\n}\n","import { Feature } from 'geojson';\nimport { MapElementConfig } from './map-element';\nimport {\n FilterSpecification,\n LayerSpecification,\n MapGeoJSONFeature,\n MapMouseEvent,\n} from 'maplibre-gl';\nimport { Subject } from 'rxjs';\n\nexport abstract class MapLayer<TElementType> {\n initialized = false;\n\n protected unsubscribe = new Subject<void>();\n\n constructor(\n protected readonly config: MapElementConfig<TElementType>,\n protected readonly sourceId: string,\n protected readonly layerIdSuffix?: string,\n ) {}\n\n get id(): string {\n const suffix = this.layerIdSuffix ? `-${this.layerIdSuffix}` : '';\n return `${this.sourceId}${suffix}`;\n }\n\n get styleLayer() {\n return this.config.map.getLayer(this.id);\n }\n\n onInit() {\n // Add the layer to the map, with the correct ordering (beforeId)\n const beforeId = this.config.mapElementRepository.getBeforeId(this.config.elementId);\n this.config.map.addLayer(this.getSpecification() as LayerSpecification, beforeId);\n\n // Keeping track of which layers have been added to the map to allow for beforeId determination\n this.#setupClickHandlers();\n this.initialized = true;\n }\n\n onDestroy() {\n this.initialized = false;\n this.#removeClickHandlers();\n\n this.config.map.removeLayer(this.id);\n\n this.unsubscribe.next();\n this.unsubscribe.complete();\n }\n\n setVisible(visible: boolean) {\n this.config.map.setLayoutProperty(this.id, 'visibility', visible ? 'visible' : 'none');\n }\n\n protected onClick?(features: Feature[]): void;\n\n protected abstract getSpecification(): Partial<LayerSpecification>;\n getFilterSpecification?(): FilterSpecification;\n\n #setupClickHandlers() {\n if (!this.onClick) return;\n\n this.config.map.on('click', this.id, (event) => {\n this.onClick?.(this.#getFeatures(event));\n });\n\n this.config.maplibreCursorService.setMouseCursor(this.config.map, this.id);\n }\n\n #removeClickHandlers() {\n if (!this.onClick) return;\n\n this.config.map.off('click', this.id, (event) => {\n this.onClick?.(this.#getFeatures(event));\n });\n }\n\n #getFeatures(event: ClickEvent): Feature[] {\n if (event.features && event.features.length > 0) {\n return this.#distinctFeatures(event.features);\n }\n return [];\n }\n\n /**\n * Filters an array of features to remove duplicates based on their properties.\n * Two features are considered duplicates if they have identical properties.\n * Particularly vector sources can yield duplicate features due to the way they\n * are rendered in tiles.\n *\n * @param features - Array of GeoJSON features to filter\n * @returns Array of unique features with no property duplicates\n * @private\n */\n #distinctFeatures(features: Feature[]): Feature[] {\n const seen = new Set();\n const uniqueFeatures: Feature[] = [];\n\n features.forEach((feature) => {\n const propertiesString = JSON.stringify(feature.properties);\n if (!seen.has(propertiesString)) {\n seen.add(propertiesString);\n uniqueFeatures.push(feature);\n }\n });\n\n return uniqueFeatures;\n }\n}\n\nexport type ClickEvent = MapMouseEvent & {\n features?: MapGeoJSONFeature[];\n} & object;\n","import { FeatureCollection } from 'geojson';\nimport { GeoJSONSource, SourceSpecification } from 'maplibre-gl';\nimport { Observable, Subject, takeUntil } from 'rxjs';\nimport { MapLayer } from './map-layer';\nimport { MapElementConfig } from './map-element';\n\nexport abstract class MapSource<TElementType> {\n layers: MapLayer<TElementType>[] = [];\n\n protected unsubscribe = new Subject<void>();\n\n #isInitialized = false;\n #featureCollection$?: Observable<FeatureCollection>;\n\n constructor(\n public readonly id: string,\n protected readonly config: MapElementConfig<TElementType>,\n ) {}\n\n onInit() {\n if (!this.config.map.getSource(this.id)) {\n this.config.map.addSource(this.id, this.getSpecification() as SourceSpecification);\n }\n\n this.layers.forEach((layer) => layer.onInit());\n\n this.isInitialized = true;\n }\n\n onDestroy() {\n this.isInitialized = false;\n\n this.layers.forEach((layer) => layer.onDestroy());\n\n this.unsubscribe.next();\n this.unsubscribe.complete();\n }\n\n get featureCollection$(): Observable<FeatureCollection> | undefined {\n return this.#featureCollection$;\n }\n\n set featureCollection$(value: Observable<FeatureCollection> | undefined) {\n this.#featureCollection$ = value;\n this.#subscribeToFeatureCollection();\n }\n\n get isInitialized(): boolean {\n return this.#isInitialized;\n }\n\n set isInitialized(value: boolean) {\n this.#isInitialized = value;\n this.#subscribeToFeatureCollection();\n }\n\n setVisible(visible: boolean) {\n this.layers.forEach((layer) => layer.setVisible(visible));\n }\n\n protected abstract getSpecification(): Partial<SourceSpecification>;\n\n #subscribeToFeatureCollection() {\n if (this.isInitialized && this.featureCollection$) {\n this.featureCollection$.pipe(takeUntil(this.unsubscribe)).subscribe({\n next: (featureCollection) => {\n const source = this.config.map.getSource(this.id);\n\n if (source && source.type === 'geojson') {\n (source as GeoJSONSource).setData(featureCollection);\n } else {\n // not needed because source is always specified as geojson with empty collection\n this.config.map.addSource(this.id, {\n type: 'geojson',\n data: featureCollection,\n });\n }\n },\n error: (error) => {\n console.error('Error updating feature collection', error);\n },\n });\n }\n }\n}\n","import {\n AfterViewInit,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n OnDestroy,\n viewChild,\n} from '@angular/core';\nimport { AnimationOptions, Map } from 'maplibre-gl';\n\n@Component({\n standalone: true,\n template: '<div #mapContainer class=\"map\"></div>',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport abstract class MapComponent implements AfterViewInit, OnDestroy {\n mapContainer = viewChild.required<ElementRef>('mapContainer');\n map!: Map;\n\n ngAfterViewInit() {\n this.map = this.#createMap(this.mapContainer().nativeElement);\n this.map.once('load', () => this.#initiateMapLoading());\n }\n\n ngOnDestroy() {\n // Note: Using `this.map.once('remove', this.onRemoveMap())` does not work correctly because,\n // by the time the `onRemoveMap` event is triggered, it is no longer possible to interact with the map for cleanup.\n // To address this we call `onRemoveMap` explicitly before destroying the MapLibre instance. This\n // ensures that our map elements are cleaned up properly before the map is removed.\n this.onRemoveMap();\n this.map?.remove();\n }\n\n resizeMap() {\n this.map?.resize();\n }\n\n zoomToLevel(zoomLevel: number, options?: AnimationOptions) {\n this.map?.zoomTo(zoomLevel, options);\n }\n\n #createMap(container: HTMLElement): Map {\n const map = new Map({\n container,\n style: {\n version: 8,\n sources: {},\n layers: [],\n },\n interactive: true,\n maxZoom: 18,\n minZoom: 6,\n bounds: [\n [5.34458238242172, 52.11623605695118],\n [5.446205917577942, 52.21132028216525],\n ],\n });\n\n // Prevent map rotation\n map.dragRotate.disable();\n\n return map;\n }\n\n protected abstract onLoadMap(): void;\n protected abstract onRemoveMap(): void;\n protected abstract onIdle(): void;\n\n #initiateMapLoading() {\n this.onLoadMap();\n this.resizeMap();\n }\n}\n","import { Injectable } from '@angular/core';\nimport { Map } from 'maplibre-gl';\n\n/**\n * Service for managing MapLibre map cursor interactions.\n *\n * Maplibre has no automatic cursor handling for layers, so when you add a clickHandler\n * to a layer, the user can click on it, but the cursor will not change to a pointer.\n * This service provides methods to set the cursor to pointer when hovering over a layer,\n * and to enable crosshair mode which takes precedence over all other cursor types.\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class MaplibreCursorService {\n /** Counter to track how many layers currently need a pointer cursor */\n #cursorPointerCount = 0;\n /** Flag indicating if crosshair mode is active */\n #crosshairMode = false;\n\n /**\n * Sets up mouse cursor handling for a map layer.\n * Changes cursor to pointer when mouse enters the layer and resets when leaving.\n *\n * @param map - The MapLibre map instance\n * @param layerId - ID of the layer to apply cursor behavior to\n */\n setMouseCursor(map: Map, layerId: string) {\n const updateCursor = (cursor: string) => {\n if (!this.#crosshairMode) this.#setMouseCursor(map, cursor);\n };\n\n map.on('mouseenter', layerId, () => updateCursor('pointer'));\n map.on('mouseleave', layerId, () => updateCursor(''));\n }\n\n /**\n * Enables or disables crosshair cursor mode for the map.\n * When enabled, crosshair cursor will take precedence over all other cursor types.\n *\n * @param value - True to enable crosshair mode, false to disable\n * @param map - The MapLibre map instance to apply the cursor to\n */\n /**\n * Enables or disables crosshair cursor mode for the map.\n * When enabled, crosshair cursor will take precedence over all other cursor types.\n *\n * @param value - True to enable crosshair mode, false to disable\n * @param map - The MapLibre map instance to apply the cursor to\n */\n setCrosshairMode(value: boolean, map: Map) {\n this.#crosshairMode = value;\n\n let cursorType = '';\n if (value) {\n cursorType = 'crosshair';\n } else if (this.#cursorPointerCount > 0) {\n cursorType = 'pointer';\n }\n\n map.getCanvas().style.cursor = cursorType;\n }\n\n /**\n * Internal method to handle mouse cursor state changes.\n * Manages the cursor pointer reference counter to ensure cursor displays correctly\n * when hovering over multiple interactive layers.\n *\n * @param map - The MapLibre map instance\n * @param cursor - Cursor type to set ('pointer' or '' to reset)\n * @private\n */\n #setMouseCursor(map: Map, cursor: string) {\n if (this.#crosshairMode) {\n // Crosshair mode takes precedence, ignore other cursor settings\n map.getCanvas().style.cursor = 'crosshair';\n return;\n }\n\n if (cursor === 'pointer') {\n this.#cursorPointerCount++;\n map.getCanvas().style.cursor = cursor;\n } else if (cursor === '') {\n this.#cursorPointerCount--;\n if (this.#cursorPointerCount < 1) {\n map.getCanvas().style.cursor = '';\n }\n } else {\n console.warn(`Cursor type '${cursor}' is not supported.`);\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './lib/map/map-constants';
2
+ export * from './lib/map/map-element.repository';
3
+ export * from './lib/map/map-element';
4
+ export * from './lib/map/map-layer';
5
+ export * from './lib/map/map-source';
6
+ export * from './lib/map/map.component';
7
+ export * from './lib/map/maplibre-cursor.service';
@@ -0,0 +1,3 @@
1
+ import { LngLatLike } from 'maplibre-gl';
2
+ export declare const BOUNDS_NL: [LngLatLike, LngLatLike];
3
+ export declare const BOUNDS_AMERSFOORT: [LngLatLike, LngLatLike];
@@ -0,0 +1,25 @@
1
+ import { Map } from 'maplibre-gl';
2
+ import { Subject } from 'rxjs';
3
+ import { MapElementRepository } from './map-element.repository';
4
+ import { MapSource } from './map-source';
5
+ import { MaplibreCursorService } from './maplibre-cursor.service';
6
+ export interface MapElementConfig<TElementType> {
7
+ map: Map;
8
+ mapElementRepository: MapElementRepository<TElementType>;
9
+ maplibreCursorService: MaplibreCursorService;
10
+ elementId: TElementType;
11
+ elementOrder: number;
12
+ }
13
+ export declare abstract class MapElement<TElementType> {
14
+ protected readonly config: MapElementConfig<TElementType>;
15
+ id: TElementType;
16
+ elementOrder: number;
17
+ sources: MapSource<TElementType>[];
18
+ alwaysVisible: boolean;
19
+ isVisible: boolean;
20
+ protected unsubscribe: Subject<void>;
21
+ constructor(config: MapElementConfig<TElementType>);
22
+ onInit(): void;
23
+ onDestroy(): void;
24
+ setVisible(visible: boolean): void;
25
+ }