@geospatial-sdk/openlayers 0.0.5-dev.10 → 0.0.5-dev.12

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.
@@ -5,5 +5,5 @@ import { MapContextDiff } from "@geospatial-sdk/core";
5
5
  * @param map
6
6
  * @param contextDiff
7
7
  */
8
- export declare function applyContextDiffToMap(map: Map, contextDiff: MapContextDiff): Map;
8
+ export declare function applyContextDiffToMap(map: Map, contextDiff: MapContextDiff): Promise<Map>;
9
9
  //# 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,GAAG,MAAM,QAAQ,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,cAAc,GAC1B,GAAG,CA6CL"}
1
+ {"version":3,"file":"apply-context-diff.d.ts","sourceRoot":"","sources":["../../lib/map/apply-context-diff.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,QAAQ,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACvC,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,cAAc,GAC5B,OAAO,CAAC,GAAG,CAAC,CAmDd"}
@@ -1,3 +1,12 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  import { createLayer } from "./create-map";
2
11
  /**
3
12
  * Apply a context diff to an OpenLayers map
@@ -5,39 +14,44 @@ import { createLayer } from "./create-map";
5
14
  * @param contextDiff
6
15
  */
7
16
  export function applyContextDiffToMap(map, contextDiff) {
8
- const layers = map.getLayers();
9
- // removed layers (sorted by descending position)
10
- if (contextDiff.layersRemoved.length > 0) {
11
- const removed = contextDiff.layersRemoved.sort((a, b) => b.position - a.position);
12
- for (const layerRemoved of removed) {
13
- layers.item(layerRemoved.position).dispose();
14
- layers.removeAt(layerRemoved.position);
17
+ return __awaiter(this, void 0, void 0, function* () {
18
+ const layers = map.getLayers();
19
+ // removed layers (sorted by descending position)
20
+ if (contextDiff.layersRemoved.length > 0) {
21
+ const removed = contextDiff.layersRemoved.sort((a, b) => b.position - a.position);
22
+ for (const layerRemoved of removed) {
23
+ layers.item(layerRemoved.position).dispose();
24
+ layers.removeAt(layerRemoved.position);
25
+ }
15
26
  }
16
- }
17
- // insert added layers
18
- for (const layerAdded of contextDiff.layersAdded) {
19
- const layer = createLayer(layerAdded.layer);
20
- if (layerAdded.position >= layers.getLength()) {
21
- layers.push(layer);
27
+ // insert added layers
28
+ const newLayers = yield Promise.all(contextDiff.layersAdded.map((layerAdded) => createLayer(layerAdded.layer)));
29
+ newLayers.forEach((layer, index) => {
30
+ const position = contextDiff.layersAdded[index].position;
31
+ if (position >= layers.getLength()) {
32
+ layers.push(layer);
33
+ }
34
+ else {
35
+ layers.insertAt(position, layer);
36
+ }
37
+ });
38
+ // move reordered layers (sorted by ascending new position)
39
+ if (contextDiff.layersReordered.length > 0) {
40
+ const reordered = contextDiff.layersReordered.sort((a, b) => a.newPosition - b.newPosition);
41
+ const olLayers = reordered.map((layer) => layers.item(layer.previousPosition));
42
+ const layersArray = layers.getArray();
43
+ for (let i = 0; i < reordered.length; i++) {
44
+ layersArray[reordered[i].newPosition] = olLayers[i];
45
+ }
46
+ map.setLayers([...layersArray]);
22
47
  }
23
- else {
24
- layers.insertAt(layerAdded.position, layer);
48
+ // recreate changed layers
49
+ for (const layerChanged of contextDiff.layersChanged) {
50
+ layers.item(layerChanged.position).dispose();
51
+ createLayer(layerChanged.layer).then(layer => {
52
+ layers.setAt(layerChanged.position, layer);
53
+ });
25
54
  }
26
- }
27
- // move reordered layers (sorted by ascending new position)
28
- if (contextDiff.layersReordered.length > 0) {
29
- const reordered = contextDiff.layersReordered.sort((a, b) => a.newPosition - b.newPosition);
30
- const olLayers = reordered.map((layer) => layers.item(layer.previousPosition));
31
- const layersArray = layers.getArray();
32
- for (let i = 0; i < reordered.length; i++) {
33
- layersArray[reordered[i].newPosition] = olLayers[i];
34
- }
35
- map.setLayers([...layersArray]);
36
- }
37
- // recreate changed layers
38
- for (const layerChanged of contextDiff.layersChanged) {
39
- layers.item(layerChanged.position).dispose();
40
- layers.setAt(layerChanged.position, createLayer(layerChanged.layer));
41
- }
42
- return map;
55
+ return map;
56
+ });
43
57
  }
@@ -2,18 +2,18 @@ import { MapContext, MapContextLayer, MapContextView } from "@geospatial-sdk/cor
2
2
  import Map from "ol/Map";
3
3
  import View from "ol/View";
4
4
  import Layer from "ol/layer/Layer";
5
- export declare function createLayer(layerModel: MapContextLayer): Layer;
5
+ export declare function createLayer(layerModel: MapContextLayer): Promise<Layer>;
6
6
  export declare function createView(viewModel: MapContextView, map: Map): View;
7
7
  /**
8
8
  * Create an OpenLayers map from a context; optionally specify a target (root element) for the map
9
9
  * @param context
10
10
  * @param target
11
11
  */
12
- export declare function createMapFromContext(context: MapContext, target?: string | HTMLElement): Map;
12
+ export declare function createMapFromContext(context: MapContext, target?: string | HTMLElement): Promise<Map>;
13
13
  /**
14
14
  * Resets an OpenLayers map from a context; existing content will be cleared
15
15
  * @param map
16
16
  * @param context
17
17
  */
18
- export declare function resetMapFromContext(map: Map, context: MapContext): Map;
18
+ export declare function resetMapFromContext(map: Map, context: MapContext): Promise<Map>;
19
19
  //# sourceMappingURL=create-map.d.ts.map
@@ -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,EACf,cAAc,EACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,GAAG,MAAM,QAAQ,CAAC;AACzB,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,KAAK,MAAM,gBAAgB,CAAC;AAgBnC,wBAAgB,WAAW,CAAC,UAAU,EAAE,eAAe,GAAG,KAAK,CAgG9D;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAmBpE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EACnB,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,GAC5B,GAAG,CAKL;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,GAAG,GAAG,CAKtE"}
1
+ {"version":3,"file":"create-map.d.ts","sourceRoot":"","sources":["../../lib/map/create-map.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,eAAe,EACf,cAAc,EACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,GAAG,MAAM,QAAQ,CAAC;AACzB,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,KAAK,MAAM,gBAAgB,CAAC;AAqBnC,wBAAsB,WAAW,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CA2I7E;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAmBpE;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACtC,OAAO,EAAE,UAAU,EACnB,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,GAC9B,OAAO,CAAC,GAAG,CAAC,CAKd;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAQrF"}
@@ -1,3 +1,12 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  import Map from "ol/Map";
2
11
  import View from "ol/View";
3
12
  import TileLayer from "ol/layer/Tile";
@@ -10,103 +19,155 @@ import { fromLonLat } from "ol/proj";
10
19
  import { bbox as bboxStrategy } from "ol/loadingstrategy";
11
20
  import { removeSearchParams } from "@geospatial-sdk/core";
12
21
  import { defaultStyle } from "./styles";
22
+ import VectorTileLayer from "ol/layer/VectorTile";
23
+ import { OGCMapTile, OGCVectorTile } from "ol/source";
24
+ import { MVT } from "ol/format";
25
+ import { OgcApiEndpoint, WfsEndpoint } from "@camptocamp/ogc-client";
13
26
  const geosjonFormat = new GeoJSON();
27
+ const WFS_MAX_FEATURES = 10000;
14
28
  export function createLayer(layerModel) {
15
29
  var _a;
16
- const { type } = layerModel;
17
- const style = defaultStyle;
18
- let layer;
19
- switch (type) {
20
- case "xyz":
21
- layer = new TileLayer({
22
- source: new XYZ({
23
- url: layerModel.url,
24
- }),
25
- });
26
- break;
27
- case "wms":
28
- layer = new TileLayer({
29
- source: new TileWMS({
30
- url: removeSearchParams(layerModel.url, ["request", "service"]),
31
- params: { LAYERS: layerModel.name },
32
- gutter: 20,
33
- }),
34
- });
35
- break;
36
- // TODO: implement when ogc-client can handle wmts
37
- // case 'wmts':
38
- // return new TileLayer({
39
- // source: new WMTS(layerModel.options),
40
- // })
41
- case "wfs":
42
- layer = new VectorLayer({
43
- source: new VectorSource({
44
- format: new GeoJSON(),
45
- url: function (extent) {
46
- const urlObj = new URL(removeSearchParams(layerModel.url, [
47
- "service",
48
- "version",
49
- "request",
50
- ]));
51
- urlObj.searchParams.set("service", "WFS");
52
- urlObj.searchParams.set("version", "1.1.0");
53
- urlObj.searchParams.set("request", "GetFeature");
54
- urlObj.searchParams.set("outputFormat", "application/json");
55
- urlObj.searchParams.set("typename", layerModel.featureType);
56
- urlObj.searchParams.set("srsname", "EPSG:3857");
57
- urlObj.searchParams.set("bbox", `${extent.join(",")},EPSG:3857`);
58
- return urlObj.toString();
59
- },
60
- strategy: bboxStrategy,
61
- }),
62
- style,
63
- });
64
- break;
65
- case "geojson": {
66
- if (layerModel.url !== undefined) {
67
- layer = new VectorLayer({
68
- source: new VectorSource({
69
- format: new GeoJSON(),
30
+ return __awaiter(this, void 0, void 0, function* () {
31
+ const { type } = layerModel;
32
+ const style = defaultStyle;
33
+ let layer;
34
+ switch (type) {
35
+ case "xyz":
36
+ layer = new TileLayer({
37
+ source: new XYZ({
70
38
  url: layerModel.url,
39
+ attributions: layerModel.attributions,
40
+ }),
41
+ });
42
+ break;
43
+ case "wms":
44
+ layer = new TileLayer({
45
+ source: new TileWMS({
46
+ url: removeSearchParams(layerModel.url, ["request", "service"]),
47
+ params: { LAYERS: layerModel.name },
48
+ gutter: 20,
49
+ attributions: layerModel.attributions,
71
50
  }),
51
+ });
52
+ break;
53
+ // TODO: implement when ogc-client can handle wmts
54
+ // case 'wmts':
55
+ // return new TileLayer({
56
+ // source: new WMTS(layerModel.options),
57
+ // })
58
+ case "wfs": {
59
+ const olLayer = new VectorLayer({
72
60
  style,
73
61
  });
62
+ new WfsEndpoint(layerModel.url).isReady().then((endpoint) => {
63
+ var _a;
64
+ const featureType = (_a = endpoint.getSingleFeatureTypeName()) !== null && _a !== void 0 ? _a : layerModel.featureType;
65
+ olLayer.setSource(new VectorSource({
66
+ format: new GeoJSON(),
67
+ url: function (extent) {
68
+ return endpoint.getFeatureUrl(featureType, {
69
+ maxFeatures: WFS_MAX_FEATURES,
70
+ asJson: true,
71
+ outputCrs: "EPSG:3857",
72
+ extent: extent,
73
+ extentCrs: "EPSG:3857",
74
+ });
75
+ },
76
+ strategy: bboxStrategy,
77
+ attributions: layerModel.attributions,
78
+ }));
79
+ });
80
+ layer = olLayer;
81
+ break;
82
+ }
83
+ case "geojson": {
84
+ if (layerModel.url !== undefined) {
85
+ layer = new VectorLayer({
86
+ source: new VectorSource({
87
+ format: new GeoJSON(),
88
+ url: layerModel.url,
89
+ attributions: layerModel.attributions,
90
+ }),
91
+ style,
92
+ });
93
+ }
94
+ else {
95
+ let geojson = layerModel.data;
96
+ if (typeof geojson === "string") {
97
+ try {
98
+ geojson = JSON.parse(geojson);
99
+ }
100
+ catch (e) {
101
+ console.warn("A layer could not be created", layerModel, e);
102
+ geojson = { type: "FeatureCollection", features: [] };
103
+ }
104
+ }
105
+ const features = geosjonFormat.readFeatures(geojson, {
106
+ featureProjection: "EPSG:3857",
107
+ dataProjection: "EPSG:4326",
108
+ });
109
+ layer = new VectorLayer({
110
+ source: new VectorSource({
111
+ features,
112
+ attributions: layerModel.attributions,
113
+ }),
114
+ style,
115
+ });
116
+ }
117
+ break;
74
118
  }
75
- else {
76
- let geojson = layerModel.data;
77
- if (typeof geojson === "string") {
78
- try {
79
- geojson = JSON.parse(geojson);
119
+ case "ogcapi": {
120
+ const ogcEndpoint = yield new OgcApiEndpoint(layerModel.url);
121
+ let layerUrl;
122
+ if (layerModel.useTiles) {
123
+ if (layerModel.useTiles === 'vector') {
124
+ layerUrl = yield ogcEndpoint.getVectorTilesetUrl(layerModel.collection, layerModel.tileMatrixSet);
125
+ layer = new VectorTileLayer({
126
+ source: new OGCVectorTile({
127
+ url: layerUrl,
128
+ format: new MVT(),
129
+ attributions: layerModel.attributions,
130
+ }),
131
+ });
80
132
  }
81
- catch (e) {
82
- console.warn("A layer could not be created", layerModel, e);
83
- geojson = { type: "FeatureCollection", features: [] };
133
+ else if (layerModel.useTiles === 'map') {
134
+ layerUrl = yield ogcEndpoint.getMapTilesetUrl(layerModel.collection, layerModel.tileMatrixSet);
135
+ layer = new TileLayer({
136
+ source: new OGCMapTile({
137
+ url: layerUrl,
138
+ attributions: layerModel.attributions,
139
+ }),
140
+ });
84
141
  }
85
142
  }
86
- const features = geosjonFormat.readFeatures(geojson, {
87
- featureProjection: "EPSG:3857",
88
- dataProjection: "EPSG:4326",
89
- });
90
- layer = new VectorLayer({
91
- source: new VectorSource({
92
- features,
93
- }),
94
- style,
95
- });
143
+ else {
144
+ layerUrl = yield ogcEndpoint.getCollectionItemsUrl(layerModel.collection, layerModel.options);
145
+ layer = new VectorLayer({
146
+ source: new VectorSource({
147
+ format: new GeoJSON(),
148
+ url: layerUrl,
149
+ attributions: layerModel.attributions,
150
+ }),
151
+ style,
152
+ });
153
+ }
154
+ break;
96
155
  }
97
- break;
156
+ default:
157
+ throw new Error(`Unrecognized layer type: ${layerModel.type}`);
98
158
  }
99
- default:
100
- throw new Error(`Unrecognized layer type: ${layerModel.type}`);
101
- }
102
- typeof layerModel.visibility !== "undefined" &&
103
- layer.setVisible(layerModel.visibility);
104
- typeof layerModel.opacity !== "undefined" &&
105
- layer.setOpacity(layerModel.opacity);
106
- typeof layerModel.attributions !== "undefined" &&
107
- ((_a = layer.getSource()) === null || _a === void 0 ? void 0 : _a.setAttributions(layerModel.attributions));
108
- layer.set("label", layerModel.label);
109
- return layer;
159
+ if (!layer) {
160
+ throw new Error(`Layer could not be created for type: ${layerModel.type}`);
161
+ }
162
+ typeof layerModel.visibility !== "undefined" &&
163
+ layer.setVisible(layerModel.visibility);
164
+ typeof layerModel.opacity !== "undefined" &&
165
+ layer.setOpacity(layerModel.opacity);
166
+ typeof layerModel.attributions !== "undefined" &&
167
+ ((_a = layer.getSource()) === null || _a === void 0 ? void 0 : _a.setAttributions(layerModel.attributions));
168
+ layer.set("label", layerModel.label);
169
+ return layer;
170
+ });
110
171
  }
111
172
  export function createView(viewModel, map) {
112
173
  const { center: centerInViewProj, zoom, maxZoom, maxExtent } = viewModel;
@@ -134,10 +195,12 @@ export function createView(viewModel, map) {
134
195
  * @param target
135
196
  */
136
197
  export function createMapFromContext(context, target) {
137
- const map = new Map({
138
- target,
198
+ return __awaiter(this, void 0, void 0, function* () {
199
+ const map = new Map({
200
+ target,
201
+ });
202
+ return yield resetMapFromContext(map, context);
139
203
  });
140
- return resetMapFromContext(map, context);
141
204
  }
142
205
  /**
143
206
  * Resets an OpenLayers map from a context; existing content will be cleared
@@ -145,8 +208,13 @@ export function createMapFromContext(context, target) {
145
208
  * @param context
146
209
  */
147
210
  export function resetMapFromContext(map, context) {
148
- map.setView(createView(context.view, map));
149
- map.getLayers().clear();
150
- context.layers.forEach((layer) => map.addLayer(createLayer(layer)));
151
- return map;
211
+ return __awaiter(this, void 0, void 0, function* () {
212
+ map.setView(createView(context.view, map));
213
+ map.getLayers().clear();
214
+ for (const layerModel of context.layers) {
215
+ const layer = yield createLayer(layerModel);
216
+ map.addLayer(layer);
217
+ }
218
+ return map;
219
+ });
152
220
  }
@@ -17,8 +17,8 @@ import { applyContextDiffToMap } from "./apply-context-diff";
17
17
  import { beforeEach } from "vitest";
18
18
  import BaseLayer from "ol/layer/Base";
19
19
 
20
- function assertEqualsToModel(layer: any, layerModel: MapContextLayer) {
21
- const reference = createLayer(layerModel) as any;
20
+ async function assertEqualsToModel(layer: any, layerModel: MapContextLayer) {
21
+ const reference = await createLayer(layerModel) as any;
22
22
  expect(reference).toBeInstanceOf(layer.constructor);
23
23
  const refSource = reference.getSource() as any;
24
24
  const layerSource = layer.getSource() as any;
@@ -38,16 +38,16 @@ describe("applyContextDiffToMap", () => {
38
38
  let map: Map;
39
39
  let layersArray: BaseLayer[];
40
40
 
41
- beforeEach(() => {
41
+ beforeEach(async () => {
42
42
  context = {
43
43
  ...SAMPLE_CONTEXT,
44
44
  layers: [SAMPLE_LAYER2, SAMPLE_LAYER1],
45
45
  };
46
- map = createMapFromContext(context);
46
+ map = await createMapFromContext(context);
47
47
  });
48
48
 
49
49
  describe("no change", () => {
50
- beforeEach(() => {
50
+ beforeEach(async () => {
51
51
  diff = {
52
52
  layersAdded: [],
53
53
  layersChanged: [],
@@ -55,7 +55,7 @@ describe("applyContextDiffToMap", () => {
55
55
  layersReordered: [],
56
56
  viewChanges: {},
57
57
  };
58
- applyContextDiffToMap(map, diff);
58
+ await applyContextDiffToMap(map, diff);
59
59
  layersArray = map.getLayers().getArray();
60
60
  });
61
61
  it("does not affect the map", () => {
@@ -159,12 +159,12 @@ describe("applyContextDiffToMap", () => {
159
159
 
160
160
  describe("reordering", () => {
161
161
  describe("three layers reordered", () => {
162
- beforeEach(() => {
162
+ beforeEach(async () => {
163
163
  context = {
164
164
  ...SAMPLE_CONTEXT,
165
165
  layers: [SAMPLE_LAYER1, SAMPLE_LAYER2, SAMPLE_LAYER3],
166
166
  };
167
- map = createMapFromContext(context);
167
+ map = await createMapFromContext(context);
168
168
  diff = {
169
169
  layersAdded: [],
170
170
  layersChanged: [],
@@ -195,12 +195,12 @@ describe("applyContextDiffToMap", () => {
195
195
  });
196
196
 
197
197
  describe("four layers reordered", () => {
198
- beforeEach(() => {
198
+ beforeEach(async () => {
199
199
  context = {
200
200
  ...SAMPLE_CONTEXT,
201
201
  layers: [SAMPLE_LAYER1, SAMPLE_LAYER3, SAMPLE_LAYER4, SAMPLE_LAYER2],
202
202
  };
203
- map = createMapFromContext(context);
203
+ map = await createMapFromContext(context);
204
204
  diff = {
205
205
  layersAdded: [],
206
206
  layersChanged: [],
@@ -234,13 +234,13 @@ describe("applyContextDiffToMap", () => {
234
234
 
235
235
  describe("combined changes", () => {
236
236
  let changedLayer: MapContextLayer;
237
- beforeEach(() => {
238
- changedLayer = { ...SAMPLE_LAYER3, extras: { prop: true } };
237
+ beforeEach(async () => {
238
+ changedLayer = {...SAMPLE_LAYER3, extras: {prop: true}};
239
239
  context = {
240
240
  ...context,
241
241
  layers: [SAMPLE_LAYER1, SAMPLE_LAYER5, SAMPLE_LAYER3, SAMPLE_LAYER4],
242
242
  };
243
- map = createMapFromContext(context);
243
+ map = await createMapFromContext(context);
244
244
  diff = {
245
245
  layersAdded: [
246
246
  {
@@ -7,16 +7,16 @@ import { createLayer } from "./create-map";
7
7
  * @param map
8
8
  * @param contextDiff
9
9
  */
10
- export function applyContextDiffToMap(
11
- map: Map,
12
- contextDiff: MapContextDiff,
13
- ): Map {
10
+ export async function applyContextDiffToMap(
11
+ map: Map,
12
+ contextDiff: MapContextDiff,
13
+ ): Promise<Map> {
14
14
  const layers = map.getLayers();
15
15
 
16
16
  // removed layers (sorted by descending position)
17
17
  if (contextDiff.layersRemoved.length > 0) {
18
18
  const removed = contextDiff.layersRemoved.sort(
19
- (a, b) => b.position - a.position,
19
+ (a, b) => b.position - a.position,
20
20
  );
21
21
  for (const layerRemoved of removed) {
22
22
  layers.item(layerRemoved.position).dispose();
@@ -25,22 +25,26 @@ export function applyContextDiffToMap(
25
25
  }
26
26
 
27
27
  // insert added layers
28
- for (const layerAdded of contextDiff.layersAdded) {
29
- const layer = createLayer(layerAdded.layer);
30
- if (layerAdded.position >= layers.getLength()) {
28
+ const newLayers = await Promise.all(
29
+ contextDiff.layersAdded.map((layerAdded) => createLayer(layerAdded.layer))
30
+ );
31
+
32
+ newLayers.forEach((layer, index) => {
33
+ const position = contextDiff.layersAdded[index].position;
34
+ if (position >= layers.getLength()) {
31
35
  layers.push(layer);
32
36
  } else {
33
- layers.insertAt(layerAdded.position, layer);
37
+ layers.insertAt(position, layer);
34
38
  }
35
- }
39
+ });
36
40
 
37
41
  // move reordered layers (sorted by ascending new position)
38
42
  if (contextDiff.layersReordered.length > 0) {
39
43
  const reordered = contextDiff.layersReordered.sort(
40
- (a, b) => a.newPosition - b.newPosition,
44
+ (a, b) => a.newPosition - b.newPosition,
41
45
  );
42
46
  const olLayers = reordered.map((layer) =>
43
- layers.item(layer.previousPosition),
47
+ layers.item(layer.previousPosition),
44
48
  );
45
49
  const layersArray = layers.getArray();
46
50
  for (let i = 0; i < reordered.length; i++) {
@@ -52,7 +56,9 @@ export function applyContextDiffToMap(
52
56
  // recreate changed layers
53
57
  for (const layerChanged of contextDiff.layersChanged) {
54
58
  layers.item(layerChanged.position).dispose();
55
- layers.setAt(layerChanged.position, createLayer(layerChanged.layer));
59
+ createLayer(layerChanged.layer).then(layer => {
60
+ layers.setAt(layerChanged.position, layer);
61
+ });
56
62
  }
57
63
  return map;
58
64
  }
@@ -15,6 +15,7 @@ import {
15
15
  MAP_CTX_LAYER_WFS_FIXTURE,
16
16
  MAP_CTX_LAYER_WMS_FIXTURE,
17
17
  MAP_CTX_LAYER_XYZ_FIXTURE,
18
+ MAP_CTX_LAYER_OGCAPI_FIXTURE,
18
19
  } from "@geospatial-sdk/core/fixtures/map-context.fixtures";
19
20
  import {
20
21
  MapContext,
@@ -35,9 +36,9 @@ describe("MapContextService", () => {
35
36
  let layerModel: MapContextLayer, layer: Layer;
36
37
 
37
38
  describe("XYZ", () => {
38
- beforeEach(() => {
39
+ beforeEach(async () => {
39
40
  layerModel = MAP_CTX_LAYER_XYZ_FIXTURE;
40
- layer = createLayer(layerModel);
41
+ layer = await createLayer(layerModel);
41
42
  });
42
43
  it("create a tile layer", () => {
43
44
  expect(layer).toBeTruthy();
@@ -62,11 +63,37 @@ describe("MapContextService", () => {
62
63
  );
63
64
  });
64
65
  });
65
-
66
+ describe("OGCAPI", () => {
67
+ beforeEach(async () => {
68
+ layerModel = MAP_CTX_LAYER_OGCAPI_FIXTURE;
69
+ layer = await createLayer(layerModel);
70
+ });
71
+ it("create a vector tile layer", () => {
72
+ expect(layer).toBeTruthy();
73
+ expect(layer).toBeInstanceOf(VectorLayer);
74
+ });
75
+ it("set correct layer properties", () => {
76
+ expect(layer.getVisible()).toBe(true);
77
+ expect(layer.getOpacity()).toBe(1);
78
+ expect(layer.get("label")).toBeUndefined();
79
+ expect(layer.getSource()?.getAttributions()).toBeNull();
80
+ });
81
+ it("create a OGCVectorTile source", () => {
82
+ const source = layer.getSource();
83
+ expect(source).toBeInstanceOf(VectorSource);
84
+ });
85
+ it("set correct url", () => {
86
+ const source = layer.getSource() as VectorSource;
87
+ const url = source.getUrl();
88
+ expect(url).toBe(
89
+ "https://demo.ldproxy.net/zoomstack/collections/airports/items?f=json",
90
+ );
91
+ });
92
+ });
66
93
  describe("WMS", () => {
67
- beforeEach(() => {
94
+ beforeEach(async () => {
68
95
  (layerModel = MAP_CTX_LAYER_WMS_FIXTURE),
69
- (layer = createLayer(layerModel));
96
+ (layer = await createLayer(layerModel));
70
97
  });
71
98
  it("create a tile layer", () => {
72
99
  expect(layer).toBeTruthy();
@@ -104,9 +131,9 @@ describe("MapContextService", () => {
104
131
  });
105
132
 
106
133
  describe("WFS", () => {
107
- beforeEach(() => {
134
+ beforeEach(async () => {
108
135
  (layerModel = MAP_CTX_LAYER_WFS_FIXTURE),
109
- (layer = createLayer(layerModel));
136
+ (layer = await createLayer(layerModel));
110
137
  });
111
138
  it("create a vector layer", () => {
112
139
  expect(layer).toBeTruthy();
@@ -116,6 +143,9 @@ describe("MapContextService", () => {
116
143
  expect(layer.getVisible()).toBe(true);
117
144
  expect(layer.getOpacity()).toBe(0.5);
118
145
  expect(layer.get("label")).toBe("Communes");
146
+ const source = layer.getSource();
147
+ expect(source).toBeInstanceOf(VectorSource);
148
+
119
149
  const attributions = layer.getSource()?.getAttributions();
120
150
  expect(attributions).not.toBeNull();
121
151
  // @ts-ignore
@@ -129,16 +159,16 @@ describe("MapContextService", () => {
129
159
  const source = layer.getSource() as VectorSource;
130
160
  const urlLoader = source.getUrl() as Function;
131
161
  expect(urlLoader([10, 20, 30, 40])).toBe(
132
- "https://www.geograndest.fr/geoserver/region-grand-est/ows?service=WFS&version=1.1.0&request=GetFeature&outputFormat=application%2Fjson&typename=ms%3Acommune_actuelle_3857&srsname=EPSG%3A3857&bbox=10%2C20%2C30%2C40%2CEPSG%3A3857",
162
+ "https://www.geograndest.fr/geoserver/region-grand-est/ows?service=WFS&version=1.1.0&request=GetFeature&outputFormat=application%2Fjson&typename=ms%3Acommune_actuelle_3857&srsname=EPSG%3A3857&bbox=10%2C20%2C30%2C40%2CEPSG%3A3857&maxFeatures=10000",
133
163
  );
134
164
  });
135
165
  });
136
166
 
137
167
  describe("GEOJSON", () => {
138
168
  describe("with inline data", () => {
139
- beforeEach(() => {
169
+ beforeEach(async () => {
140
170
  layerModel = MAP_CTX_LAYER_GEOJSON_FIXTURE;
141
- layer = createLayer(layerModel);
171
+ layer = await createLayer(layerModel);
142
172
  });
143
173
  it("create a VectorLayer", () => {
144
174
  expect(layer).toBeTruthy();
@@ -162,10 +192,10 @@ describe("MapContextService", () => {
162
192
  });
163
193
  });
164
194
  describe("with inline data as string", () => {
165
- beforeEach(() => {
166
- layerModel = { ...MAP_CTX_LAYER_GEOJSON_FIXTURE };
195
+ beforeEach(async () => {
196
+ layerModel = {...MAP_CTX_LAYER_GEOJSON_FIXTURE};
167
197
  layerModel.data = JSON.stringify(layerModel.data);
168
- layer = createLayer(layerModel);
198
+ layer = await createLayer(layerModel);
169
199
  });
170
200
  it("create a VectorLayer", () => {
171
201
  expect(layer).toBeTruthy();
@@ -185,7 +215,7 @@ describe("MapContextService", () => {
185
215
  });
186
216
  });
187
217
  describe("with invalid inline data as string", () => {
188
- beforeEach(() => {
218
+ beforeEach(async () => {
189
219
  const spy = vi.spyOn(window.console, "warn");
190
220
  spy.mockClear();
191
221
  layerModel = {
@@ -193,7 +223,7 @@ describe("MapContextService", () => {
193
223
  url: undefined,
194
224
  data: "blargz",
195
225
  };
196
- layer = createLayer(layerModel);
226
+ layer = await createLayer(layerModel);
197
227
  });
198
228
  it("create a VectorLayer", () => {
199
229
  expect(layer).toBeTruthy();
@@ -209,9 +239,9 @@ describe("MapContextService", () => {
209
239
  });
210
240
  });
211
241
  describe("with remote file url", () => {
212
- beforeEach(() => {
242
+ beforeEach(async () => {
213
243
  layerModel = MAP_CTX_LAYER_GEOJSON_REMOTE_FIXTURE;
214
- layer = createLayer(layerModel);
244
+ layer = await createLayer(layerModel);
215
245
  });
216
246
  it("create a VectorLayer", () => {
217
247
  expect(layer).toBeTruthy();
@@ -238,8 +268,8 @@ describe("MapContextService", () => {
238
268
  let map: Map;
239
269
  describe("from center and zoom", () => {
240
270
  const contextModel = MAP_CTX_FIXTURE;
241
- beforeEach(() => {
242
- map = createMapFromContext(contextModel);
271
+ beforeEach(async () => {
272
+ map = await createMapFromContext(contextModel);
243
273
  view = createView(contextModel.view, map);
244
274
  });
245
275
  it("create a view", () => {
@@ -18,18 +18,24 @@ import { fromLonLat } from "ol/proj";
18
18
  import { bbox as bboxStrategy } from "ol/loadingstrategy";
19
19
  import { removeSearchParams } from "@geospatial-sdk/core";
20
20
  import { defaultStyle } from "./styles";
21
+ import VectorTileLayer from "ol/layer/VectorTile";
22
+ import {OGCMapTile, OGCVectorTile} from "ol/source";
23
+ import {MVT} from "ol/format";
24
+ import {OgcApiEndpoint, WfsEndpoint} from "@camptocamp/ogc-client";
21
25
 
22
26
  const geosjonFormat = new GeoJSON();
27
+ const WFS_MAX_FEATURES = 10000;
23
28
 
24
- export function createLayer(layerModel: MapContextLayer): Layer {
29
+ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
25
30
  const { type } = layerModel;
26
31
  const style = defaultStyle;
27
- let layer: Layer;
32
+ let layer: Layer | undefined;
28
33
  switch (type) {
29
34
  case "xyz":
30
35
  layer = new TileLayer({
31
36
  source: new XYZ({
32
37
  url: layerModel.url,
38
+ attributions: layerModel.attributions,
33
39
  }),
34
40
  });
35
41
  break;
@@ -39,6 +45,7 @@ export function createLayer(layerModel: MapContextLayer): Layer {
39
45
  url: removeSearchParams(layerModel.url, ["request", "service"]),
40
46
  params: { LAYERS: layerModel.name },
41
47
  gutter: 20,
48
+ attributions: layerModel.attributions,
42
49
  }),
43
50
  });
44
51
  break;
@@ -47,38 +54,40 @@ export function createLayer(layerModel: MapContextLayer): Layer {
47
54
  // return new TileLayer({
48
55
  // source: new WMTS(layerModel.options),
49
56
  // })
50
- case "wfs":
51
- layer = new VectorLayer({
52
- source: new VectorSource({
53
- format: new GeoJSON(),
54
- url: function (extent) {
55
- const urlObj = new URL(
56
- removeSearchParams(layerModel.url, [
57
- "service",
58
- "version",
59
- "request",
60
- ]),
61
- );
62
- urlObj.searchParams.set("service", "WFS");
63
- urlObj.searchParams.set("version", "1.1.0");
64
- urlObj.searchParams.set("request", "GetFeature");
65
- urlObj.searchParams.set("outputFormat", "application/json");
66
- urlObj.searchParams.set("typename", layerModel.featureType);
67
- urlObj.searchParams.set("srsname", "EPSG:3857");
68
- urlObj.searchParams.set("bbox", `${extent.join(",")},EPSG:3857`);
69
- return urlObj.toString();
70
- },
71
- strategy: bboxStrategy,
72
- }),
57
+ case "wfs":{
58
+ const olLayer = new VectorLayer({
73
59
  style,
74
60
  });
61
+ new WfsEndpoint(layerModel.url).isReady().then((endpoint) => {
62
+ const featureType =
63
+ endpoint.getSingleFeatureTypeName() ?? layerModel.featureType;
64
+ olLayer.setSource(
65
+ new VectorSource({
66
+ format: new GeoJSON(),
67
+ url: function (extent) {
68
+ return endpoint.getFeatureUrl(featureType, {
69
+ maxFeatures: WFS_MAX_FEATURES,
70
+ asJson: true,
71
+ outputCrs: "EPSG:3857",
72
+ extent: extent as [number, number, number, number],
73
+ extentCrs: "EPSG:3857",
74
+ });
75
+ },
76
+ strategy: bboxStrategy,
77
+ attributions: layerModel.attributions,
78
+ }),
79
+ );
80
+ });
81
+ layer = olLayer;
75
82
  break;
83
+ }
76
84
  case "geojson": {
77
85
  if (layerModel.url !== undefined) {
78
86
  layer = new VectorLayer({
79
87
  source: new VectorSource({
80
88
  format: new GeoJSON(),
81
89
  url: layerModel.url,
90
+ attributions: layerModel.attributions,
82
91
  }),
83
92
  style,
84
93
  });
@@ -99,6 +108,42 @@ export function createLayer(layerModel: MapContextLayer): Layer {
99
108
  layer = new VectorLayer({
100
109
  source: new VectorSource({
101
110
  features,
111
+ attributions: layerModel.attributions,
112
+ }),
113
+ style,
114
+ });
115
+ }
116
+ break;
117
+ }
118
+ case "ogcapi": {
119
+ const ogcEndpoint = await new OgcApiEndpoint(layerModel.url);
120
+ let layerUrl: string;
121
+ if (layerModel.useTiles) {
122
+ if (layerModel.useTiles === 'vector') {
123
+ layerUrl = await ogcEndpoint.getVectorTilesetUrl(layerModel.collection, layerModel.tileMatrixSet);
124
+ layer = new VectorTileLayer({
125
+ source: new OGCVectorTile({
126
+ url: layerUrl,
127
+ format: new MVT(),
128
+ attributions: layerModel.attributions,
129
+ }),
130
+ });
131
+ } else if (layerModel.useTiles === 'map') {
132
+ layerUrl = await ogcEndpoint.getMapTilesetUrl(layerModel.collection, layerModel.tileMatrixSet);
133
+ layer = new TileLayer({
134
+ source: new OGCMapTile({
135
+ url: layerUrl,
136
+ attributions: layerModel.attributions,
137
+ }),
138
+ });
139
+ }
140
+ } else {
141
+ layerUrl = await ogcEndpoint.getCollectionItemsUrl(layerModel.collection, layerModel.options);
142
+ layer = new VectorLayer({
143
+ source: new VectorSource({
144
+ format: new GeoJSON(),
145
+ url: layerUrl,
146
+ attributions: layerModel.attributions,
102
147
  }),
103
148
  style,
104
149
  });
@@ -108,6 +153,9 @@ export function createLayer(layerModel: MapContextLayer): Layer {
108
153
  default:
109
154
  throw new Error(`Unrecognized layer type: ${layerModel.type}`);
110
155
  }
156
+ if (!layer) {
157
+ throw new Error(`Layer could not be created for type: ${layerModel.type}`);
158
+ }
111
159
  typeof layerModel.visibility !== "undefined" &&
112
160
  layer.setVisible(layerModel.visibility);
113
161
  typeof layerModel.opacity !== "undefined" &&
@@ -145,14 +193,14 @@ export function createView(viewModel: MapContextView, map: Map): View {
145
193
  * @param context
146
194
  * @param target
147
195
  */
148
- export function createMapFromContext(
149
- context: MapContext,
150
- target?: string | HTMLElement,
151
- ): Map {
196
+ export async function createMapFromContext(
197
+ context: MapContext,
198
+ target?: string | HTMLElement,
199
+ ): Promise<Map> {
152
200
  const map = new Map({
153
201
  target,
154
202
  });
155
- return resetMapFromContext(map, context);
203
+ return await resetMapFromContext(map, context);
156
204
  }
157
205
 
158
206
  /**
@@ -160,9 +208,12 @@ export function createMapFromContext(
160
208
  * @param map
161
209
  * @param context
162
210
  */
163
- export function resetMapFromContext(map: Map, context: MapContext): Map {
211
+ export async function resetMapFromContext(map: Map, context: MapContext): Promise<Map> {
164
212
  map.setView(createView(context.view, map));
165
213
  map.getLayers().clear();
166
- context.layers.forEach((layer) => map.addLayer(createLayer(layer)));
214
+ for (const layerModel of context.layers) {
215
+ const layer = await createLayer(layerModel);
216
+ map.addLayer(layer);
217
+ }
167
218
  return map;
168
219
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geospatial-sdk/openlayers",
3
- "version": "0.0.5-dev.10+cc9736c",
3
+ "version": "0.0.5-dev.12+8efa402",
4
4
  "description": "OpenLayers-related utilities",
5
5
  "keywords": [
6
6
  "ol",
@@ -35,8 +35,8 @@
35
35
  "ol": ">6.x"
36
36
  },
37
37
  "dependencies": {
38
- "@geospatial-sdk/core": "^0.0.5-dev.10+cc9736c",
38
+ "@geospatial-sdk/core": "^0.0.5-dev.12+8efa402",
39
39
  "chroma-js": "^2.4.2"
40
40
  },
41
- "gitHead": "cc9736c7f9471df0304b3beb82ed31f8926ab627"
41
+ "gitHead": "8efa4028fb9c2f68aefcf72b744a7afb5c38f19c"
42
42
  }