@ndwnu/map 0.0.1-beta.5 → 0.0.1-beta.7
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/fesm2022/ndwnu-map.mjs +620 -433
- package/fesm2022/ndwnu-map.mjs.map +1 -1
- package/package.json +6 -6
- package/{index.d.ts → types/ndwnu-map.d.ts} +156 -127
package/fesm2022/ndwnu-map.mjs
CHANGED
|
@@ -1,230 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Subject, takeUntil, filter, BehaviorSubject, map } from 'rxjs';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
3
|
import { viewChild, input, ChangeDetectionStrategy, Component, 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
|
-
const DEFAULT_MAP_CONFIG = {
|
|
16
|
-
center: [5.387827, 52.155172],
|
|
17
|
-
zoom: 8,
|
|
18
|
-
maxZoom: 18,
|
|
19
|
-
minZoom: 6,
|
|
20
|
-
interactive: true,
|
|
21
|
-
dragRotate: false,
|
|
22
|
-
doubleClickZoom: true,
|
|
23
|
-
scrollZoom: true,
|
|
24
|
-
boxZoom: true,
|
|
25
|
-
dragPan: true,
|
|
26
|
-
keyboard: true,
|
|
27
|
-
touchZoomRotate: true,
|
|
28
|
-
};
|
|
29
|
-
// Common bounds that users can optionally use
|
|
30
|
-
const COMMON_BOUNDS = {
|
|
31
|
-
NETHERLANDS: [
|
|
32
|
-
[3.079667, 50.587611],
|
|
33
|
-
[7.572028, 53.636667],
|
|
34
|
-
],
|
|
35
|
-
AMERSFOORT: [
|
|
36
|
-
[5.34458238242172, 52.11623605695118],
|
|
37
|
-
[5.446205917577942, 52.21132028216525],
|
|
38
|
-
],
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Repository for managing map elements.
|
|
43
|
-
* Provides methods to add, remove, show, hide, and toggle map elements.
|
|
44
|
-
* Also tracks element visibility state and provides observable streams for elements.
|
|
45
|
-
*
|
|
46
|
-
* @typeparam TElementType - The type of ID used for the map elements
|
|
47
|
-
*/
|
|
48
|
-
class MapElementRepository {
|
|
49
|
-
/** Subject that holds the current array of map elements */
|
|
50
|
-
mapElementsSubject = new BehaviorSubject([]);
|
|
51
|
-
// mapElements are not directly exposed to prevent circular reference errors when
|
|
52
|
-
// they are used in a template. Because the mapElements$ observable contains MapLibre
|
|
53
|
-
// GL JS map objects, which have circular references.
|
|
54
|
-
/** Observable stream of all map elements */
|
|
55
|
-
mapElements$ = this.mapElementsSubject.asObservable();
|
|
56
|
-
/** Observable stream of only visible map elements */
|
|
57
|
-
visibleMapElements$ = this.mapElements$.pipe(map((elements) => elements.filter((element) => element.isVisible)));
|
|
58
|
-
/**
|
|
59
|
-
* Gets the ids (TElementType) as an array of all map element
|
|
60
|
-
* @returns Array of TElementType
|
|
61
|
-
*/
|
|
62
|
-
mapElementIds$ = this.mapElements$.pipe(map((elements) => elements.map((element) => element.id)));
|
|
63
|
-
/**
|
|
64
|
-
* Gets the ids (TElementType) as an array of all visible map element
|
|
65
|
-
* @returns Array of TElementType
|
|
66
|
-
*/
|
|
67
|
-
visibleMapElementIds$ = this.visibleMapElements$.pipe(map((elements) => elements.map((element) => element.id)));
|
|
68
|
-
/**
|
|
69
|
-
* Gets the current array of all map elements
|
|
70
|
-
* @returns Array of map elements
|
|
71
|
-
*/
|
|
72
|
-
get mapElements() {
|
|
73
|
-
return this.mapElementsSubject.getValue();
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Gets only the currently visible map elements
|
|
77
|
-
* @returns Array of visible map elements
|
|
78
|
-
*/
|
|
79
|
-
get visibleMapElements() {
|
|
80
|
-
return this.mapElements.filter((element) => element.isVisible);
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Finds a map element by its ID
|
|
84
|
-
*
|
|
85
|
-
* @param elementId - The ID of the element to find
|
|
86
|
-
* @returns The map element if found, undefined otherwise
|
|
87
|
-
*/
|
|
88
|
-
getMapElementById(elementId) {
|
|
89
|
-
return this.mapElements.find((element) => element.id === elementId);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Checks if a map element is currently visible
|
|
93
|
-
*
|
|
94
|
-
* @param elementId - The ID of the element to check
|
|
95
|
-
* @returns True if the element is visible, false otherwise
|
|
96
|
-
*/
|
|
97
|
-
isMapElementVisible(elementId) {
|
|
98
|
-
return this.visibleMapElements.some((element) => element.id === elementId);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Adds a new map element to the repository and initializes it.
|
|
102
|
-
* Sets the element's visibility based on its configuration.
|
|
103
|
-
*
|
|
104
|
-
* @param mapElement - The map element to add
|
|
105
|
-
*/
|
|
106
|
-
addMapElement(mapElement) {
|
|
107
|
-
const currentElements = this.mapElements;
|
|
108
|
-
this.mapElementsSubject.next([...currentElements, mapElement]);
|
|
109
|
-
mapElement.onInit();
|
|
110
|
-
if (mapElement.alwaysVisible || this.isMapElementVisible(mapElement.id)) {
|
|
111
|
-
this.showMapElement(mapElement.id);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
this.hideMapElement(mapElement.id);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Removes a map element from the repository and destroys it.
|
|
119
|
-
*
|
|
120
|
-
* @param mapElement - The map element to remove
|
|
121
|
-
*/
|
|
122
|
-
removeMapElement(mapElement) {
|
|
123
|
-
const filteredElements = this.mapElements.filter((element) => element.id !== mapElement.id);
|
|
124
|
-
this.mapElementsSubject.next(filteredElements);
|
|
125
|
-
mapElement.onDestroy();
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Removes all map elements from the repository and destroys them.
|
|
129
|
-
*/
|
|
130
|
-
removeAllMapElements() {
|
|
131
|
-
this.mapElements.forEach((element) => {
|
|
132
|
-
this.removeMapElement(element);
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Sets the visibility of a map element by its ID and updates the subject with the new state.
|
|
137
|
-
*
|
|
138
|
-
* @param elementId - The ID of the element to update
|
|
139
|
-
* @param visible - Whether the element should be visible or hidden
|
|
140
|
-
*/
|
|
141
|
-
setMapElementVisibility(elementId, visible) {
|
|
142
|
-
const element = this.getMapElementById(elementId);
|
|
143
|
-
if (element) {
|
|
144
|
-
element.setVisible(visible);
|
|
145
|
-
const elements = this.mapElements.filter((el) => el.id !== elementId);
|
|
146
|
-
this.mapElementsSubject.next([...elements, element]);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Shows a map element by its ID and updates the subject with the new state.
|
|
151
|
-
*
|
|
152
|
-
* @param elementId - The ID of the element to show
|
|
153
|
-
*/
|
|
154
|
-
showMapElement(elementId) {
|
|
155
|
-
this.setMapElementVisibility(elementId, true);
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Hides a map element by its ID and updates the subject with the new state.
|
|
159
|
-
*
|
|
160
|
-
* @param elementId - The ID of the element to hide
|
|
161
|
-
*/
|
|
162
|
-
hideMapElement(elementId) {
|
|
163
|
-
this.setMapElementVisibility(elementId, false);
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Toggles the visibility of a map element by its ID.
|
|
167
|
-
* If the element is currently visible, it will be hidden, and vice versa.
|
|
168
|
-
*
|
|
169
|
-
* @param elementId - The ID of the element to toggle visibility
|
|
170
|
-
*/
|
|
171
|
-
toggleMapElement(elementId) {
|
|
172
|
-
if (this.visibleMapElements.some((element) => element.id === elementId)) {
|
|
173
|
-
this.hideMapElement(elementId);
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
this.showMapElement(elementId);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Gets the ID of the layer that should come before the specified element's layers
|
|
181
|
-
* in the map's layer stack. This is used for maintaining proper layer ordering.
|
|
182
|
-
*
|
|
183
|
-
* @param elementId - The ID of the element to find a "before" reference for
|
|
184
|
-
* @returns The ID of the first layer of the next element in order, or undefined if none exists
|
|
185
|
-
*/
|
|
186
|
-
getBeforeId(elementId) {
|
|
187
|
-
const currentElement = this.getMapElementById(elementId);
|
|
188
|
-
if (!currentElement) {
|
|
189
|
-
return undefined;
|
|
190
|
-
}
|
|
191
|
-
const nextElement = this.mapElements
|
|
192
|
-
.filter((item) => item.elementOrder > currentElement.elementOrder)
|
|
193
|
-
.sort((a, b) => a.elementOrder - b.elementOrder)[0];
|
|
194
|
-
const nextElementLayerIds = nextElement?.sources
|
|
195
|
-
.flatMap((source) => source.layers)
|
|
196
|
-
.filter((layer) => layer.initialized)
|
|
197
|
-
.map((layer) => layer.id);
|
|
198
|
-
return nextElement && nextElementLayerIds.length > 0 ? nextElementLayerIds[0] : undefined;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
class MapElement {
|
|
203
|
-
config;
|
|
204
|
-
id;
|
|
205
|
-
elementOrder;
|
|
206
|
-
sources = [];
|
|
207
|
-
alwaysVisible = false;
|
|
208
|
-
isVisible = false;
|
|
209
|
-
unsubscribe = new Subject();
|
|
210
|
-
constructor(config) {
|
|
211
|
-
this.config = config;
|
|
212
|
-
this.id = config.elementId;
|
|
213
|
-
this.elementOrder = config.elementOrder;
|
|
214
|
-
}
|
|
215
|
-
onInit() {
|
|
216
|
-
this.sources.forEach((source) => source.onInit());
|
|
217
|
-
}
|
|
218
|
-
onDestroy() {
|
|
219
|
-
this.sources.forEach((source) => source.onDestroy());
|
|
220
|
-
this.unsubscribe.next();
|
|
221
|
-
this.unsubscribe.complete();
|
|
222
|
-
}
|
|
223
|
-
setVisible(visible) {
|
|
224
|
-
this.isVisible = visible;
|
|
225
|
-
this.sources.forEach((source) => source.setVisible(visible));
|
|
226
|
-
}
|
|
227
|
-
}
|
|
4
|
+
import { Map as Map$1 } from 'maplibre-gl';
|
|
228
5
|
|
|
229
6
|
class MapLayer {
|
|
230
7
|
config;
|
|
@@ -265,8 +42,12 @@ class MapLayer {
|
|
|
265
42
|
#setupClickHandlers() {
|
|
266
43
|
if (!this.onClick)
|
|
267
44
|
return;
|
|
45
|
+
const mapElement = this.config.mapElementRepository.getMapElementById(this.config.elementId);
|
|
46
|
+
if (!mapElement?.isInteractive) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
268
49
|
this.config.map.on('click', this.id, (event) => {
|
|
269
|
-
this.onClick?.(this.#getFeatures(event));
|
|
50
|
+
this.onClick?.(event.point, this.#getFeatures(event));
|
|
270
51
|
});
|
|
271
52
|
this.config.maplibreCursorService.setMouseCursor(this.config.map, this.id);
|
|
272
53
|
}
|
|
@@ -274,7 +55,7 @@ class MapLayer {
|
|
|
274
55
|
if (!this.onClick)
|
|
275
56
|
return;
|
|
276
57
|
this.config.map.off('click', this.id, (event) => {
|
|
277
|
-
this.onClick?.(this.#getFeatures(event));
|
|
58
|
+
this.onClick?.(event.point, this.#getFeatures(event));
|
|
278
59
|
});
|
|
279
60
|
}
|
|
280
61
|
#getFeatures(event) {
|
|
@@ -307,27 +88,95 @@ class MapLayer {
|
|
|
307
88
|
}
|
|
308
89
|
}
|
|
309
90
|
|
|
310
|
-
class
|
|
311
|
-
|
|
312
|
-
config
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (!this.config.map.getSource(this.id)) {
|
|
323
|
-
this.config.map.addSource(this.id, this.getSpecification());
|
|
91
|
+
class ApiLayer extends MapLayer {
|
|
92
|
+
_layerSpecification;
|
|
93
|
+
constructor(config, _layerSpecification) {
|
|
94
|
+
const sourceId = 'source' in _layerSpecification ? _layerSpecification.source : '';
|
|
95
|
+
super(config, sourceId);
|
|
96
|
+
this._layerSpecification = _layerSpecification;
|
|
97
|
+
// Ensure that layout visibility is set after the base constructor has been called
|
|
98
|
+
if (this._layerSpecification.layout) {
|
|
99
|
+
this._layerSpecification.layout.visibility = 'none';
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this._layerSpecification.layout = { visibility: 'none' };
|
|
324
103
|
}
|
|
325
|
-
this.layers.forEach((layer) => layer.onInit());
|
|
326
|
-
this.isInitialized = true;
|
|
327
104
|
}
|
|
328
|
-
|
|
329
|
-
this.
|
|
330
|
-
|
|
105
|
+
get id() {
|
|
106
|
+
return this._layerSpecification.id;
|
|
107
|
+
}
|
|
108
|
+
getSpecification() {
|
|
109
|
+
return this._layerSpecification;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class ApiBackgroundLayer extends ApiLayer {
|
|
114
|
+
constructor(config, layerSpecification) {
|
|
115
|
+
super(config, layerSpecification);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
class MapElement {
|
|
120
|
+
config;
|
|
121
|
+
id;
|
|
122
|
+
elementOrder;
|
|
123
|
+
sources = [];
|
|
124
|
+
alwaysVisible = false;
|
|
125
|
+
isVisible = false;
|
|
126
|
+
isInteractive = true;
|
|
127
|
+
unsubscribe = new Subject();
|
|
128
|
+
constructor(config) {
|
|
129
|
+
this.config = config;
|
|
130
|
+
this.id = config.elementId;
|
|
131
|
+
this.elementOrder = config.elementOrder;
|
|
132
|
+
if (config.isInteractive !== undefined) {
|
|
133
|
+
this.isInteractive = config.isInteractive;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
get legendItems() {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
onInit() {
|
|
140
|
+
this.sources.forEach((source) => source.onInit());
|
|
141
|
+
}
|
|
142
|
+
onDestroy() {
|
|
143
|
+
this.sources.forEach((source) => source.onDestroy());
|
|
144
|
+
this.unsubscribe.next();
|
|
145
|
+
this.unsubscribe.complete();
|
|
146
|
+
}
|
|
147
|
+
setVisible(visible) {
|
|
148
|
+
this.isVisible = visible;
|
|
149
|
+
this.sources.forEach((source) => source.setVisible(visible));
|
|
150
|
+
}
|
|
151
|
+
reload() {
|
|
152
|
+
this.sources.forEach((source) => source.reload());
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
class MapSource {
|
|
157
|
+
id;
|
|
158
|
+
config;
|
|
159
|
+
layers = [];
|
|
160
|
+
unsubscribe = new Subject();
|
|
161
|
+
activeFilter;
|
|
162
|
+
#isInitialized = false;
|
|
163
|
+
#filter$;
|
|
164
|
+
#filterSubscription;
|
|
165
|
+
#featureCollection$;
|
|
166
|
+
constructor(id, config) {
|
|
167
|
+
this.id = id;
|
|
168
|
+
this.config = config;
|
|
169
|
+
}
|
|
170
|
+
onInit() {
|
|
171
|
+
if (!this.config.map.getSource(this.id)) {
|
|
172
|
+
this.config.map.addSource(this.id, this.getSpecification());
|
|
173
|
+
}
|
|
174
|
+
this.layers.forEach((layer) => layer.onInit());
|
|
175
|
+
this.isInitialized = true;
|
|
176
|
+
}
|
|
177
|
+
onDestroy() {
|
|
178
|
+
this.isInitialized = false;
|
|
179
|
+
this.layers.forEach((layer) => layer.onDestroy());
|
|
331
180
|
this.unsubscribe.next();
|
|
332
181
|
this.unsubscribe.complete();
|
|
333
182
|
}
|
|
@@ -344,93 +193,537 @@ class MapSource {
|
|
|
344
193
|
set isInitialized(value) {
|
|
345
194
|
this.#isInitialized = value;
|
|
346
195
|
this.#subscribeToFeatureCollection();
|
|
196
|
+
this.#subscribeToFilters();
|
|
197
|
+
}
|
|
198
|
+
get filter$() {
|
|
199
|
+
return this.#filter$;
|
|
200
|
+
}
|
|
201
|
+
set filter$(value) {
|
|
202
|
+
this.#filter$ = value;
|
|
203
|
+
this.#subscribeToFilters();
|
|
204
|
+
}
|
|
205
|
+
setVisible(visible) {
|
|
206
|
+
this.layers.forEach((layer) => layer.setVisible(visible));
|
|
207
|
+
}
|
|
208
|
+
reload() {
|
|
209
|
+
this.config.map.refreshTiles(this.id);
|
|
210
|
+
}
|
|
211
|
+
applyFilterToLayers(filterValue) {
|
|
212
|
+
const hasSourceFilter = !!this.getFilterSpecification;
|
|
213
|
+
const hasLayerFilter = this.layers.some((layer) => !!layer.getFilterSpecification);
|
|
214
|
+
if (!hasSourceFilter && !hasLayerFilter) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.activeFilter = filterValue;
|
|
218
|
+
const sourceFilter = this.getFilterSpecification?.(filterValue);
|
|
219
|
+
this.layers.forEach((layer) => {
|
|
220
|
+
const layerFilter = layer.getFilterSpecification?.(filterValue);
|
|
221
|
+
const combinedFilter = (sourceFilter && layerFilter
|
|
222
|
+
? ['all', sourceFilter, layerFilter]
|
|
223
|
+
: sourceFilter || layerFilter);
|
|
224
|
+
this.config.map.setFilter(layer.id, combinedFilter);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
#subscribeToFilters() {
|
|
228
|
+
if (!this.isInitialized)
|
|
229
|
+
return;
|
|
230
|
+
if (this.#filterSubscription) {
|
|
231
|
+
this.#filterSubscription.unsubscribe();
|
|
232
|
+
}
|
|
233
|
+
if (this.#filter$) {
|
|
234
|
+
this.#filterSubscription = this.#filter$.pipe(takeUntil(this.unsubscribe)).subscribe({
|
|
235
|
+
next: (filter) => {
|
|
236
|
+
this.applyFilterToLayers(filter);
|
|
237
|
+
},
|
|
238
|
+
error: (error) => {
|
|
239
|
+
console.error('Error updating filter', error);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
this.applyFilterToLayers(undefined);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
#subscribeToFeatureCollection() {
|
|
248
|
+
if (this.isInitialized && this.featureCollection$) {
|
|
249
|
+
this.featureCollection$.pipe(takeUntil(this.unsubscribe)).subscribe({
|
|
250
|
+
next: (featureCollection) => {
|
|
251
|
+
const source = this.config.map.getSource(this.id);
|
|
252
|
+
if (source && source.type === 'geojson') {
|
|
253
|
+
source.setData(featureCollection);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// not needed because source is always specified as geojson with empty collection
|
|
257
|
+
this.config.map.addSource(this.id, {
|
|
258
|
+
type: 'geojson',
|
|
259
|
+
data: featureCollection,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
error: (error) => {
|
|
264
|
+
console.error('Error updating feature collection', error);
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
class ApiSource extends MapSource {
|
|
272
|
+
_styleSpecification;
|
|
273
|
+
_layerFilter;
|
|
274
|
+
constructor(id, config, _styleSpecification, _layerFilter) {
|
|
275
|
+
super(id, config);
|
|
276
|
+
this._styleSpecification = _styleSpecification;
|
|
277
|
+
this._layerFilter = _layerFilter;
|
|
278
|
+
}
|
|
279
|
+
getSpecification() {
|
|
280
|
+
return this._styleSpecification.sources[this.id];
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Creates ApiLayer instances for this source without initializing them.
|
|
284
|
+
*/
|
|
285
|
+
createLayers(layers) {
|
|
286
|
+
this.layers = layers
|
|
287
|
+
.filter((layer) => layer.type !== 'background')
|
|
288
|
+
.filter((layer) => layer.source === this.id)
|
|
289
|
+
.filter((layer) => !this._layerFilter || this._layerFilter(layer))
|
|
290
|
+
.map((layer) => new ApiLayer(this.config, layer));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
class ApiElement extends MapElement {
|
|
295
|
+
_http;
|
|
296
|
+
_mapStyleUrl;
|
|
297
|
+
_layerFilter;
|
|
298
|
+
backgroundLayers = [];
|
|
299
|
+
#visible = false;
|
|
300
|
+
constructor(config, _http, _mapStyleUrl, _layerFilter) {
|
|
301
|
+
super(config);
|
|
302
|
+
this._http = _http;
|
|
303
|
+
this._mapStyleUrl = _mapStyleUrl;
|
|
304
|
+
this._layerFilter = _layerFilter;
|
|
305
|
+
}
|
|
306
|
+
onInit() {
|
|
307
|
+
super.onInit();
|
|
308
|
+
this._http
|
|
309
|
+
.get(this._mapStyleUrl)
|
|
310
|
+
.pipe(filter((styleSpecification) => !!styleSpecification), takeUntil(this.unsubscribe))
|
|
311
|
+
.subscribe((styleSpecification) => {
|
|
312
|
+
this.#createBackgroundLayers(styleSpecification.layers);
|
|
313
|
+
this.#createSources(styleSpecification);
|
|
314
|
+
// If setVisible() was called before these layers were created,
|
|
315
|
+
// re-apply the current visibility state so the new layers respect it.
|
|
316
|
+
this.setVisible(this.#visible);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
setVisible(visible) {
|
|
320
|
+
this.#visible = visible;
|
|
321
|
+
super.setVisible(visible);
|
|
322
|
+
this.backgroundLayers.forEach((layer) => layer.setVisible(visible));
|
|
323
|
+
}
|
|
324
|
+
#createSources(styleSpecification) {
|
|
325
|
+
const layers = styleSpecification.layers;
|
|
326
|
+
// First, create all sources and their layer instances (without initializing them)
|
|
327
|
+
this.sources = Object.entries(styleSpecification.sources).map(([id]) => {
|
|
328
|
+
const source = new ApiSource(id, this.config, styleSpecification, this._layerFilter);
|
|
329
|
+
source.onInit();
|
|
330
|
+
source.createLayers(layers);
|
|
331
|
+
return source;
|
|
332
|
+
});
|
|
333
|
+
// Then, initialize all layers according to the style.json order
|
|
334
|
+
// This ensures correct ordering regardless of which source they belong to
|
|
335
|
+
this.#initializeLayersInOrder(layers);
|
|
336
|
+
}
|
|
337
|
+
#initializeLayersInOrder(layers) {
|
|
338
|
+
// Create a map of layerId -> ApiLayer for quick lookup
|
|
339
|
+
const layerMap = new Map(this.sources.flatMap((source) => source.layers.map((layer) => [layer.id, layer])));
|
|
340
|
+
// Initialize layers in the order they appear in style.json
|
|
341
|
+
for (const layerSpec of layers) {
|
|
342
|
+
const layer = layerMap.get(layerSpec.id);
|
|
343
|
+
if (layer) {
|
|
344
|
+
layer.onInit();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// API elements are created from the style specification and this can contain layers of type 'background'
|
|
349
|
+
// These layers do not have a source and therefore don't fit in the element > source > layer hierarchy
|
|
350
|
+
// That is why we create these layers from the element itself
|
|
351
|
+
#createBackgroundLayers(layerSpecifications) {
|
|
352
|
+
this.backgroundLayers = layerSpecifications
|
|
353
|
+
.filter((layer) => layer.type === 'background')
|
|
354
|
+
.filter((layer) => !this._layerFilter || this._layerFilter(layer))
|
|
355
|
+
.map((layerSpecification) => {
|
|
356
|
+
const apiLayer = new ApiBackgroundLayer(this.config, layerSpecification);
|
|
357
|
+
apiLayer.onInit();
|
|
358
|
+
return apiLayer;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const DEFAULT_MAP_CONFIG = {
|
|
364
|
+
center: [5.387827, 52.155172],
|
|
365
|
+
zoom: 8,
|
|
366
|
+
maxZoom: 18,
|
|
367
|
+
minZoom: 6,
|
|
368
|
+
interactive: true,
|
|
369
|
+
dragRotate: false,
|
|
370
|
+
doubleClickZoom: true,
|
|
371
|
+
scrollZoom: true,
|
|
372
|
+
boxZoom: true,
|
|
373
|
+
dragPan: true,
|
|
374
|
+
keyboard: true,
|
|
375
|
+
touchZoomRotate: true,
|
|
376
|
+
};
|
|
377
|
+
// Common bounds that users can optionally use
|
|
378
|
+
const COMMON_BOUNDS = {
|
|
379
|
+
NETHERLANDS: [
|
|
380
|
+
[3.079667, 50.587611],
|
|
381
|
+
[7.572028, 53.636667],
|
|
382
|
+
],
|
|
383
|
+
AMERSFOORT: [
|
|
384
|
+
[5.34458238242172, 52.11623605695118],
|
|
385
|
+
[5.446205917577942, 52.21132028216525],
|
|
386
|
+
],
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
class MapComponent {
|
|
390
|
+
mapContainer = viewChild.required('mapContainer');
|
|
391
|
+
config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
392
|
+
map;
|
|
393
|
+
ngAfterViewInit() {
|
|
394
|
+
this.map = this.#createMap(this.mapContainer().nativeElement);
|
|
395
|
+
this.map.once('load', () => this.#initiateMapLoading());
|
|
396
|
+
}
|
|
397
|
+
ngOnDestroy() {
|
|
398
|
+
// Note: Using `this.map.once('remove', this.onRemoveMap())` does not work correctly because,
|
|
399
|
+
// by the time the `onRemoveMap` event is triggered, it is no longer possible to interact with the map for cleanup.
|
|
400
|
+
// To address this we call `onRemoveMap` explicitly before destroying the MapLibre instance. This
|
|
401
|
+
// ensures that our map elements are cleaned up properly before the map is removed.
|
|
402
|
+
this.onRemoveMap();
|
|
403
|
+
this.map?.remove();
|
|
404
|
+
}
|
|
405
|
+
resizeMap() {
|
|
406
|
+
this.map?.resize();
|
|
407
|
+
}
|
|
408
|
+
zoomToLevel(zoomLevel, options) {
|
|
409
|
+
this.map?.zoomTo(zoomLevel, options);
|
|
410
|
+
}
|
|
411
|
+
#createMap(container) {
|
|
412
|
+
const config = { ...DEFAULT_MAP_CONFIG, ...this.config() };
|
|
413
|
+
const options = {
|
|
414
|
+
container,
|
|
415
|
+
style: {
|
|
416
|
+
version: 8,
|
|
417
|
+
sources: {},
|
|
418
|
+
layers: [],
|
|
419
|
+
},
|
|
420
|
+
maxZoom: config.maxZoom,
|
|
421
|
+
minZoom: config.minZoom,
|
|
422
|
+
interactive: config.interactive,
|
|
423
|
+
doubleClickZoom: config.doubleClickZoom,
|
|
424
|
+
scrollZoom: config.scrollZoom,
|
|
425
|
+
boxZoom: config.boxZoom,
|
|
426
|
+
dragPan: config.dragPan,
|
|
427
|
+
keyboard: config.keyboard,
|
|
428
|
+
touchZoomRotate: config.touchZoomRotate,
|
|
429
|
+
};
|
|
430
|
+
const map = new Map$1(options);
|
|
431
|
+
return map;
|
|
432
|
+
}
|
|
433
|
+
#initiateMapLoading() {
|
|
434
|
+
this.onLoadMap();
|
|
435
|
+
this.resizeMap();
|
|
436
|
+
}
|
|
437
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
438
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.8", type: MapComponent, isStandalone: true, selector: "ng-component", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, 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 });
|
|
439
|
+
}
|
|
440
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MapComponent, decorators: [{
|
|
441
|
+
type: Component,
|
|
442
|
+
args: [{
|
|
443
|
+
standalone: true,
|
|
444
|
+
template: '<div #mapContainer class="map"></div>',
|
|
445
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
446
|
+
}]
|
|
447
|
+
}], propDecorators: { mapContainer: [{ type: i0.ViewChild, args: ['mapContainer', { isSignal: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }] } });
|
|
448
|
+
|
|
449
|
+
const BOUNDS_NL = [
|
|
450
|
+
[3.079667, 50.587611],
|
|
451
|
+
[7.572028, 53.636667],
|
|
452
|
+
];
|
|
453
|
+
const BOUNDS_AMERSFOORT = [
|
|
454
|
+
[5.34458238242172, 52.11623605695118],
|
|
455
|
+
[5.446205917577942, 52.21132028216525],
|
|
456
|
+
];
|
|
457
|
+
const lineWidthFrcSpecification = [
|
|
458
|
+
'interpolate',
|
|
459
|
+
['exponential', 1.1],
|
|
460
|
+
['zoom'],
|
|
461
|
+
10,
|
|
462
|
+
[
|
|
463
|
+
'match',
|
|
464
|
+
['get', 'functionalRoadClass'],
|
|
465
|
+
'0',
|
|
466
|
+
2.5,
|
|
467
|
+
'1',
|
|
468
|
+
2.5,
|
|
469
|
+
'2',
|
|
470
|
+
2.5,
|
|
471
|
+
'3',
|
|
472
|
+
1.5,
|
|
473
|
+
'4',
|
|
474
|
+
1.5,
|
|
475
|
+
'5',
|
|
476
|
+
1,
|
|
477
|
+
0,
|
|
478
|
+
],
|
|
479
|
+
13,
|
|
480
|
+
[
|
|
481
|
+
'match',
|
|
482
|
+
['get', 'functionalRoadClass'],
|
|
483
|
+
'0',
|
|
484
|
+
7,
|
|
485
|
+
'1',
|
|
486
|
+
7,
|
|
487
|
+
'2',
|
|
488
|
+
5.7,
|
|
489
|
+
'3',
|
|
490
|
+
4.1,
|
|
491
|
+
'4',
|
|
492
|
+
4.1,
|
|
493
|
+
'5',
|
|
494
|
+
2.7,
|
|
495
|
+
'6',
|
|
496
|
+
1.5,
|
|
497
|
+
0,
|
|
498
|
+
],
|
|
499
|
+
15,
|
|
500
|
+
[
|
|
501
|
+
'match',
|
|
502
|
+
['get', 'functionalRoadClass'],
|
|
503
|
+
'0',
|
|
504
|
+
10.4,
|
|
505
|
+
'1',
|
|
506
|
+
10.4,
|
|
507
|
+
'2',
|
|
508
|
+
8.4,
|
|
509
|
+
'3',
|
|
510
|
+
6.4,
|
|
511
|
+
'4',
|
|
512
|
+
6.4,
|
|
513
|
+
'5',
|
|
514
|
+
5.4,
|
|
515
|
+
'6',
|
|
516
|
+
3.4,
|
|
517
|
+
'7',
|
|
518
|
+
3.4,
|
|
519
|
+
3,
|
|
520
|
+
],
|
|
521
|
+
20,
|
|
522
|
+
[
|
|
523
|
+
'match',
|
|
524
|
+
['get', 'functionalRoadClass'],
|
|
525
|
+
'0',
|
|
526
|
+
24,
|
|
527
|
+
'1',
|
|
528
|
+
24,
|
|
529
|
+
'2',
|
|
530
|
+
22,
|
|
531
|
+
'3',
|
|
532
|
+
20,
|
|
533
|
+
'4',
|
|
534
|
+
18,
|
|
535
|
+
'5',
|
|
536
|
+
16,
|
|
537
|
+
'6',
|
|
538
|
+
14,
|
|
539
|
+
'7',
|
|
540
|
+
10,
|
|
541
|
+
8,
|
|
542
|
+
],
|
|
543
|
+
];
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Repository for managing map elements.
|
|
547
|
+
* Provides methods to add, remove, show, hide, and toggle map elements.
|
|
548
|
+
* Also tracks element visibility state and provides observable streams for elements.
|
|
549
|
+
*
|
|
550
|
+
* @typeparam TElementType - The type of ID used for the map elements
|
|
551
|
+
* @typeparam TFilter - The type of filter used for the map elements
|
|
552
|
+
* @typeparam TLegendItem - The type of legend item used for the map elements
|
|
553
|
+
*/
|
|
554
|
+
class MapElementRepository {
|
|
555
|
+
/** Subject that holds the current array of map elements */
|
|
556
|
+
mapElementsSubject = new BehaviorSubject([]);
|
|
557
|
+
// mapElements are not directly exposed to prevent circular reference errors when
|
|
558
|
+
// they are used in a template. Because the mapElements$ observable contains MapLibre
|
|
559
|
+
// GL JS map objects, which have circular references.
|
|
560
|
+
/** Observable stream of all map elements */
|
|
561
|
+
mapElements$ = this.mapElementsSubject.asObservable();
|
|
562
|
+
/** Observable stream of only visible map elements */
|
|
563
|
+
visibleMapElements$ = this.mapElements$.pipe(map((elements) => elements.filter((element) => element.isVisible)));
|
|
564
|
+
/** Observable stream of legend items from all visible map elements */
|
|
565
|
+
legendItems$ = this.visibleMapElements$.pipe(map((elements) => elements.flatMap((element) => element.legendItems)));
|
|
566
|
+
/**
|
|
567
|
+
* Gets the ids (TElementType) as an array of all map element
|
|
568
|
+
* @returns Array of TElementType
|
|
569
|
+
*/
|
|
570
|
+
mapElementIds$ = this.mapElements$.pipe(map((elements) => elements.map((element) => element.id)));
|
|
571
|
+
/**
|
|
572
|
+
* Gets the ids (TElementType) as an array of all visible map element
|
|
573
|
+
* @returns Array of TElementType
|
|
574
|
+
*/
|
|
575
|
+
visibleMapElementIds$ = this.visibleMapElements$.pipe(map((elements) => elements.map((element) => element.id)));
|
|
576
|
+
/**
|
|
577
|
+
* Gets the ids (TElementType) as an array of all visible map element
|
|
578
|
+
* @returns Array of TElementType
|
|
579
|
+
*/
|
|
580
|
+
get visibleMapElementIds() {
|
|
581
|
+
return this.visibleMapElements.map((element) => element.id);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Gets the current array of all map elements
|
|
585
|
+
* @returns Array of map elements
|
|
586
|
+
*/
|
|
587
|
+
get mapElements() {
|
|
588
|
+
return this.mapElementsSubject.getValue();
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Gets only the currently visible map elements
|
|
592
|
+
* @returns Array of visible map elements
|
|
593
|
+
*/
|
|
594
|
+
get visibleMapElements() {
|
|
595
|
+
return this.mapElements.filter((element) => element.isVisible);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Finds a map element by its ID
|
|
599
|
+
*
|
|
600
|
+
* @param elementId - The ID of the element to find
|
|
601
|
+
* @returns The map element if found, undefined otherwise
|
|
602
|
+
*/
|
|
603
|
+
getMapElementById(elementId) {
|
|
604
|
+
return this.mapElements.find((element) => element.id === elementId);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Checks if a map element is currently visible
|
|
608
|
+
*
|
|
609
|
+
* @param elementId - The ID of the element to check
|
|
610
|
+
* @returns True if the element is visible, false otherwise
|
|
611
|
+
*/
|
|
612
|
+
isMapElementVisible(elementId) {
|
|
613
|
+
return this.visibleMapElements.some((element) => element.id === elementId);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Adds a new map element to the repository and initializes it.
|
|
617
|
+
* Sets the element's visibility based on its configuration.
|
|
618
|
+
*
|
|
619
|
+
* @param mapElement - The map element to add
|
|
620
|
+
*/
|
|
621
|
+
addMapElement(mapElement) {
|
|
622
|
+
const currentElements = this.mapElements;
|
|
623
|
+
this.mapElementsSubject.next([...currentElements, mapElement]);
|
|
624
|
+
mapElement.onInit();
|
|
625
|
+
if (mapElement.alwaysVisible || this.isMapElementVisible(mapElement.id)) {
|
|
626
|
+
this.showMapElement(mapElement.id);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
this.hideMapElement(mapElement.id);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Removes a map element from the repository and destroys it.
|
|
634
|
+
*
|
|
635
|
+
* @param mapElement - The map element to remove
|
|
636
|
+
*/
|
|
637
|
+
removeMapElement(mapElement) {
|
|
638
|
+
const filteredElements = this.mapElements.filter((element) => element.id !== mapElement.id);
|
|
639
|
+
this.mapElementsSubject.next(filteredElements);
|
|
640
|
+
mapElement.onDestroy();
|
|
347
641
|
}
|
|
348
|
-
|
|
349
|
-
|
|
642
|
+
/**
|
|
643
|
+
* Removes all map elements from the repository and destroys them.
|
|
644
|
+
*/
|
|
645
|
+
removeAllMapElements() {
|
|
646
|
+
this.mapElements.forEach((element) => {
|
|
647
|
+
this.removeMapElement(element);
|
|
648
|
+
});
|
|
350
649
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
data: featureCollection,
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
},
|
|
367
|
-
error: (error) => {
|
|
368
|
-
console.error('Error updating feature collection', error);
|
|
369
|
-
},
|
|
370
|
-
});
|
|
650
|
+
/**
|
|
651
|
+
* Sets the visibility of a map element by its ID and updates the subject with the new state.
|
|
652
|
+
*
|
|
653
|
+
* @param elementId - The ID of the element to update
|
|
654
|
+
* @param visible - Whether the element should be visible or hidden
|
|
655
|
+
*/
|
|
656
|
+
setMapElementVisibility(elementId, visible) {
|
|
657
|
+
const element = this.getMapElementById(elementId);
|
|
658
|
+
if (element) {
|
|
659
|
+
element.setVisible(visible);
|
|
660
|
+
const elements = this.mapElements.filter((el) => el.id !== elementId);
|
|
661
|
+
this.mapElementsSubject.next([...elements, element]);
|
|
371
662
|
}
|
|
372
663
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
this.map = this.#createMap(this.mapContainer().nativeElement);
|
|
381
|
-
this.map.once('load', () => this.#initiateMapLoading());
|
|
382
|
-
}
|
|
383
|
-
ngOnDestroy() {
|
|
384
|
-
// Note: Using `this.map.once('remove', this.onRemoveMap())` does not work correctly because,
|
|
385
|
-
// by the time the `onRemoveMap` event is triggered, it is no longer possible to interact with the map for cleanup.
|
|
386
|
-
// To address this we call `onRemoveMap` explicitly before destroying the MapLibre instance. This
|
|
387
|
-
// ensures that our map elements are cleaned up properly before the map is removed.
|
|
388
|
-
this.onRemoveMap();
|
|
389
|
-
this.map?.remove();
|
|
664
|
+
/**
|
|
665
|
+
* Shows a map element by its ID and updates the subject with the new state.
|
|
666
|
+
*
|
|
667
|
+
* @param elementId - The ID of the element to show
|
|
668
|
+
*/
|
|
669
|
+
showMapElement(elementId) {
|
|
670
|
+
this.setMapElementVisibility(elementId, true);
|
|
390
671
|
}
|
|
391
|
-
|
|
392
|
-
|
|
672
|
+
/**
|
|
673
|
+
* Hides a map element by its ID and updates the subject with the new state.
|
|
674
|
+
*
|
|
675
|
+
* @param elementId - The ID of the element to hide
|
|
676
|
+
*/
|
|
677
|
+
hideMapElement(elementId) {
|
|
678
|
+
this.setMapElementVisibility(elementId, false);
|
|
393
679
|
}
|
|
394
|
-
|
|
395
|
-
|
|
680
|
+
/**
|
|
681
|
+
* Triggers a reload of the tiles in the sources of the map element with the given ID
|
|
682
|
+
*
|
|
683
|
+
* @param elementId - The ID of the element to reload
|
|
684
|
+
*/
|
|
685
|
+
reloadMapElement(elementId) {
|
|
686
|
+
const element = this.getMapElementById(elementId);
|
|
687
|
+
if (element) {
|
|
688
|
+
element.reload();
|
|
689
|
+
}
|
|
396
690
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
scrollZoom: config.scrollZoom,
|
|
411
|
-
boxZoom: config.boxZoom,
|
|
412
|
-
dragPan: config.dragPan,
|
|
413
|
-
keyboard: config.keyboard,
|
|
414
|
-
touchZoomRotate: config.touchZoomRotate,
|
|
415
|
-
};
|
|
416
|
-
const map = new Map(options);
|
|
417
|
-
return map;
|
|
691
|
+
/**
|
|
692
|
+
* Toggles the visibility of a map element by its ID.
|
|
693
|
+
* If the element is currently visible, it will be hidden, and vice versa.
|
|
694
|
+
*
|
|
695
|
+
* @param elementId - The ID of the element to toggle visibility
|
|
696
|
+
*/
|
|
697
|
+
toggleMapElement(elementId) {
|
|
698
|
+
if (this.visibleMapElements.some((element) => element.id === elementId)) {
|
|
699
|
+
this.hideMapElement(elementId);
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
this.showMapElement(elementId);
|
|
703
|
+
}
|
|
418
704
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
705
|
+
/**
|
|
706
|
+
* Gets the ID of the layer that should come before the specified element's layers
|
|
707
|
+
* in the map's layer stack. This is used for maintaining proper layer ordering.
|
|
708
|
+
*
|
|
709
|
+
* @param elementId - The ID of the element to find a "before" reference for
|
|
710
|
+
* @returns The ID of the first layer of the next element in order, or undefined if none exists
|
|
711
|
+
*/
|
|
712
|
+
getBeforeId(elementId) {
|
|
713
|
+
const currentElement = this.getMapElementById(elementId);
|
|
714
|
+
if (!currentElement) {
|
|
715
|
+
return undefined;
|
|
716
|
+
}
|
|
717
|
+
const nextElement = this.mapElements
|
|
718
|
+
.filter((item) => item.elementOrder > currentElement.elementOrder)
|
|
719
|
+
.sort((a, b) => a.elementOrder - b.elementOrder)[0];
|
|
720
|
+
const nextElementLayerIds = nextElement?.sources
|
|
721
|
+
.flatMap((source) => source.layers)
|
|
722
|
+
.filter((layer) => layer.initialized)
|
|
723
|
+
.map((layer) => layer.id);
|
|
724
|
+
return nextElement && nextElementLayerIds.length > 0 ? nextElementLayerIds[0] : undefined;
|
|
422
725
|
}
|
|
423
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MapComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
424
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.9", type: MapComponent, isStandalone: true, selector: "ng-component", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, 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 });
|
|
425
726
|
}
|
|
426
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: MapComponent, decorators: [{
|
|
427
|
-
type: Component,
|
|
428
|
-
args: [{
|
|
429
|
-
standalone: true,
|
|
430
|
-
template: '<div #mapContainer class="map"></div>',
|
|
431
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
432
|
-
}]
|
|
433
|
-
}], propDecorators: { mapContainer: [{ type: i0.ViewChild, args: ['mapContainer', { isSignal: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }] } });
|
|
434
727
|
|
|
435
728
|
/**
|
|
436
729
|
* Service for managing MapLibre map cursor interactions.
|
|
@@ -514,125 +807,19 @@ class MaplibreCursorService {
|
|
|
514
807
|
console.warn(`Cursor type '${cursor}' is not supported.`);
|
|
515
808
|
}
|
|
516
809
|
}
|
|
517
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
518
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
810
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MaplibreCursorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
811
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MaplibreCursorService, providedIn: 'root' });
|
|
519
812
|
}
|
|
520
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
813
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MaplibreCursorService, decorators: [{
|
|
521
814
|
type: Injectable,
|
|
522
815
|
args: [{
|
|
523
816
|
providedIn: 'root',
|
|
524
817
|
}]
|
|
525
818
|
}] });
|
|
526
819
|
|
|
527
|
-
class ApiLayer extends MapLayer {
|
|
528
|
-
_layerSpecification;
|
|
529
|
-
constructor(config, _layerSpecification) {
|
|
530
|
-
const sourceId = 'source' in _layerSpecification ? _layerSpecification.source : '';
|
|
531
|
-
super(config, sourceId);
|
|
532
|
-
this._layerSpecification = _layerSpecification;
|
|
533
|
-
// Ensure that layout visibility is set after the base constructor has been called
|
|
534
|
-
if (this._layerSpecification.layout) {
|
|
535
|
-
this._layerSpecification.layout.visibility = 'none';
|
|
536
|
-
}
|
|
537
|
-
else {
|
|
538
|
-
this._layerSpecification.layout = { visibility: 'none' };
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
get id() {
|
|
542
|
-
return this._layerSpecification.id;
|
|
543
|
-
}
|
|
544
|
-
getSpecification() {
|
|
545
|
-
return this._layerSpecification;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
class ApiBackgroundLayer extends ApiLayer {
|
|
550
|
-
constructor(config, layerSpecification) {
|
|
551
|
-
super(config, layerSpecification);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
class ApiSource extends MapSource {
|
|
556
|
-
_styleSpecification;
|
|
557
|
-
_layerFilter;
|
|
558
|
-
constructor(id, config, _styleSpecification, _layerFilter) {
|
|
559
|
-
super(id, config);
|
|
560
|
-
this._styleSpecification = _styleSpecification;
|
|
561
|
-
this._layerFilter = _layerFilter;
|
|
562
|
-
}
|
|
563
|
-
overrideonInit() {
|
|
564
|
-
super.onInit();
|
|
565
|
-
this.createLayers(this._styleSpecification.layers);
|
|
566
|
-
}
|
|
567
|
-
getSpecification() {
|
|
568
|
-
return this._styleSpecification.sources[this.id];
|
|
569
|
-
}
|
|
570
|
-
createLayers(layers) {
|
|
571
|
-
this.layers = layers
|
|
572
|
-
.filter((layer) => layer.source === this.id)
|
|
573
|
-
.filter((layer) => !this._layerFilter || this._layerFilter(layer))
|
|
574
|
-
.map((layer) => {
|
|
575
|
-
const apiLayer = new ApiLayer(this.config, layer);
|
|
576
|
-
apiLayer.onInit();
|
|
577
|
-
return apiLayer;
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
class ApiElement extends MapElement {
|
|
583
|
-
_http;
|
|
584
|
-
_mapStyleUrl;
|
|
585
|
-
_layerFilter;
|
|
586
|
-
backgroundLayers = [];
|
|
587
|
-
#visible = false;
|
|
588
|
-
constructor(config, _http, _mapStyleUrl, _layerFilter) {
|
|
589
|
-
super(config);
|
|
590
|
-
this._http = _http;
|
|
591
|
-
this._mapStyleUrl = _mapStyleUrl;
|
|
592
|
-
this._layerFilter = _layerFilter;
|
|
593
|
-
}
|
|
594
|
-
onInit() {
|
|
595
|
-
super.onInit();
|
|
596
|
-
this._http
|
|
597
|
-
.get(this._mapStyleUrl)
|
|
598
|
-
.pipe(filter((styleSpecification) => !!styleSpecification), takeUntil(this.unsubscribe))
|
|
599
|
-
.subscribe((styleSpecification) => {
|
|
600
|
-
this.#createBackgroundLayers(styleSpecification.layers);
|
|
601
|
-
this.#createSources(styleSpecification);
|
|
602
|
-
// If setVisible() was called before these layers were created,
|
|
603
|
-
// re-apply the current visibility state so the new layers respect it.
|
|
604
|
-
this.setVisible(this.#visible);
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
setVisible(visible) {
|
|
608
|
-
this.#visible = visible;
|
|
609
|
-
super.setVisible(visible);
|
|
610
|
-
this.backgroundLayers.forEach((layer) => layer.setVisible(visible));
|
|
611
|
-
}
|
|
612
|
-
#createSources(styleSpecification) {
|
|
613
|
-
this.sources = Object.entries(styleSpecification.sources).map(([id]) => {
|
|
614
|
-
const source = new ApiSource(id, this.config, styleSpecification, this._layerFilter);
|
|
615
|
-
source.onInit();
|
|
616
|
-
return source;
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
// API elements are created from the style specification and this can contain layers of type 'background'
|
|
620
|
-
// These layers do not have a source and therefore don't fit in the element > source > layer hierarchy
|
|
621
|
-
// That is why we create these layers from the element itself
|
|
622
|
-
#createBackgroundLayers(layerSpecifications) {
|
|
623
|
-
this.backgroundLayers = layerSpecifications
|
|
624
|
-
.filter((layer) => layer.type === 'background')
|
|
625
|
-
.map((layerSpecification) => {
|
|
626
|
-
const apiLayer = new ApiBackgroundLayer(this.config, layerSpecification);
|
|
627
|
-
apiLayer.onInit();
|
|
628
|
-
return apiLayer;
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
820
|
/**
|
|
634
821
|
* Generated bundle index. Do not edit.
|
|
635
822
|
*/
|
|
636
823
|
|
|
637
|
-
export { ApiBackgroundLayer, ApiElement, ApiLayer, ApiSource, BOUNDS_AMERSFOORT, BOUNDS_NL, COMMON_BOUNDS, DEFAULT_MAP_CONFIG, MapComponent, MapElement, MapElementRepository, MapLayer, MapSource, MaplibreCursorService };
|
|
824
|
+
export { ApiBackgroundLayer, ApiElement, ApiLayer, ApiSource, BOUNDS_AMERSFOORT, BOUNDS_NL, COMMON_BOUNDS, DEFAULT_MAP_CONFIG, MapComponent, MapElement, MapElementRepository, MapLayer, MapSource, MaplibreCursorService, lineWidthFrcSpecification };
|
|
638
825
|
//# sourceMappingURL=ndwnu-map.mjs.map
|