@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.
- package/dist/helpers/map.helpers.d.ts +46 -12
- package/dist/helpers/map.helpers.d.ts.map +1 -1
- package/dist/helpers/map.helpers.js +153 -36
- package/dist/map/apply-context-diff.d.ts +12 -3
- package/dist/map/apply-context-diff.d.ts.map +1 -1
- package/dist/map/apply-context-diff.js +67 -13
- package/dist/map/create-map.d.ts +7 -3
- package/dist/map/create-map.d.ts.map +1 -1
- package/dist/map/create-map.js +63 -22
- package/dist/maplibre.models.d.ts +2 -1
- package/dist/maplibre.models.d.ts.map +1 -1
- package/lib/helpers/map.helpers.ts +188 -50
- package/lib/map/apply-context-diff.test.ts +476 -58
- package/lib/map/apply-context-diff.ts +95 -22
- package/lib/map/create-map.test.ts +158 -63
- package/lib/map/create-map.ts +70 -34
- package/lib/maplibre.models.ts +2 -1
- package/package.json +3 -3
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
26
|
+
* @param metadata
|
|
36
27
|
*/
|
|
37
28
|
export function createDatasetFromGeoJsonLayer(
|
|
38
29
|
layerModel: LayerContextWithStyle,
|
|
39
30
|
geojson: FeatureCollection<Geometry | null> | string,
|
|
40
|
-
|
|
31
|
+
metadata: LayerMetadataSpecification,
|
|
41
32
|
): Dataset {
|
|
42
|
-
const sourceId = generateLayerId(
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
(
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
}
|