@geospatial-sdk/openlayers 0.0.5-dev.55 → 0.0.5-dev.57

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.
Files changed (37) hide show
  1. package/dist/map/apply-context-diff.d.ts.map +1 -1
  2. package/dist/map/apply-context-diff.js +9 -1
  3. package/dist/map/create-map.d.ts +1 -1
  4. package/dist/map/create-map.d.ts.map +1 -1
  5. package/dist/map/create-map.js +85 -44
  6. package/dist/map/feature-hover.js +1 -1
  7. package/dist/map/handle-errors.d.ts +0 -7
  8. package/dist/map/handle-errors.d.ts.map +1 -1
  9. package/dist/map/handle-errors.js +10 -14
  10. package/dist/map/index.d.ts +2 -1
  11. package/dist/map/index.d.ts.map +1 -1
  12. package/dist/map/index.js +2 -1
  13. package/dist/map/layer-update.d.ts.map +1 -1
  14. package/dist/map/layer-update.js +1 -1
  15. package/dist/map/listen.d.ts +4 -0
  16. package/dist/map/listen.d.ts.map +1 -0
  17. package/dist/map/listen.js +70 -0
  18. package/dist/map/register-events.d.ts +16 -2
  19. package/dist/map/register-events.d.ts.map +1 -1
  20. package/dist/map/register-events.js +172 -81
  21. package/dist/map/resolved-map-state.d.ts +8 -0
  22. package/dist/map/resolved-map-state.d.ts.map +1 -0
  23. package/dist/map/resolved-map-state.js +26 -0
  24. package/lib/map/apply-context-diff.ts +16 -5
  25. package/lib/map/create-map.test.ts +178 -40
  26. package/lib/map/create-map.ts +114 -55
  27. package/lib/map/feature-hover.ts +1 -1
  28. package/lib/map/handle-errors.test.ts +13 -36
  29. package/lib/map/handle-errors.ts +10 -28
  30. package/lib/map/index.ts +2 -1
  31. package/lib/map/layer-update.ts +3 -2
  32. package/lib/map/listen.test.ts +977 -0
  33. package/lib/map/listen.ts +123 -0
  34. package/lib/map/register-events.ts +229 -109
  35. package/lib/map/resolved-map-state.ts +38 -0
  36. package/package.json +3 -3
  37. package/lib/map/register-events.test.ts +0 -259
@@ -7,9 +7,9 @@ import {
7
7
  import {
8
8
  MAP_CTX_EXTENT_FIXTURE,
9
9
  MAP_CTX_FIXTURE,
10
- MAP_CTX_LAYER_GEOTIFF_FIXTURE,
11
10
  MAP_CTX_LAYER_GEOJSON_FIXTURE,
12
11
  MAP_CTX_LAYER_GEOJSON_REMOTE_FIXTURE,
12
+ MAP_CTX_LAYER_GEOTIFF_FIXTURE,
13
13
  MAP_CTX_LAYER_MAPBLIBRE_STYLE_FIXTURE,
14
14
  MAP_CTX_LAYER_MVT_FIXTURE,
15
15
  MAP_CTX_LAYER_OGCAPI_FIXTURE,
@@ -44,10 +44,10 @@ import {
44
44
  createView,
45
45
  resetMapFromContext,
46
46
  } from "./create-map.js";
47
- import {
48
- handleEndpointError,
49
- tileLoadErrorCatchFunction,
50
- } from "./handle-errors.js";
47
+ import { tileLoadErrorCatchFunction } from "./handle-errors.js";
48
+ import { GEOSPATIAL_SDK_PREFIX } from "./constants.js";
49
+ import ImageLayer from "ol/layer/Image.js";
50
+ import ImageWMS from "ol/source/ImageWMS.js";
51
51
 
52
52
  vi.mock("./handle-errors", async (importOriginal) => {
53
53
  const actual =
@@ -59,18 +59,22 @@ vi.mock("./handle-errors", async (importOriginal) => {
59
59
  };
60
60
  });
61
61
 
62
- afterEach(() => {
62
+ beforeEach(() => {
63
+ vi.useFakeTimers();
63
64
  vi.clearAllMocks();
64
65
  });
65
66
 
66
67
  describe("MapContextService", () => {
67
68
  describe("#createLayer", () => {
68
69
  let layerModel: MapContextLayer, layer: Layer;
70
+ const eventCallback = vi.fn();
69
71
 
70
72
  describe("XYZ", () => {
71
73
  beforeEach(async () => {
72
74
  layerModel = MAP_CTX_LAYER_XYZ_FIXTURE;
73
75
  layer = await createLayer(layerModel);
76
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-loading-status`, eventCallback);
77
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
74
78
  });
75
79
  it("create a tile layer", () => {
76
80
  expect(layer).toBeTruthy();
@@ -108,13 +112,25 @@ describe("MapContextService", () => {
108
112
  tileLoadFunction(tile, "http://example.com/tile");
109
113
  expect(tileLoadErrorCatchFunction).toHaveBeenCalled();
110
114
  });
115
+ it("emits a loaded event initially", async () => {
116
+ await vi.runAllTimersAsync();
117
+ expect(eventCallback).toHaveBeenCalledWith({
118
+ layerState: {
119
+ loaded: true,
120
+ },
121
+ target: layer,
122
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
123
+ });
124
+ });
111
125
  });
112
- describe("OGCAPI", () => {
126
+ describe("OGCAPI (as geojson items)", () => {
113
127
  beforeEach(async () => {
114
128
  layerModel = MAP_CTX_LAYER_OGCAPI_FIXTURE;
115
129
  layer = await createLayer(layerModel);
130
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-loading-status`, eventCallback);
131
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
116
132
  });
117
- it("create a vector tile layer", () => {
133
+ it("create a vector layer", () => {
118
134
  expect(layer).toBeTruthy();
119
135
  expect(layer).toBeInstanceOf(VectorLayer);
120
136
  });
@@ -124,7 +140,7 @@ describe("MapContextService", () => {
124
140
  expect(layer.get("label")).toBeUndefined();
125
141
  expect(layer.getSource()?.getAttributions()).toBeNull();
126
142
  });
127
- it("create a OGCVectorTile source", () => {
143
+ it("create a vector source", () => {
128
144
  const source = layer.getSource();
129
145
  expect(source).toBeInstanceOf(VectorSource);
130
146
  });
@@ -135,11 +151,23 @@ describe("MapContextService", () => {
135
151
  "https://demo.ldproxy.net/zoomstack/collections/airports/items?f=json",
136
152
  );
137
153
  });
154
+ it("emits a loaded event initially", async () => {
155
+ await vi.runAllTimersAsync();
156
+ expect(eventCallback).toHaveBeenCalledWith({
157
+ layerState: {
158
+ loaded: true,
159
+ },
160
+ target: layer,
161
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
162
+ });
163
+ });
138
164
  });
139
165
  describe("WMS", () => {
140
166
  beforeEach(async () => {
141
- (layerModel = MAP_CTX_LAYER_WMS_FIXTURE),
142
- (layer = await createLayer(layerModel));
167
+ layerModel = MAP_CTX_LAYER_WMS_FIXTURE;
168
+ layer = await createLayer(layerModel);
169
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-loading-status`, eventCallback);
170
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
143
171
  });
144
172
  it("create a tile layer", () => {
145
173
  expect(layer).toBeTruthy();
@@ -159,8 +187,11 @@ describe("MapContextService", () => {
159
187
  it("set correct WMS params", () => {
160
188
  const source = layer.getSource() as TileWMS;
161
189
  const params = source.getParams();
162
- expect(params.LAYERS).toBe((layerModel as MapContextLayerWms).name);
163
- expect(params.STYLES).toBe((layerModel as MapContextLayerWms).style);
190
+ expect(params).toEqual({
191
+ LAYERS: (layerModel as MapContextLayerWms).name,
192
+ STYLES: (layerModel as MapContextLayerWms).style,
193
+ TILED: true,
194
+ });
164
195
  });
165
196
  it("set correct url without existing REQUEST and SERVICE params", () => {
166
197
  const source = layer.getSource() as TileWMS;
@@ -189,12 +220,68 @@ describe("MapContextService", () => {
189
220
  tileLoadFunction(tile, "http://example.com/tile");
190
221
  expect(tileLoadErrorCatchFunction).toHaveBeenCalled();
191
222
  });
223
+ it("emits a loaded event initially", async () => {
224
+ await vi.runAllTimersAsync();
225
+ expect(eventCallback).toHaveBeenCalledWith({
226
+ layerState: {
227
+ loaded: true,
228
+ },
229
+ target: layer,
230
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
231
+ });
232
+ });
233
+
234
+ describe("not using tiles", () => {
235
+ beforeEach(async () => {
236
+ layerModel = { ...MAP_CTX_LAYER_WMS_FIXTURE, useTiles: false };
237
+ layer = await createLayer(layerModel);
238
+ layer.on(
239
+ `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
240
+ eventCallback,
241
+ );
242
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
243
+ });
244
+ it("create an image layer", () => {
245
+ expect(layer).toBeTruthy();
246
+ expect(layer).toBeInstanceOf(ImageLayer);
247
+ });
248
+ it("set correct layer properties", () => {
249
+ expect(layer.getVisible()).toBe(false);
250
+ expect(layer.getOpacity()).toBe(0.5);
251
+ expect(layer.get("label")).toBe("Communes");
252
+ // @ts-expect-error TS2554 we're not providing a view extent here
253
+ expect(layer.getSource()?.getAttributions()!()).toEqual([
254
+ "camptocamp",
255
+ ]);
256
+ });
257
+ it("create an ImageWMS source", () => {
258
+ const source = layer.getSource();
259
+ expect(source).toBeInstanceOf(ImageWMS);
260
+ });
261
+ it("set correct WMS params", () => {
262
+ const source = layer.getSource() as ImageWMS;
263
+ const params = source.getParams();
264
+ expect(params).toEqual({
265
+ LAYERS: (layerModel as MapContextLayerWms).name,
266
+ STYLES: (layerModel as MapContextLayerWms).style,
267
+ });
268
+ });
269
+ it("set correct url without existing REQUEST and SERVICE params", () => {
270
+ const source = layer.getSource() as ImageWMS;
271
+ const url = source.getUrl();
272
+ expect(url).toBe(
273
+ "https://www.datagrandest.fr/geoserver/region-grand-est/ows",
274
+ );
275
+ });
276
+ });
192
277
  });
193
278
 
194
279
  describe("WFS", () => {
195
280
  beforeEach(async () => {
196
- (layerModel = MAP_CTX_LAYER_WFS_FIXTURE),
197
- (layer = await createLayer(layerModel));
281
+ layerModel = MAP_CTX_LAYER_WFS_FIXTURE;
282
+ layer = await createLayer(layerModel);
283
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-loading-status`, eventCallback);
284
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
198
285
  });
199
286
  it("create a vector layer", () => {
200
287
  expect(layer).toBeTruthy();
@@ -224,17 +311,15 @@ describe("MapContextService", () => {
224
311
  "https://www.datagrandest.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",
225
312
  );
226
313
  });
227
- it("should NOT call handleEndpointError", () => {
228
- expect(handleEndpointError).not.toHaveBeenCalled();
229
- });
230
- });
231
- describe("WFS error", () => {
232
- beforeEach(async () => {
233
- layerModel = { ...MAP_CTX_LAYER_WFS_FIXTURE, url: "https://wfs/error" };
234
- layer = await createLayer(layerModel);
235
- });
236
- it("should call handleEndpointError", () => {
237
- expect(handleEndpointError).toHaveBeenCalled();
314
+ it("emits a loaded event initially", async () => {
315
+ await vi.runAllTimersAsync();
316
+ expect(eventCallback).toHaveBeenCalledWith({
317
+ layerState: {
318
+ loaded: true,
319
+ },
320
+ target: layer,
321
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
322
+ });
238
323
  });
239
324
  });
240
325
 
@@ -243,6 +328,11 @@ describe("MapContextService", () => {
243
328
  beforeEach(async () => {
244
329
  layerModel = MAP_CTX_LAYER_GEOJSON_FIXTURE;
245
330
  layer = await createLayer(layerModel);
331
+ layer.on(
332
+ `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
333
+ eventCallback,
334
+ );
335
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
246
336
  });
247
337
  it("create a VectorLayer", () => {
248
338
  expect(layer).toBeTruthy();
@@ -264,6 +354,16 @@ describe("MapContextService", () => {
264
354
  .data as FeatureCollection;
265
355
  expect(features.length).toBe(data.features.length);
266
356
  });
357
+ it("emits a loaded event", async () => {
358
+ await vi.runAllTimersAsync();
359
+ expect(eventCallback).toHaveBeenCalledWith({
360
+ layerState: {
361
+ loaded: true,
362
+ },
363
+ target: layer,
364
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
365
+ });
366
+ });
267
367
  });
268
368
  describe("with inline data as string", () => {
269
369
  beforeEach(async () => {
@@ -316,6 +416,11 @@ describe("MapContextService", () => {
316
416
  beforeEach(async () => {
317
417
  layerModel = MAP_CTX_LAYER_GEOJSON_REMOTE_FIXTURE;
318
418
  layer = await createLayer(layerModel);
419
+ layer.on(
420
+ `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
421
+ eventCallback,
422
+ );
423
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
319
424
  });
320
425
  it("create a VectorLayer", () => {
321
426
  expect(layer).toBeTruthy();
@@ -335,6 +440,16 @@ describe("MapContextService", () => {
335
440
  (layerModel as MapContextLayerGeojson).url,
336
441
  );
337
442
  });
443
+ it("emits a loaded event as well as data info eventually", async () => {
444
+ await vi.runAllTimersAsync();
445
+ expect(eventCallback).toHaveBeenCalledWith({
446
+ layerState: {
447
+ loaded: true,
448
+ },
449
+ target: layer,
450
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
451
+ });
452
+ });
338
453
  });
339
454
  });
340
455
 
@@ -342,6 +457,8 @@ describe("MapContextService", () => {
342
457
  beforeEach(async () => {
343
458
  layerModel = MAP_CTX_LAYER_WMTS_FIXTURE;
344
459
  layer = await createLayer(layerModel);
460
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-loading-status`, eventCallback);
461
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
345
462
  });
346
463
  it("create a tile layer", () => {
347
464
  expect(layer).toBeTruthy();
@@ -366,20 +483,15 @@ describe("MapContextService", () => {
366
483
  "https://services.geo.sg.ch/wss/service/SG00066_WMTS/guest/tile/1.0.0/SG00066/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}",
367
484
  ]);
368
485
  });
369
- it("should NOT call handleEndpointError", () => {
370
- expect(handleEndpointError).not.toHaveBeenCalled();
371
- });
372
- });
373
- describe("WMTS error", () => {
374
- beforeEach(async () => {
375
- layerModel = {
376
- ...MAP_CTX_LAYER_WMTS_FIXTURE,
377
- url: "https://wmts/error",
378
- };
379
- layer = await createLayer(layerModel);
380
- });
381
- it("should call handleEndpointError", () => {
382
- expect(handleEndpointError).toHaveBeenCalled();
486
+ it("emits a loaded event initially", async () => {
487
+ await vi.runAllTimersAsync();
488
+ expect(eventCallback).toHaveBeenCalledWith({
489
+ layerState: {
490
+ loaded: true,
491
+ },
492
+ target: layer,
493
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
494
+ });
383
495
  });
384
496
  });
385
497
  describe("WMTS without default style given", () => {
@@ -389,6 +501,8 @@ describe("MapContextService", () => {
389
501
  url: "https://wmts/no-default-style",
390
502
  };
391
503
  layer = await createLayer(layerModel);
504
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-loading-status`, eventCallback);
505
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
392
506
  });
393
507
  it("uses the first style as default", async () => {
394
508
  const source = layer.getSource() as WMTS;
@@ -400,6 +514,8 @@ describe("MapContextService", () => {
400
514
  beforeEach(async () => {
401
515
  layerModel = MAP_CTX_LAYER_MAPBLIBRE_STYLE_FIXTURE;
402
516
  layer = await createLayer(layerModel);
517
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-loading-status`, eventCallback);
518
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
403
519
  });
404
520
  it("create a tile layer", () => {
405
521
  expect(layer).toBeTruthy();
@@ -414,12 +530,24 @@ describe("MapContextService", () => {
414
530
  const source = layer.getSource();
415
531
  expect(source).toBeInstanceOf(VectorTile);
416
532
  });
533
+ it("emits a loaded event initially", async () => {
534
+ await vi.runAllTimersAsync();
535
+ expect(eventCallback).toHaveBeenCalledWith({
536
+ layerState: {
537
+ loaded: true,
538
+ },
539
+ target: layer,
540
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
541
+ });
542
+ });
417
543
  });
418
544
 
419
545
  describe("MVT", () => {
420
546
  beforeEach(async () => {
421
547
  layerModel = MAP_CTX_LAYER_MVT_FIXTURE;
422
548
  layer = await createLayer(layerModel);
549
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-loading-status`, eventCallback);
550
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, eventCallback);
423
551
  });
424
552
  it("create a VectorTileLayer", () => {
425
553
  expect(layer).toBeTruthy();
@@ -434,6 +562,16 @@ describe("MapContextService", () => {
434
562
  expect(layer.getOpacity()).toBe(1);
435
563
  expect(layer.get("label")).toBeUndefined();
436
564
  });
565
+ it("emits a loaded event initially", async () => {
566
+ await vi.runAllTimersAsync();
567
+ expect(eventCallback).toHaveBeenCalledWith({
568
+ layerState: {
569
+ loaded: true,
570
+ },
571
+ target: layer,
572
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
573
+ });
574
+ });
437
575
  });
438
576
 
439
577
  describe("GeoTIFF", () => {
@@ -25,16 +25,14 @@ import OGCVectorTile from "ol/source/OGCVectorTile.js";
25
25
  import WMTS from "ol/source/WMTS.js";
26
26
  import MVT from "ol/format/MVT.js";
27
27
  import {
28
+ EndpointError,
28
29
  OgcApiEndpoint,
29
30
  WfsEndpoint,
30
31
  WmtsEndpoint,
31
32
  } from "@camptocamp/ogc-client";
32
33
  import { MapboxVectorLayer } from "ol-mapbox-style";
33
34
  import { Tile } from "ol";
34
- import {
35
- handleEndpointError,
36
- tileLoadErrorCatchFunction,
37
- } from "./handle-errors.js";
35
+ import { tileLoadErrorCatchFunction } from "./handle-errors.js";
38
36
  import VectorTile from "ol/source/VectorTile.js";
39
37
  import GeoTIFF from "ol/source/GeoTIFF.js";
40
38
  import WebGLTileLayer from "ol/layer/WebGLTile.js";
@@ -45,6 +43,15 @@ import {
45
43
  updateLayerProperties,
46
44
  } from "./layer-update.js";
47
45
  import { initHoverLayer } from "./feature-hover.js";
46
+ import {
47
+ emitLayerCreationError,
48
+ emitLayerLoadingError,
49
+ emitLayerLoadingStatusSuccess,
50
+ propagateLayerStateChangeEventToMap,
51
+ } from "./register-events.js";
52
+ import { GEOSPATIAL_SDK_PREFIX } from "./constants.js";
53
+ import ImageLayer from "ol/layer/Image.js";
54
+ import ImageWMS from "ol/source/ImageWMS.js";
48
55
 
49
56
  // Register proj4 with OpenLayers so that arbitrary EPSG codes
50
57
  // (e.g., UTM zones from GeoTIFF metadata) can be reprojected to the map projection
@@ -53,9 +60,15 @@ register(proj4);
53
60
  const GEOJSON = new GeoJSON();
54
61
  const WFS_MAX_FEATURES = 10000;
55
62
 
63
+ // We need to defer some events being dispatched to make sure they are caught by the map
64
+ // where the layers sit
65
+ // FIXME: this should be better handled in a separate module!
66
+ const defer = () => new Promise((resolve) => setTimeout(resolve, 0));
67
+
56
68
  export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
57
69
  const { type } = layerModel;
58
- let layer: Layer | undefined;
70
+ let layer: Layer;
71
+
59
72
  switch (type) {
60
73
  case "xyz":
61
74
  {
@@ -82,31 +95,46 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
82
95
  });
83
96
  layer.setSource(source);
84
97
  }
98
+ defer().then(() => emitLayerLoadingStatusSuccess(layer));
85
99
  }
86
100
  break;
101
+
87
102
  case "wms":
88
103
  {
89
- layer = new TileLayer({});
90
- const source = new TileWMS({
91
- url: removeSearchParams(layerModel.url, ["request", "service"]),
92
- params: {
93
- LAYERS: layerModel.name,
94
- ...(layerModel.style && { STYLES: layerModel.style }),
95
- },
96
- gutter: 20,
97
- attributions: layerModel.attributions,
98
- });
99
- source.setTileLoadFunction(function (tile: Tile, src: string) {
100
- return tileLoadErrorCatchFunction(
101
- layer as TileLayer<TileWMS>,
102
- tile,
103
- src,
104
- );
105
- });
106
- layer.setSource(source);
104
+ const url = removeSearchParams(layerModel.url, ["request", "service"]);
105
+ const params = {
106
+ LAYERS: layerModel.name,
107
+ ...(layerModel.style && { STYLES: layerModel.style }),
108
+ };
109
+ if (layerModel.useTiles === false) {
110
+ layer = new ImageLayer({
111
+ source: new ImageWMS({
112
+ url,
113
+ params,
114
+ attributions: layerModel.attributions,
115
+ }),
116
+ });
117
+ } else {
118
+ layer = new TileLayer({
119
+ source: new TileWMS({
120
+ url,
121
+ params: { ...params, TILED: true },
122
+ gutter: 20,
123
+ attributions: layerModel.attributions,
124
+ tileLoadFunction: function (tile: Tile, src: string) {
125
+ return tileLoadErrorCatchFunction(
126
+ layer as TileLayer<TileWMS>,
127
+ tile,
128
+ src,
129
+ );
130
+ },
131
+ }),
132
+ });
133
+ }
134
+ defer().then(() => emitLayerLoadingStatusSuccess(layer));
107
135
  }
108
-
109
136
  break;
137
+
110
138
  case "wmts": {
111
139
  const olLayer = new TileLayer({});
112
140
  const endpoint = new WmtsEndpoint(layerModel.url);
@@ -138,11 +166,16 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
138
166
  }),
139
167
  );
140
168
  })
169
+ .then(() => emitLayerLoadingStatusSuccess(olLayer))
141
170
  .catch((e) => {
142
- handleEndpointError(olLayer, e);
171
+ const httpStatus =
172
+ e instanceof EndpointError ? e.httpStatus : undefined;
173
+ emitLayerLoadingError(olLayer, e, httpStatus);
143
174
  });
144
- return olLayer;
175
+ layer = olLayer;
176
+ break;
145
177
  }
178
+
146
179
  case "wfs": {
147
180
  const olLayer = new VectorLayer({
148
181
  style: layerModel.style ?? defaultStyle,
@@ -169,28 +202,35 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
169
202
  }),
170
203
  );
171
204
  })
205
+ .then(() => emitLayerLoadingStatusSuccess(olLayer))
172
206
  .catch((e) => {
173
- handleEndpointError(olLayer, e);
207
+ const httpStatus =
208
+ e instanceof EndpointError ? e.httpStatus : undefined;
209
+ emitLayerLoadingError(olLayer, e, httpStatus);
174
210
  });
175
211
  layer = olLayer;
176
212
  break;
177
213
  }
214
+
178
215
  case "maplibre-style": {
179
216
  layer = new MapboxVectorLayer({
180
217
  styleUrl: layerModel.styleUrl,
181
218
  accessToken: layerModel.accessToken,
182
219
  }) as unknown as Layer;
220
+ defer().then(() => emitLayerLoadingStatusSuccess(layer));
183
221
  break;
184
222
  }
223
+
185
224
  case "geojson": {
225
+ layer = new VectorLayer({
226
+ style: layerModel.style ?? defaultStyle,
227
+ });
228
+ let source: VectorSource;
186
229
  if (layerModel.url !== undefined) {
187
- layer = new VectorLayer({
188
- source: new VectorSource({
189
- format: new GeoJSON(),
190
- url: layerModel.url,
191
- attributions: layerModel.attributions,
192
- }),
193
- style: layerModel.style ?? defaultStyle,
230
+ source = new VectorSource({
231
+ format: new GeoJSON(),
232
+ url: layerModel.url,
233
+ attributions: layerModel.attributions,
194
234
  });
195
235
  } else {
196
236
  let geojson = layerModel.data;
@@ -206,16 +246,17 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
206
246
  featureProjection: "EPSG:3857",
207
247
  dataProjection: "EPSG:4326",
208
248
  }) as Feature<Geometry>[];
209
- layer = new VectorLayer({
210
- source: new VectorSource({
211
- features,
212
- attributions: layerModel.attributions,
213
- }),
214
- style: layerModel.style ?? defaultStyle,
249
+ source = new VectorSource({
250
+ features,
251
+ attributions: layerModel.attributions,
215
252
  });
216
253
  }
254
+ layer.setSource(source);
255
+ // FIXME: actually track layer loading and data info
256
+ defer().then(() => emitLayerLoadingStatusSuccess(layer));
217
257
  break;
218
258
  }
259
+
219
260
  case "ogcapi": {
220
261
  const ogcEndpoint = new OgcApiEndpoint(layerModel.url);
221
262
  let layerUrl: string;
@@ -249,17 +290,21 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
249
290
  layerModel.collection,
250
291
  layerModel.options,
251
292
  );
293
+ const source = new VectorSource({
294
+ format: new GeoJSON(),
295
+ url: layerUrl,
296
+ attributions: layerModel.attributions,
297
+ });
252
298
  layer = new VectorLayer({
253
- source: new VectorSource({
254
- format: new GeoJSON(),
255
- url: layerUrl,
256
- attributions: layerModel.attributions,
257
- }),
299
+ source,
258
300
  style: layerModel.style ?? defaultStyle,
259
301
  });
260
302
  }
303
+ // FIXME: actually track layer loading
304
+ defer().then(() => emitLayerLoadingStatusSuccess(layer));
261
305
  break;
262
306
  }
307
+
263
308
  case "geotiff": {
264
309
  const geoTiffSource = new GeoTIFF({
265
310
  sources: [{ url: layerModel.url }],
@@ -268,26 +313,38 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
268
313
  layer = new WebGLTileLayer({
269
314
  source: geoTiffSource,
270
315
  });
316
+ // FIXME: actually track tile loading
317
+ defer().then(() => emitLayerLoadingStatusSuccess(layer));
271
318
  break;
272
319
  }
273
- default:
274
- throw new Error(`Unrecognized layer type: ${JSON.stringify(layerModel)}`);
275
- }
276
- if (!layer) {
277
- throw new Error(`Layer could not be created for type: ${layerModel.type}`);
320
+
321
+ default: {
322
+ // we create an empty placeholder layer so that we still have a corresponding layer in OL
323
+ layer = new VectorLayer({
324
+ properties: {
325
+ [`${GEOSPATIAL_SDK_PREFIX}layer-with-error`]: true,
326
+ },
327
+ });
328
+ defer().then(() =>
329
+ emitLayerCreationError(
330
+ layer,
331
+ new Error(`Unrecognized layer type: ${JSON.stringify(layerModel)}`),
332
+ ),
333
+ );
334
+ }
278
335
  }
279
336
 
280
- updateLayerProperties(layerModel, layer);
337
+ updateLayerProperties(layerModel, layer!);
281
338
 
282
- return layer;
339
+ return layer!;
283
340
  }
284
341
 
285
- export function updateLayerInMap(
342
+ export async function updateLayerInMap(
286
343
  map: Map,
287
344
  layerModel: MapContextLayer,
288
345
  layerPosition: number,
289
346
  previousLayerModel: MapContextLayer,
290
- ) {
347
+ ): Promise<void> {
291
348
  const layers = map.getLayers();
292
349
  const updatedLayer = layers.item(layerPosition) as Layer;
293
350
 
@@ -299,8 +356,9 @@ export function updateLayerInMap(
299
356
 
300
357
  // dispose and recreate layer
301
358
  updatedLayer.dispose();
302
- createLayer(layerModel).then((layer) => {
359
+ await createLayer(layerModel).then((layer) => {
303
360
  layers.setAt(layerPosition, layer);
361
+ propagateLayerStateChangeEventToMap(map, layer);
304
362
  });
305
363
  }
306
364
 
@@ -369,6 +427,7 @@ export async function resetMapFromContext(
369
427
  for (const layerModel of context.layers) {
370
428
  const layer = await createLayer(layerModel);
371
429
  map.addLayer(layer);
430
+ propagateLayerStateChangeEventToMap(map, layer);
372
431
  }
373
432
  initHoverLayer(map);
374
433
  return map;
@@ -108,7 +108,7 @@ export function initHoverLayer(map: OlMap) {
108
108
  );
109
109
  const features = Array.from(featuresByLayer.values()).flat();
110
110
  map.dispatchEvent({
111
- type: FeaturesHoverEventType,
111
+ type: `${GEOSPATIAL_SDK_PREFIX}${FeaturesHoverEventType}`,
112
112
  features,
113
113
  featuresByLayer,
114
114
  } as unknown as BaseEvent);