@geospatial-sdk/maplibre 0.0.5-dev.48 → 0.0.5-dev.50

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,4 +1,4 @@
1
- import { Map, StyleSpecification } from "maplibre-gl";
1
+ import type { LayerSpecification, Map, StyleSpecification } from "maplibre-gl";
2
2
  import {
3
3
  Dataset,
4
4
  LayerContextWithStyle,
@@ -8,49 +8,48 @@ import {
8
8
  import { FeatureCollection, Geometry } from "geojson";
9
9
  import { contextStyleToMaplibreLayers } from "./style.helpers.js";
10
10
  import { getHash } from "@geospatial-sdk/core/dist/utils/hash.js";
11
- import { MapContextLayer } from "@geospatial-sdk/core";
11
+ import { MapContextBaseLayer, MapContextLayer } from "@geospatial-sdk/core";
12
12
 
13
- /**
14
- * Remove all layers from a given source in the map.
15
- * @param map
16
- * @param sourceId
17
- */
18
- export function removeLayersFromSource(map: Map, sourceId: string) {
19
- const layers = map.getStyle().layers;
20
- const layersWithSource = layers.filter(
21
- (layer) => layer.type !== "background",
22
- ) as LayerSpecificationWithSource[];
23
- const layerIds = layersWithSource
24
- .filter(
25
- (layer) => layer.hasOwnProperty("source") && layer.source === sourceId,
26
- )
27
- .map((layer) => layer.id);
28
- layerIds.forEach((layer) => map.removeLayer(layer));
13
+ function getOpacityPaintPropNames(layerType: string): string[] {
14
+ switch (layerType) {
15
+ case "circle":
16
+ return ["circle-opacity", "circle-stroke-opacity"];
17
+ default:
18
+ return [`${layerType}-opacity`];
19
+ }
29
20
  }
30
21
 
31
22
  /**
32
23
  * Create a Maplibre source and layers from a GeoJSON MapContextLayer and its style.
33
24
  * @param layerModel
34
25
  * @param geojson
35
- * @param sourcePosition
26
+ * @param metadata
36
27
  */
37
28
  export function createDatasetFromGeoJsonLayer(
38
29
  layerModel: LayerContextWithStyle,
39
30
  geojson: FeatureCollection<Geometry | null> | string,
40
- sourcePosition: number,
31
+ metadata: LayerMetadataSpecification,
41
32
  ): Dataset {
42
- const sourceId = generateLayerId(layerModel);
33
+ const sourceId = generateLayerId();
43
34
  const partialLayers = contextStyleToMaplibreLayers(layerModel.style);
44
35
  const layers = partialLayers.map((layer) => ({
45
36
  ...layer,
46
37
  id: `${sourceId}-${layer.type}`,
47
38
  source: sourceId,
39
+ paint: {
40
+ ...layer.paint,
41
+ ...getOpacityPaintPropNames(layer.type!).reduce(
42
+ (acc, prop) => ({
43
+ ...acc,
44
+ [prop]: layerModel.opacity ?? 1,
45
+ }),
46
+ {},
47
+ ),
48
+ },
48
49
  layout: {
49
50
  visibility: layerModel.visibility === false ? "none" : "visible",
50
51
  },
51
- metadata: {
52
- sourcePosition,
53
- },
52
+ metadata,
54
53
  }));
55
54
  return {
56
55
  sources: {
@@ -63,39 +62,178 @@ export function createDatasetFromGeoJsonLayer(
63
62
  } as StyleSpecification;
64
63
  }
65
64
 
65
+ export function getLayersFromContextLayer(
66
+ map: Map,
67
+ layerModel: MapContextLayer,
68
+ ): LayerSpecificationWithSource[] {
69
+ const layerId = layerModel.id;
70
+ const layerHash = generateLayerHashWithoutUpdatableProps(layerModel);
71
+
72
+ const layers = map.getStyle().layers;
73
+ const result: LayerSpecificationWithSource[] = [];
74
+ for (let i = 0; i < layers.length; i++) {
75
+ const layer = layers[i];
76
+ const metadata = layer.metadata as LayerMetadataSpecification | undefined;
77
+ if (layerId !== undefined) {
78
+ if (metadata?.layerId === layerId) {
79
+ result.push(layer as LayerSpecificationWithSource);
80
+ }
81
+ } else if (metadata?.layerHash === layerHash) {
82
+ result.push(layer as LayerSpecificationWithSource);
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+
88
+ /**
89
+ * This returns all MapLibre layers that correspond to a position in a MapContext
90
+ * @param map
91
+ * @param position
92
+ */
66
93
  export function getLayersAtPosition(
67
94
  map: Map,
68
95
  position: number,
69
96
  ): LayerSpecificationWithSource[] {
97
+ let layerId = undefined;
98
+ let layerHash = undefined;
99
+ const result: LayerSpecificationWithSource[] = [];
100
+
70
101
  const layers = map.getStyle().layers;
71
- const layersWithSource = layers.filter(
72
- (layer) => layer.type !== "background", //TODO background layers is not managed
73
- ) as LayerSpecificationWithSource[];
74
- return layersWithSource.filter(
75
- (layer) =>
76
- (layer.metadata as LayerMetadataSpecification)?.sourcePosition ===
77
- position,
78
- );
102
+ let currentPosition = -1;
103
+ for (let i = 0; i < layers.length; i++) {
104
+ const layer = layers[i] as LayerSpecificationWithSource;
105
+ const metadata = layer.metadata as LayerMetadataSpecification | undefined;
106
+ if (metadata?.layerId !== layerId || metadata?.layerHash !== layerHash) {
107
+ currentPosition++;
108
+ layerId = metadata?.layerId;
109
+ layerHash = metadata?.layerHash;
110
+ }
111
+ if (currentPosition === position) {
112
+ result.push(layer);
113
+ }
114
+ }
115
+ return result;
79
116
  }
80
117
 
81
- export function getBeforeId(map: Map, position: number): string | undefined {
82
- const beforeLayer = map
83
- .getStyle()
84
- .layers.find(
85
- (layer) =>
86
- (layer.metadata as LayerMetadataSpecification).sourcePosition ===
87
- position + 1,
88
- );
89
- return beforeLayer ? beforeLayer.id : undefined;
118
+ /**
119
+ * This returns the id of the first MapLibre layer that corresponds to the given MapContext position;
120
+ * used as a beforeId for adding/moving layers
121
+ * @param map
122
+ * @param position
123
+ */
124
+ export function getFirstLayerIdAtPosition(
125
+ map: Map,
126
+ position: number,
127
+ ): string | undefined {
128
+ let layerId = undefined;
129
+ let layerHash = undefined;
130
+
131
+ const layers = map.getStyle().layers;
132
+ let currentPosition = -1;
133
+ for (let i = 0; i < layers.length; i++) {
134
+ const layer = layers[i];
135
+ const metadata = layer.metadata as LayerMetadataSpecification | undefined;
136
+ if (metadata?.layerId !== layerId || metadata?.layerHash !== layerHash) {
137
+ currentPosition++;
138
+ if (currentPosition === position) {
139
+ return layer.id;
140
+ }
141
+
142
+ layerId = metadata?.layerId;
143
+ layerHash = metadata?.layerHash;
144
+ }
145
+ }
146
+ return undefined;
147
+ }
148
+
149
+ const UPDATABLE_PROPERTIES: (keyof MapContextBaseLayer)[] = [
150
+ "opacity",
151
+ "visibility",
152
+ "label",
153
+ "extras",
154
+ "version",
155
+ // "attributions", // currently, updating the attribution means recreating the source & layer
156
+ // TODO (when available) "zIndex"
157
+ ];
158
+
159
+ /**
160
+ * This generates a layer hash that stays consistent even if updatable properties change.
161
+ * @param layerModel
162
+ */
163
+ export function generateLayerHashWithoutUpdatableProps(
164
+ layerModel: MapContextLayer,
165
+ ) {
166
+ return getHash(layerModel, UPDATABLE_PROPERTIES);
167
+ }
168
+
169
+ /**
170
+ * Incremental update is possible only if certain properties are changed: opacity,
171
+ * visibility, zIndex, etc.
172
+ *
173
+ * Note: we assume that both layers are different versions of the same layer (this
174
+ * will not be checked again)
175
+ * @param oldLayer
176
+ * @param newLayer
177
+ * @return Returns `true` if the only properties changed are the updatable ones
178
+ */
179
+ export function canDoIncrementalUpdate(
180
+ oldLayer: MapContextLayer,
181
+ newLayer: MapContextLayer,
182
+ ): boolean {
183
+ const oldHash = generateLayerHashWithoutUpdatableProps(oldLayer);
184
+ const newHash = generateLayerHashWithoutUpdatableProps(newLayer);
185
+ // true if only updatable props have changed between the two versions of the layer
186
+ return oldHash === newHash;
90
187
  }
91
188
 
92
- export function generateLayerId(layerModel: MapContextLayer) {
93
- return getHash(layerModel, [
94
- "name",
95
- "style",
96
- "visibility",
97
- "opacity",
98
- "version",
99
- "extras",
100
- ]);
189
+ /**
190
+ * This simply generates a unique id
191
+ */
192
+ export function generateLayerId() {
193
+ return Math.floor(Math.random() * 1000000).toString(10);
194
+ }
195
+
196
+ /**
197
+ * Will apply generic properties to the layer; only changes the necessary
198
+ * properties to avoid updating the map too often
199
+ * @param map
200
+ * @param layer
201
+ * @param layerModel
202
+ * @param previousLayerModel
203
+ */
204
+ export function updateLayerProperties(
205
+ map: Map,
206
+ layer: LayerSpecification,
207
+ layerModel: MapContextLayer,
208
+ previousLayerModel: MapContextLayer,
209
+ ) {
210
+ function shouldApplyProperty(prop: keyof MapContextBaseLayer): boolean {
211
+ // if the new layer model does not define that property, skip it
212
+ // (setting or resetting it to a default value would be counter-intuitive)
213
+ if (!(prop in layerModel) || typeof layerModel[prop] === "undefined")
214
+ return false;
215
+
216
+ // if the value did not change in the new layer model, skip it
217
+ if (layerModel[prop] === previousLayerModel[prop]) {
218
+ return false;
219
+ }
220
+
221
+ // any other case: apply the property
222
+ return true;
223
+ }
224
+ const layerId = layer.id;
225
+ const layerType = layer.type;
226
+ if (shouldApplyProperty("visibility")) {
227
+ map.setLayoutProperty(
228
+ layerId,
229
+ "visibility",
230
+ layerModel.visibility === false ? "none" : "visible",
231
+ );
232
+ }
233
+ if (shouldApplyProperty("opacity")) {
234
+ getOpacityPaintPropNames(layerType).forEach((paintProp) => {
235
+ map.setPaintProperty(layerId, paintProp, layerModel.opacity ?? 1);
236
+ });
237
+ }
238
+ // TODO: z-index
101
239
  }