@geospatial-sdk/maplibre 0.0.5-dev.49 → 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,21 +1,55 @@
1
- import { Map } from "maplibre-gl";
2
- import { Dataset, LayerContextWithStyle, LayerSpecificationWithSource } from "../maplibre.models.js";
1
+ import type { LayerSpecification, Map } from "maplibre-gl";
2
+ import { Dataset, LayerContextWithStyle, LayerMetadataSpecification, LayerSpecificationWithSource } from "../maplibre.models.js";
3
3
  import { FeatureCollection, Geometry } from "geojson";
4
4
  import { MapContextLayer } from "@geospatial-sdk/core";
5
- /**
6
- * Remove all layers from a given source in the map.
7
- * @param map
8
- * @param sourceId
9
- */
10
- export declare function removeLayersFromSource(map: Map, sourceId: string): void;
11
5
  /**
12
6
  * Create a Maplibre source and layers from a GeoJSON MapContextLayer and its style.
13
7
  * @param layerModel
14
8
  * @param geojson
15
- * @param sourcePosition
9
+ * @param metadata
10
+ */
11
+ export declare function createDatasetFromGeoJsonLayer(layerModel: LayerContextWithStyle, geojson: FeatureCollection<Geometry | null> | string, metadata: LayerMetadataSpecification): Dataset;
12
+ export declare function getLayersFromContextLayer(map: Map, layerModel: MapContextLayer): LayerSpecificationWithSource[];
13
+ /**
14
+ * This returns all MapLibre layers that correspond to a position in a MapContext
15
+ * @param map
16
+ * @param position
16
17
  */
17
- export declare function createDatasetFromGeoJsonLayer(layerModel: LayerContextWithStyle, geojson: FeatureCollection<Geometry | null> | string, sourcePosition: number): Dataset;
18
18
  export declare function getLayersAtPosition(map: Map, position: number): LayerSpecificationWithSource[];
19
- export declare function getBeforeId(map: Map, position: number): string | undefined;
20
- export declare function generateLayerId(layerModel: MapContextLayer): string;
19
+ /**
20
+ * This returns the id of the first MapLibre layer that corresponds to the given MapContext position;
21
+ * used as a beforeId for adding/moving layers
22
+ * @param map
23
+ * @param position
24
+ */
25
+ export declare function getFirstLayerIdAtPosition(map: Map, position: number): string | undefined;
26
+ /**
27
+ * This generates a layer hash that stays consistent even if updatable properties change.
28
+ * @param layerModel
29
+ */
30
+ export declare function generateLayerHashWithoutUpdatableProps(layerModel: MapContextLayer): string;
31
+ /**
32
+ * Incremental update is possible only if certain properties are changed: opacity,
33
+ * visibility, zIndex, etc.
34
+ *
35
+ * Note: we assume that both layers are different versions of the same layer (this
36
+ * will not be checked again)
37
+ * @param oldLayer
38
+ * @param newLayer
39
+ * @return Returns `true` if the only properties changed are the updatable ones
40
+ */
41
+ export declare function canDoIncrementalUpdate(oldLayer: MapContextLayer, newLayer: MapContextLayer): boolean;
42
+ /**
43
+ * This simply generates a unique id
44
+ */
45
+ export declare function generateLayerId(): string;
46
+ /**
47
+ * Will apply generic properties to the layer; only changes the necessary
48
+ * properties to avoid updating the map too often
49
+ * @param map
50
+ * @param layer
51
+ * @param layerModel
52
+ * @param previousLayerModel
53
+ */
54
+ export declare function updateLayerProperties(map: Map, layer: LayerSpecification, layerModel: MapContextLayer, previousLayerModel: MapContextLayer): void;
21
55
  //# sourceMappingURL=map.helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"map.helpers.d.ts","sourceRoot":"","sources":["../../lib/helpers/map.helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAsB,MAAM,aAAa,CAAC;AACtD,OAAO,EACL,OAAO,EACP,qBAAqB,EAErB,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGtD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,QAWhE;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,qBAAqB,EACjC,OAAO,EAAE,iBAAiB,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,MAAM,EACpD,cAAc,EAAE,MAAM,GACrB,OAAO,CAuBT;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,GACf,4BAA4B,EAAE,CAUhC;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAS1E;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,eAAe,UAS1D"}
1
+ {"version":3,"file":"map.helpers.d.ts","sourceRoot":"","sources":["../../lib/helpers/map.helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAsB,MAAM,aAAa,CAAC;AAC/E,OAAO,EACL,OAAO,EACP,qBAAqB,EACrB,0BAA0B,EAC1B,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGtD,OAAO,EAAuB,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAW5E;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,qBAAqB,EACjC,OAAO,EAAE,iBAAiB,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,MAAM,EACpD,QAAQ,EAAE,0BAA0B,GACnC,OAAO,CA+BT;AAED,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,eAAe,GAC1B,4BAA4B,EAAE,CAkBhC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,GACf,4BAA4B,EAAE,CAoBhC;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CAoBpB;AAYD;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,eAAe,UAG5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAKT;AAED;;GAEG;AACH,wBAAgB,eAAe,WAE9B;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,kBAAkB,EACzB,UAAU,EAAE,eAAe,EAC3B,kBAAkB,EAAE,eAAe,QA+BpC"}
@@ -1,37 +1,37 @@
1
1
  import { contextStyleToMaplibreLayers } from "./style.helpers.js";
2
2
  import { getHash } from "@geospatial-sdk/core/dist/utils/hash.js";
3
- /**
4
- * Remove all layers from a given source in the map.
5
- * @param map
6
- * @param sourceId
7
- */
8
- export function removeLayersFromSource(map, sourceId) {
9
- const layers = map.getStyle().layers;
10
- const layersWithSource = layers.filter((layer) => layer.type !== "background");
11
- const layerIds = layersWithSource
12
- .filter((layer) => layer.hasOwnProperty("source") && layer.source === sourceId)
13
- .map((layer) => layer.id);
14
- layerIds.forEach((layer) => map.removeLayer(layer));
3
+ function getOpacityPaintPropNames(layerType) {
4
+ switch (layerType) {
5
+ case "circle":
6
+ return ["circle-opacity", "circle-stroke-opacity"];
7
+ default:
8
+ return [`${layerType}-opacity`];
9
+ }
15
10
  }
16
11
  /**
17
12
  * Create a Maplibre source and layers from a GeoJSON MapContextLayer and its style.
18
13
  * @param layerModel
19
14
  * @param geojson
20
- * @param sourcePosition
15
+ * @param metadata
21
16
  */
22
- export function createDatasetFromGeoJsonLayer(layerModel, geojson, sourcePosition) {
23
- const sourceId = generateLayerId(layerModel);
17
+ export function createDatasetFromGeoJsonLayer(layerModel, geojson, metadata) {
18
+ const sourceId = generateLayerId();
24
19
  const partialLayers = contextStyleToMaplibreLayers(layerModel.style);
25
20
  const layers = partialLayers.map((layer) => ({
26
21
  ...layer,
27
22
  id: `${sourceId}-${layer.type}`,
28
23
  source: sourceId,
24
+ paint: {
25
+ ...layer.paint,
26
+ ...getOpacityPaintPropNames(layer.type).reduce((acc, prop) => ({
27
+ ...acc,
28
+ [prop]: layerModel.opacity ?? 1,
29
+ }), {}),
30
+ },
29
31
  layout: {
30
32
  visibility: layerModel.visibility === false ? "none" : "visible",
31
33
  },
32
- metadata: {
33
- sourcePosition,
34
- },
34
+ metadata,
35
35
  }));
36
36
  return {
37
37
  sources: {
@@ -43,26 +43,143 @@ export function createDatasetFromGeoJsonLayer(layerModel, geojson, sourcePositio
43
43
  layers,
44
44
  };
45
45
  }
46
+ export function getLayersFromContextLayer(map, layerModel) {
47
+ const layerId = layerModel.id;
48
+ const layerHash = generateLayerHashWithoutUpdatableProps(layerModel);
49
+ const layers = map.getStyle().layers;
50
+ const result = [];
51
+ for (let i = 0; i < layers.length; i++) {
52
+ const layer = layers[i];
53
+ const metadata = layer.metadata;
54
+ if (layerId !== undefined) {
55
+ if (metadata?.layerId === layerId) {
56
+ result.push(layer);
57
+ }
58
+ }
59
+ else if (metadata?.layerHash === layerHash) {
60
+ result.push(layer);
61
+ }
62
+ }
63
+ return result;
64
+ }
65
+ /**
66
+ * This returns all MapLibre layers that correspond to a position in a MapContext
67
+ * @param map
68
+ * @param position
69
+ */
46
70
  export function getLayersAtPosition(map, position) {
71
+ let layerId = undefined;
72
+ let layerHash = undefined;
73
+ const result = [];
47
74
  const layers = map.getStyle().layers;
48
- const layersWithSource = layers.filter((layer) => layer.type !== "background");
49
- return layersWithSource.filter((layer) => layer.metadata?.sourcePosition ===
50
- position);
75
+ let currentPosition = -1;
76
+ for (let i = 0; i < layers.length; i++) {
77
+ const layer = layers[i];
78
+ const metadata = layer.metadata;
79
+ if (metadata?.layerId !== layerId || metadata?.layerHash !== layerHash) {
80
+ currentPosition++;
81
+ layerId = metadata?.layerId;
82
+ layerHash = metadata?.layerHash;
83
+ }
84
+ if (currentPosition === position) {
85
+ result.push(layer);
86
+ }
87
+ }
88
+ return result;
51
89
  }
52
- export function getBeforeId(map, position) {
53
- const beforeLayer = map
54
- .getStyle()
55
- .layers.find((layer) => layer.metadata.sourcePosition ===
56
- position + 1);
57
- return beforeLayer ? beforeLayer.id : undefined;
90
+ /**
91
+ * This returns the id of the first MapLibre layer that corresponds to the given MapContext position;
92
+ * used as a beforeId for adding/moving layers
93
+ * @param map
94
+ * @param position
95
+ */
96
+ export function getFirstLayerIdAtPosition(map, position) {
97
+ let layerId = undefined;
98
+ let layerHash = undefined;
99
+ const layers = map.getStyle().layers;
100
+ let currentPosition = -1;
101
+ for (let i = 0; i < layers.length; i++) {
102
+ const layer = layers[i];
103
+ const metadata = layer.metadata;
104
+ if (metadata?.layerId !== layerId || metadata?.layerHash !== layerHash) {
105
+ currentPosition++;
106
+ if (currentPosition === position) {
107
+ return layer.id;
108
+ }
109
+ layerId = metadata?.layerId;
110
+ layerHash = metadata?.layerHash;
111
+ }
112
+ }
113
+ return undefined;
58
114
  }
59
- export function generateLayerId(layerModel) {
60
- return getHash(layerModel, [
61
- "name",
62
- "style",
63
- "visibility",
64
- "opacity",
65
- "version",
66
- "extras",
67
- ]);
115
+ const UPDATABLE_PROPERTIES = [
116
+ "opacity",
117
+ "visibility",
118
+ "label",
119
+ "extras",
120
+ "version",
121
+ // "attributions", // currently, updating the attribution means recreating the source & layer
122
+ // TODO (when available) "zIndex"
123
+ ];
124
+ /**
125
+ * This generates a layer hash that stays consistent even if updatable properties change.
126
+ * @param layerModel
127
+ */
128
+ export function generateLayerHashWithoutUpdatableProps(layerModel) {
129
+ return getHash(layerModel, UPDATABLE_PROPERTIES);
130
+ }
131
+ /**
132
+ * Incremental update is possible only if certain properties are changed: opacity,
133
+ * visibility, zIndex, etc.
134
+ *
135
+ * Note: we assume that both layers are different versions of the same layer (this
136
+ * will not be checked again)
137
+ * @param oldLayer
138
+ * @param newLayer
139
+ * @return Returns `true` if the only properties changed are the updatable ones
140
+ */
141
+ export function canDoIncrementalUpdate(oldLayer, newLayer) {
142
+ const oldHash = generateLayerHashWithoutUpdatableProps(oldLayer);
143
+ const newHash = generateLayerHashWithoutUpdatableProps(newLayer);
144
+ // true if only updatable props have changed between the two versions of the layer
145
+ return oldHash === newHash;
146
+ }
147
+ /**
148
+ * This simply generates a unique id
149
+ */
150
+ export function generateLayerId() {
151
+ return Math.floor(Math.random() * 1000000).toString(10);
152
+ }
153
+ /**
154
+ * Will apply generic properties to the layer; only changes the necessary
155
+ * properties to avoid updating the map too often
156
+ * @param map
157
+ * @param layer
158
+ * @param layerModel
159
+ * @param previousLayerModel
160
+ */
161
+ export function updateLayerProperties(map, layer, layerModel, previousLayerModel) {
162
+ function shouldApplyProperty(prop) {
163
+ // if the new layer model does not define that property, skip it
164
+ // (setting or resetting it to a default value would be counter-intuitive)
165
+ if (!(prop in layerModel) || typeof layerModel[prop] === "undefined")
166
+ return false;
167
+ // if the value did not change in the new layer model, skip it
168
+ if (layerModel[prop] === previousLayerModel[prop]) {
169
+ return false;
170
+ }
171
+ // any other case: apply the property
172
+ return true;
173
+ }
174
+ const layerId = layer.id;
175
+ const layerType = layer.type;
176
+ if (shouldApplyProperty("visibility")) {
177
+ map.setLayoutProperty(layerId, "visibility", layerModel.visibility === false ? "none" : "visible");
178
+ }
179
+ if (shouldApplyProperty("opacity")) {
180
+ getOpacityPaintPropNames(layerType).forEach((paintProp) => {
181
+ map.setPaintProperty(layerId, paintProp, layerModel.opacity ?? 1);
182
+ });
183
+ }
184
+ // TODO: z-index
68
185
  }
@@ -1,9 +1,18 @@
1
- import { MapContextDiff } from "@geospatial-sdk/core";
2
- import { Map } from "maplibre-gl";
1
+ import { MapContextDiff, MapContextLayer } from "@geospatial-sdk/core";
2
+ import type { Map } from "maplibre-gl";
3
+ /**
4
+ * This will either update the layers in the map or recreate them;
5
+ * the returned promise resolves when the update is done
6
+ * @param map
7
+ * @param layerModel
8
+ * @param previousLayerModel
9
+ * @param layerPosition
10
+ */
11
+ export declare function updateLayerInMap(map: Map, layerModel: MapContextLayer, previousLayerModel: MapContextLayer, layerPosition: number): Promise<void>;
3
12
  /**
4
13
  * Apply a context diff to an MapLibre map
5
14
  * @param map
6
15
  * @param contextDiff
7
16
  */
8
- export declare function applyContextDiffToMap(map: Map, contextDiff: MapContextDiff): Promise<void>;
17
+ export declare function applyContextDiffToMap(map: Map, contextDiff: MapContextDiff): Promise<Map>;
9
18
  //# sourceMappingURL=apply-context-diff.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"apply-context-diff.d.ts","sourceRoot":"","sources":["../../lib/map/apply-context-diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AASlC;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,cAAc,GAC1B,OAAO,CAAC,IAAI,CAAC,CAsEf"}
1
+ {"version":3,"file":"apply-context-diff.d.ts","sourceRoot":"","sources":["../../lib/map/apply-context-diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAUvC;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,eAAe,EAC3B,kBAAkB,EAAE,eAAe,EACnC,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,cAAc,GAC1B,OAAO,CAAC,GAAG,CAAC,CAgGd"}
@@ -1,5 +1,43 @@
1
1
  import { createLayer } from "./create-map.js";
2
- import { generateLayerId, getBeforeId, getLayersAtPosition, removeLayersFromSource, } from "../helpers/map.helpers.js";
2
+ import { canDoIncrementalUpdate, getFirstLayerIdAtPosition, getLayersAtPosition, getLayersFromContextLayer, updateLayerProperties, } from "../helpers/map.helpers.js";
3
+ /**
4
+ * This will either update the layers in the map or recreate them;
5
+ * the returned promise resolves when the update is done
6
+ * @param map
7
+ * @param layerModel
8
+ * @param previousLayerModel
9
+ * @param layerPosition
10
+ */
11
+ export async function updateLayerInMap(map, layerModel, previousLayerModel, layerPosition) {
12
+ // if an incremental update is possible, do it to avoid costly layer recreation
13
+ if (canDoIncrementalUpdate(previousLayerModel, layerModel)) {
14
+ // we can find the existing layers by using the hash or id of the layerModel
15
+ const mlUpdatedLayers = getLayersFromContextLayer(map, layerModel);
16
+ for (const layer of mlUpdatedLayers) {
17
+ updateLayerProperties(map, layer, layerModel, previousLayerModel);
18
+ }
19
+ return;
20
+ }
21
+ const mlLayersToRemove = getLayersAtPosition(map, layerPosition);
22
+ const sourcesToRemove = [];
23
+ for (const layer of mlLayersToRemove) {
24
+ if (layer.source && !sourcesToRemove.includes(layer.source)) {
25
+ sourcesToRemove.push(layer.source);
26
+ }
27
+ map.removeLayer(layer.id);
28
+ }
29
+ for (const sourceId of sourcesToRemove) {
30
+ map.removeSource(sourceId);
31
+ }
32
+ const styleDiff = await createLayer(layerModel);
33
+ if (!styleDiff)
34
+ return;
35
+ const beforeId = getFirstLayerIdAtPosition(map, layerPosition);
36
+ Object.keys(styleDiff.sources).forEach((sourceId) => map.addSource(sourceId, styleDiff.sources[sourceId]));
37
+ styleDiff.layers.map((layer) => {
38
+ map.addLayer(layer, beforeId);
39
+ });
40
+ }
3
41
  /**
4
42
  * Apply a context diff to an MapLibre map
5
43
  * @param map
@@ -10,7 +48,7 @@ export async function applyContextDiffToMap(map, contextDiff) {
10
48
  if (contextDiff.layersRemoved.length > 0) {
11
49
  const removed = contextDiff.layersRemoved.sort((a, b) => b.position - a.position);
12
50
  for (const layerRemoved of removed) {
13
- const mlLayers = getLayersAtPosition(map, layerRemoved.position);
51
+ const mlLayers = getLayersFromContextLayer(map, layerRemoved.layer);
14
52
  if (mlLayers.length === 0) {
15
53
  console.warn(`[Warning] applyContextDiffToMap: no layer found at position ${layerRemoved.position} to remove.`);
16
54
  continue;
@@ -23,26 +61,41 @@ export async function applyContextDiffToMap(map, contextDiff) {
23
61
  }
24
62
  }
25
63
  // insert added layers
26
- const newLayers = await Promise.all(contextDiff.layersAdded.map((layerAdded) => createLayer(layerAdded.layer, layerAdded.position)));
64
+ const newLayers = await Promise.all(contextDiff.layersAdded.map((layerAdded) => createLayer(layerAdded.layer)));
27
65
  newLayers.forEach((style, index) => {
66
+ if (!style)
67
+ return;
28
68
  const position = contextDiff.layersAdded[index].position;
29
- const beforeId = getBeforeId(map, position);
69
+ const beforeId = getFirstLayerIdAtPosition(map, position);
30
70
  Object.keys(style.sources).forEach((sourceId) => map.addSource(sourceId, style.sources[sourceId]));
31
71
  style.layers.map((layer) => {
32
72
  map.addLayer(layer, beforeId);
33
73
  });
34
74
  });
75
+ // handle reordered layers (sorted by ascending new position)
76
+ if (contextDiff.layersReordered.length > 0) {
77
+ const reordered = contextDiff.layersReordered.sort((a, b) => a.newPosition - b.newPosition);
78
+ // collect all layers to be moved
79
+ const mlLayersToMove = reordered.map((layerReordered) => getLayersFromContextLayer(map, layerReordered.layer));
80
+ // move layers
81
+ for (let i = 0; i < reordered.length; i++) {
82
+ const layerReordered = reordered[i];
83
+ const mlLayers = mlLayersToMove[i];
84
+ const beforeId = getFirstLayerIdAtPosition(map, layerReordered.newPosition + 1);
85
+ if (mlLayers[0].id === beforeId) {
86
+ // layer is already at the right position
87
+ continue;
88
+ }
89
+ // then we add the moved the layer to its new position
90
+ for (const layer of mlLayers) {
91
+ map.moveLayer(layer.id, beforeId);
92
+ }
93
+ }
94
+ }
35
95
  // recreate changed layers
36
96
  for (const layerChanged of contextDiff.layersChanged) {
37
- const { layer, position } = layerChanged;
38
- const sourceId = generateLayerId(layer);
39
- removeLayersFromSource(map, sourceId);
40
- const beforeId = getBeforeId(map, position);
41
- createLayer(layer, position).then((styleDiff) => {
42
- styleDiff.layers.map((layer) => {
43
- map.addLayer(layer, beforeId);
44
- });
45
- });
97
+ const { layer, previousLayer, position } = layerChanged;
98
+ await updateLayerInMap(map, layer, previousLayer, position);
46
99
  }
47
100
  if (typeof contextDiff.viewChanges !== "undefined") {
48
101
  const { viewChanges } = contextDiff;
@@ -57,4 +110,5 @@ export async function applyContextDiffToMap(map, contextDiff) {
57
110
  });
58
111
  }
59
112
  }
113
+ return map;
60
114
  }
@@ -1,11 +1,15 @@
1
1
  import { MapContext, MapContextLayer } from "@geospatial-sdk/core";
2
2
  import { Map, MapOptions } from "maplibre-gl";
3
3
  import { PartialStyleSpecification } from "../maplibre.models.js";
4
- export declare function createLayer(layerModel: MapContextLayer, sourcePosition: number): Promise<PartialStyleSpecification>;
5
4
  /**
6
- * Create an Maplibre map from a context; optionally specify a target (root element) for the map
5
+ * Create a Maplibre layer from a MapContextLayer. Returns null if the layer could not be created.
6
+ * @param layerModel
7
+ */
8
+ export declare function createLayer(layerModel: MapContextLayer): Promise<PartialStyleSpecification | null>;
9
+ /**
10
+ * Create a Maplibre map from a context; map options need to be provided
7
11
  * @param context
8
- * @param target
12
+ * @param mapOptions
9
13
  */
10
14
  export declare function createMapFromContext(context: MapContext, mapOptions: MapOptions): Promise<Map>;
11
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"create-map.d.ts","sourceRoot":"","sources":["../../lib/map/create-map.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,eAAe,EAGhB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAEL,GAAG,EACH,UAAU,EAEX,MAAM,aAAa,CAAC;AAWrB,OAAO,EAAW,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAO3E,wBAAsB,WAAW,CAC/B,UAAU,EAAE,eAAe,EAC3B,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,yBAAyB,CAAC,CAgGpC;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,GAAG,CAAC,CAGd;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,GAAG,CAAC,CAoBd"}
1
+ {"version":3,"file":"create-map.d.ts","sourceRoot":"","sources":["../../lib/map/create-map.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,eAAe,EAGhB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAsB,GAAG,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAYlE,OAAO,EAEL,yBAAyB,EAC1B,MAAM,uBAAuB,CAAC;AAO/B;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,eAAe,GAC1B,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAiI3C;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,GAAG,CAAC,CAGd;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,GAAG,CAAC,CAqBd"}
@@ -1,16 +1,23 @@
1
1
  import { removeSearchParams, } from "@geospatial-sdk/core";
2
- import { Map, } from "maplibre-gl";
2
+ import { Map } from "maplibre-gl";
3
3
  import { OgcApiEndpoint, WfsEndpoint, WmsEndpoint, } from "@camptocamp/ogc-client";
4
- import { createDatasetFromGeoJsonLayer, generateLayerId, } from "../helpers/map.helpers.js";
4
+ import { createDatasetFromGeoJsonLayer, generateLayerHashWithoutUpdatableProps, generateLayerId, } from "../helpers/map.helpers.js";
5
5
  const featureCollection = {
6
6
  type: "FeatureCollection",
7
7
  features: [],
8
8
  };
9
- export async function createLayer(layerModel, sourcePosition) {
9
+ /**
10
+ * Create a Maplibre layer from a MapContextLayer. Returns null if the layer could not be created.
11
+ * @param layerModel
12
+ */
13
+ export async function createLayer(layerModel) {
10
14
  const { type } = layerModel;
15
+ const metadata = "id" in layerModel
16
+ ? { layerId: layerModel.id }
17
+ : { layerHash: generateLayerHashWithoutUpdatableProps(layerModel) };
18
+ const layerId = generateLayerId();
11
19
  switch (type) {
12
20
  case "wms": {
13
- const layerId = generateLayerId(layerModel);
14
21
  const sourceId = layerId;
15
22
  const endpoint = await new WmsEndpoint(layerModel.url).isReady();
16
23
  let url = endpoint.getMapUrl([layerModel.name], {
@@ -22,7 +29,7 @@ export async function createLayer(layerModel, sourcePosition) {
22
29
  });
23
30
  url = removeSearchParams(url, ["bbox"]);
24
31
  url = `${url.toString()}&BBOX={bbox-epsg-3857}`;
25
- const dataset = {
32
+ return {
26
33
  sources: {
27
34
  [sourceId]: {
28
35
  type: "raster",
@@ -35,14 +42,16 @@ export async function createLayer(layerModel, sourcePosition) {
35
42
  id: layerId,
36
43
  type: "raster",
37
44
  source: sourceId,
38
- paint: {},
39
- metadata: {
40
- sourcePosition,
45
+ paint: {
46
+ "raster-opacity": layerModel.opacity ?? 1,
41
47
  },
48
+ layout: {
49
+ visibility: layerModel.visibility === false ? "none" : "visible",
50
+ },
51
+ metadata,
42
52
  },
43
53
  ],
44
54
  };
45
- return dataset;
46
55
  }
47
56
  case "wfs": {
48
57
  const entryPoint = await new WfsEndpoint(layerModel.url).isReady();
@@ -50,7 +59,7 @@ export async function createLayer(layerModel, sourcePosition) {
50
59
  asJson: true,
51
60
  outputCrs: "EPSG:4326",
52
61
  });
53
- return createDatasetFromGeoJsonLayer(layerModel, url, sourcePosition);
62
+ return createDatasetFromGeoJsonLayer(layerModel, url, metadata);
54
63
  }
55
64
  case "geojson": {
56
65
  let geojson;
@@ -72,33 +81,63 @@ export async function createLayer(layerModel, sourcePosition) {
72
81
  geojson = data;
73
82
  }
74
83
  }
75
- return createDatasetFromGeoJsonLayer(layerModel, geojson, sourcePosition);
84
+ return createDatasetFromGeoJsonLayer(layerModel, geojson, metadata);
76
85
  }
77
86
  case "ogcapi": {
78
87
  const ogcEndpoint = new OgcApiEndpoint(layerModel.url);
79
- let layerUrl;
80
88
  if (layerModel.useTiles) {
81
89
  console.warn("[Warning] OGC API - Tiles not yet implemented.");
90
+ return null;
82
91
  }
83
- else {
84
- layerUrl = await ogcEndpoint.getCollectionItemsUrl(layerModel.collection, { ...layerModel.options, asJson: true });
85
- return createDatasetFromGeoJsonLayer(layerModel, layerUrl, sourcePosition);
86
- }
87
- break;
92
+ const layerUrl = await ogcEndpoint.getCollectionItemsUrl(layerModel.collection, { ...layerModel.options, asJson: true });
93
+ return createDatasetFromGeoJsonLayer(layerModel, layerUrl, metadata);
88
94
  }
89
95
  case "maplibre-style": {
90
96
  console.warn("[Warning] Maplibre style - Not yet fully implemented.");
91
97
  const style = await fetch(layerModel.styleUrl).then((res) => res.json());
92
- style.layers?.forEach((layer) => (layer.metadata = { sourcePosition }));
98
+ style.layers?.forEach((layer) => (layer.metadata = metadata));
93
99
  return style;
94
100
  }
101
+ case "xyz": {
102
+ const sourceId = layerId;
103
+ return {
104
+ sources: {
105
+ [sourceId]: {
106
+ type: "raster",
107
+ tiles: [layerModel.url],
108
+ tileSize: 256,
109
+ },
110
+ },
111
+ layers: [
112
+ {
113
+ id: layerId,
114
+ type: "raster",
115
+ source: sourceId,
116
+ paint: {
117
+ "raster-opacity": layerModel.opacity ?? 1,
118
+ },
119
+ layout: {
120
+ visibility: layerModel.visibility === false ? "none" : "visible",
121
+ },
122
+ metadata,
123
+ },
124
+ ],
125
+ };
126
+ }
127
+ case "wmts": {
128
+ console.warn(`WMTS layers are not yet supported in Maplibre`, layerModel);
129
+ return null;
130
+ }
131
+ default: {
132
+ console.error(`Layer could not be created`, layerModel);
133
+ return null;
134
+ }
95
135
  }
96
- return {};
97
136
  }
98
137
  /**
99
- * Create an Maplibre map from a context; optionally specify a target (root element) for the map
138
+ * Create a Maplibre map from a context; map options need to be provided
100
139
  * @param context
101
- * @param target
140
+ * @param mapOptions
102
141
  */
103
142
  export async function createMapFromContext(context, mapOptions) {
104
143
  const map = new Map(mapOptions);
@@ -114,7 +153,9 @@ export async function resetMapFromContext(map, context) {
114
153
  map.setCenter(context.view.center);
115
154
  for (let i = 0; i < context.layers.length; i++) {
116
155
  const layerModel = context.layers[i];
117
- const partialMLStyle = await createLayer(layerModel, i);
156
+ const partialMLStyle = await createLayer(layerModel);
157
+ if (!partialMLStyle)
158
+ continue;
118
159
  if (partialMLStyle.glyphs) {
119
160
  map.setGlyphs(partialMLStyle.glyphs);
120
161
  }
@@ -2,7 +2,8 @@ import { BackgroundLayerSpecification, LayerSpecification, StyleSpecification }
2
2
  import { MapContextLayerGeojson, MapContextLayerOgcApi, MapContextLayerWfs } from "@geospatial-sdk/core";
3
3
  export type LayerSpecificationWithSource = Exclude<LayerSpecification, BackgroundLayerSpecification>;
4
4
  export interface LayerMetadataSpecification {
5
- sourcePosition: number;
5
+ layerId?: number | string;
6
+ layerHash?: string;
6
7
  }
7
8
  export type LayerContextWithStyle = MapContextLayerWfs | MapContextLayerOgcApi | MapContextLayerGeojson;
8
9
  export type Dataset = Pick<StyleSpecification, "sources" | "layers">;
@@ -1 +1 @@
1
- {"version":3,"file":"maplibre.models.d.ts","sourceRoot":"","sources":["../lib/maplibre.models.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,MAAM,4BAA4B,GAAG,OAAO,CAChD,kBAAkB,EAClB,4BAA4B,CAC7B,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,qBAAqB,GAC7B,kBAAkB,GAClB,qBAAqB,GACrB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,QAAQ,CAAC,CAAC;AAErE,MAAM,MAAM,yBAAyB,GAAG,OAAO,GAAG;IAChD,MAAM,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;CACvC,CAAC"}
1
+ {"version":3,"file":"maplibre.models.d.ts","sourceRoot":"","sources":["../lib/maplibre.models.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,MAAM,4BAA4B,GAAG,OAAO,CAChD,kBAAkB,EAClB,4BAA4B,CAC7B,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,qBAAqB,GAC7B,kBAAkB,GAClB,qBAAqB,GACrB,sBAAsB,CAAC;AAE3B,MAAM,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,QAAQ,CAAC,CAAC;AAErE,MAAM,MAAM,yBAAyB,GAAG,OAAO,GAAG;IAChD,MAAM,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;CACvC,CAAC"}