@geospatial-sdk/openlayers 0.0.5-dev.32 → 0.0.5-dev.34

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 +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,EAEf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,GAAG,MAAM,QAAQ,CAAC;AACzB,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,KAAK,MAAM,gBAAgB,CAAC;AA4BnC,wBAAsB,WAAW,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAuL7E;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CA+B3E;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,UAAU,EACnB,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,GAC5B,OAAO,CAAC,GAAG,CAAC,CAKd;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,GAAG,CAAC,CAQd"}
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,EAEf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,GAAG,MAAM,QAAQ,CAAC;AACzB,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,KAAK,MAAM,gBAAgB,CAAC;AAiCnC,wBAAsB,WAAW,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAgN7E;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CA+B3E;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,UAAU,EACnB,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,GAC5B,OAAO,CAAC,GAAG,CAAC,CAKd;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,GAAG,CAAC,CAQd"}
@@ -26,6 +26,7 @@ import WMTS from "ol/source/WMTS";
26
26
  import MVT from "ol/format/MVT";
27
27
  import { OgcApiEndpoint, WfsEndpoint, WmtsEndpoint, } from "@camptocamp/ogc-client";
28
28
  import { MapboxVectorLayer } from "ol-mapbox-style";
29
+ import { handleEndpointError, tileLoadErrorCatchFunction, } from "./handle-errors";
29
30
  const GEOJSON = new GeoJSON();
30
31
  const WFS_MAX_FEATURES = 10000;
31
32
  export function createLayer(layerModel) {
@@ -35,27 +36,39 @@ export function createLayer(layerModel) {
35
36
  let layer;
36
37
  switch (type) {
37
38
  case "xyz":
38
- layer = new TileLayer({
39
- source: new XYZ({
39
+ {
40
+ layer = new TileLayer({});
41
+ const source = new XYZ({
40
42
  url: layerModel.url,
41
43
  attributions: layerModel.attributions,
42
- }),
43
- });
44
+ });
45
+ source.setTileLoadFunction(function (tile, src) {
46
+ return tileLoadErrorCatchFunction(layer, tile, src);
47
+ });
48
+ layer.setSource(source);
49
+ }
44
50
  break;
45
51
  case "wms":
46
- layer = new TileLayer({
47
- source: new TileWMS({
52
+ {
53
+ layer = new TileLayer({});
54
+ const source = new TileWMS({
48
55
  url: removeSearchParams(layerModel.url, ["request", "service"]),
49
56
  params: Object.assign({ LAYERS: layerModel.name }, (layerModel.style && { STYLES: layerModel.style })),
50
57
  gutter: 20,
51
58
  attributions: layerModel.attributions,
52
- }),
53
- });
59
+ });
60
+ source.setTileLoadFunction(function (tile, src) {
61
+ return tileLoadErrorCatchFunction(layer, tile, src);
62
+ });
63
+ layer.setSource(source);
64
+ }
54
65
  break;
55
66
  case "wmts": {
56
67
  const olLayer = new TileLayer({});
57
68
  const endpoint = new WmtsEndpoint(layerModel.url);
58
- endpoint.isReady().then((endpoint) => __awaiter(this, void 0, void 0, function* () {
69
+ endpoint
70
+ .isReady()
71
+ .then((endpoint) => __awaiter(this, void 0, void 0, function* () {
59
72
  var _f;
60
73
  const layerName = (_f = endpoint.getSingleLayerName()) !== null && _f !== void 0 ? _f : layerModel.name;
61
74
  const layer = endpoint.getLayerByName(layerName);
@@ -79,14 +92,19 @@ export function createLayer(layerModel) {
79
92
  dimensions,
80
93
  attributions: layerModel.attributions,
81
94
  }));
82
- }));
95
+ }))
96
+ .catch((e) => {
97
+ handleEndpointError(olLayer, e);
98
+ });
83
99
  return olLayer;
84
100
  }
85
101
  case "wfs": {
86
102
  const olLayer = new VectorLayer({
87
103
  style: (_a = layerModel.style) !== null && _a !== void 0 ? _a : defaultStyle,
88
104
  });
89
- new WfsEndpoint(layerModel.url).isReady().then((endpoint) => {
105
+ new WfsEndpoint(layerModel.url)
106
+ .isReady()
107
+ .then((endpoint) => {
90
108
  var _a;
91
109
  const featureType = (_a = endpoint.getSingleFeatureTypeName()) !== null && _a !== void 0 ? _a : layerModel.featureType;
92
110
  olLayer.setSource(new VectorSource({
@@ -103,6 +121,9 @@ export function createLayer(layerModel) {
103
121
  strategy: bboxStrategy,
104
122
  attributions: layerModel.attributions,
105
123
  }));
124
+ })
125
+ .catch((e) => {
126
+ handleEndpointError(olLayer, e);
106
127
  });
107
128
  layer = olLayer;
108
129
  break;
@@ -0,0 +1,11 @@
1
+ import { EndpointError } from "@camptocamp/ogc-client";
2
+ import { Tile } from "ol";
3
+ import { Layer } from "ol/layer";
4
+ import TileLayer from "ol/layer/Tile";
5
+ import VectorLayer from "ol/layer/Vector";
6
+ import TileSource from "ol/source/Tile";
7
+ import VectorSource from "ol/source/Vector";
8
+ export declare function handleEndpointError(layer: TileLayer<TileSource> | VectorLayer<VectorSource>, error: EndpointError): void;
9
+ export declare function handleTileError(response: Response | Error, tile: Tile, layer: Layer): void;
10
+ export declare function tileLoadErrorCatchFunction(layer: Layer, tile: Tile, src: string): void;
11
+ //# sourceMappingURL=handle-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle-errors.d.ts","sourceRoot":"","sources":["../../lib/map/handle-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EAAa,IAAI,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,SAAS,MAAM,eAAe,CAAC;AACtC,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAC1C,OAAO,UAAU,MAAM,gBAAgB,CAAC;AACxC,OAAO,YAAY,MAAM,kBAAkB,CAAC;AAG5C,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,SAAS,CAAC,UAAU,CAAC,GAAG,WAAW,CAAC,YAAY,CAAC,EACxD,KAAK,EAAE,aAAa,QAIrB;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,QAAQ,GAAG,KAAK,EAC1B,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,KAAK,QAKb;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,QAqBZ"}
@@ -0,0 +1,33 @@
1
+ import { SourceLoadErrorEvent } from "@geospatial-sdk/core";
2
+ import TileState from "ol/TileState.js";
3
+ export function handleEndpointError(layer, error) {
4
+ console.error("Error loading Endpoint", error);
5
+ layer.dispatchEvent(new SourceLoadErrorEvent(error));
6
+ }
7
+ export function handleTileError(response, tile, layer) {
8
+ console.error("Error loading tile", response);
9
+ tile.setState(TileState.ERROR);
10
+ layer.dispatchEvent(new SourceLoadErrorEvent(response));
11
+ }
12
+ export function tileLoadErrorCatchFunction(layer, tile, src) {
13
+ fetch(src)
14
+ .then((response) => {
15
+ if (response.ok) {
16
+ response
17
+ .blob()
18
+ .then((blob) => {
19
+ const image = tile.getImage();
20
+ image.src = URL.createObjectURL(blob);
21
+ })
22
+ .catch(() => {
23
+ handleTileError(response, tile, layer);
24
+ });
25
+ }
26
+ else {
27
+ handleTileError(response, tile, layer);
28
+ }
29
+ })
30
+ .catch((error) => {
31
+ handleTileError(error, tile, layer);
32
+ });
33
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"register-events.d.ts","sourceRoot":"","sources":["../../lib/map/register-events.ts"],"names":[],"mappings":"AAAA,OAAO,GAA4B,MAAM,QAAQ,CAAC;AAClD,OAAO,EAIL,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,OAAO,MAAM,mBAAmB,CAAC;AACxC,OAAO,QAAQ,MAAM,oBAAoB,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAK1D,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,GAAG,EACV,KAAK,EAAE,KAAK,GACX,OAAO,EAAE,CAOX;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,OAAO,GAAG,QAAQ,EAC1B,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,UAAU,GACrB,MAAM,GAAG,IAAI,CAWf;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,GAAG,EACV,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,OAAO,EAAE,CAAC,CA2BpB;AAyCD,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,eAAe,EACpD,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,QAgC9C"}
1
+ {"version":3,"file":"register-events.d.ts","sourceRoot":"","sources":["../../lib/map/register-events.ts"],"names":[],"mappings":"AAAA,OAAO,GAA4B,MAAM,QAAQ,CAAC;AAClD,OAAO,EAIL,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,OAAO,MAAM,mBAAmB,CAAC;AACxC,OAAO,QAAQ,MAAM,oBAAoB,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAM1D,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,GAAG,EACV,KAAK,EAAE,KAAK,GACX,OAAO,EAAE,CAOX;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,OAAO,GAAG,QAAQ,EAC1B,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,UAAU,GACrB,MAAM,GAAG,IAAI,CAWf;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,GAAG,EACV,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,OAAO,EAAE,CAAC,CA2BpB;AAyCD,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,eAAe,EACpD,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,QAmE9C"}
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { FeaturesClickEventType, FeaturesHoverEventType, MapClickEventType, } from "@geospatial-sdk/core";
10
+ import { FeaturesClickEventType, FeaturesHoverEventType, MapClickEventType, SourceLoadErrorType, } from "@geospatial-sdk/core";
11
11
  import { toLonLat } from "ol/proj";
12
12
  import GeoJSON from "ol/format/GeoJSON";
13
13
  import TileWMS from "ol/source/TileWMS";
@@ -108,6 +108,32 @@ export function listen(map, eventType, callback) {
108
108
  });
109
109
  });
110
110
  break;
111
+ case SourceLoadErrorType: {
112
+ const errorCallback = (event) => {
113
+ callback(event);
114
+ };
115
+ //attach event listener to all existing layers
116
+ map.getLayers().forEach((layer) => {
117
+ if (layer) {
118
+ layer.on(SourceLoadErrorType, errorCallback);
119
+ }
120
+ });
121
+ //attach event listener when layer is added
122
+ map.getLayers().on("add", (event) => {
123
+ const layer = event.element;
124
+ if (layer) {
125
+ layer.on(SourceLoadErrorType, errorCallback);
126
+ }
127
+ });
128
+ //remove event listener when layer is removed
129
+ map.getLayers().on("remove", (event) => {
130
+ const layer = event.element;
131
+ if (layer) {
132
+ layer.un(SourceLoadErrorType, errorCallback);
133
+ }
134
+ });
135
+ break;
136
+ }
111
137
  default:
112
138
  throw new Error(`Unrecognized event type: ${eventType}`);
113
139
  }
@@ -65,7 +65,7 @@ describe("applyContextDiffToMap", () => {
65
65
  });
66
66
 
67
67
  describe("layers added", () => {
68
- beforeEach(() => {
68
+ beforeEach(async () => {
69
69
  diff = {
70
70
  layersAdded: [
71
71
  {
@@ -81,7 +81,7 @@ describe("applyContextDiffToMap", () => {
81
81
  layersRemoved: [],
82
82
  layersReordered: [],
83
83
  };
84
- applyContextDiffToMap(map, diff);
84
+ await applyContextDiffToMap(map, diff);
85
85
  layersArray = map.getLayers().getArray();
86
86
  });
87
87
  it("adds the layers to the map", () => {
@@ -24,6 +24,7 @@ import {
24
24
  MapContextLayer,
25
25
  MapContextLayerGeojson,
26
26
  MapContextLayerWms,
27
+ SourceLoadErrorEvent,
27
28
  } from "@geospatial-sdk/core";
28
29
  import Layer from "ol/layer/Layer";
29
30
  import {
@@ -35,6 +36,21 @@ import {
35
36
  import WMTS from "ol/source/WMTS";
36
37
  import { VectorTile } from "ol/source";
37
38
  import { MapboxVectorLayer } from "ol-mapbox-style";
39
+ import {
40
+ handleEndpointError,
41
+ tileLoadErrorCatchFunction,
42
+ } from "./handle-errors";
43
+ import { ImageTile } from "ol";
44
+ import TileState from "ol/TileState.js";
45
+
46
+ vi.mock("./handle-errors", async (importOriginal) => {
47
+ const actual = await importOriginal();
48
+ return {
49
+ ...actual,
50
+ tileLoadErrorCatchFunction: vi.fn(),
51
+ handleEndpointError: vi.fn(),
52
+ };
53
+ });
38
54
 
39
55
  describe("MapContextService", () => {
40
56
  describe("#createLayer", () => {
@@ -67,6 +83,20 @@ describe("MapContextService", () => {
67
83
  "https://a.tile.openstreetmap.org/{z}/{x}/{y}.png",
68
84
  );
69
85
  });
86
+ it("should set tileLoadErrorCatchFunction to handle errors", () => {
87
+ const source = layer.getSource() as XYZ;
88
+ const tileLoadFunction = source.getTileLoadFunction();
89
+ expect(tileLoadFunction).toBeInstanceOf(Function);
90
+ const tile = new ImageTile(
91
+ [0, 0, 0],
92
+ TileState.IDLE,
93
+ "",
94
+ null,
95
+ () => {},
96
+ );
97
+ tileLoadFunction(tile, "http://example.com/tile");
98
+ expect(tileLoadErrorCatchFunction).toHaveBeenCalled();
99
+ });
70
100
  });
71
101
  describe("OGCAPI", () => {
72
102
  beforeEach(async () => {
@@ -134,6 +164,20 @@ describe("MapContextService", () => {
134
164
  const gutter = source["gutter_"];
135
165
  expect(gutter).toBe(20);
136
166
  });
167
+ it("should set tileLoadErrorCatchFunction to handle errors", () => {
168
+ const source = layer.getSource() as TileWMS;
169
+ const tileLoadFunction = source.getTileLoadFunction();
170
+ expect(tileLoadFunction).toBeInstanceOf(Function);
171
+ const tile = new ImageTile(
172
+ [0, 0, 0],
173
+ TileState.IDLE,
174
+ "",
175
+ null,
176
+ () => {},
177
+ );
178
+ tileLoadFunction(tile, "http://example.com/tile");
179
+ expect(tileLoadErrorCatchFunction).toHaveBeenCalled();
180
+ });
137
181
  });
138
182
 
139
183
  describe("WFS", () => {
@@ -168,6 +212,18 @@ describe("MapContextService", () => {
168
212
  "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",
169
213
  );
170
214
  });
215
+ it("should NOT call handleEndpointError", () => {
216
+ expect(handleEndpointError).not.toHaveBeenCalled();
217
+ });
218
+ });
219
+ describe("WFS error", () => {
220
+ beforeEach(async () => {
221
+ layerModel = { ...MAP_CTX_LAYER_WFS_FIXTURE, url: "https://wfs/error" };
222
+ layer = await createLayer(layerModel);
223
+ });
224
+ it("should call handleEndpointError", () => {
225
+ expect(handleEndpointError).toHaveBeenCalled();
226
+ });
171
227
  });
172
228
 
173
229
  describe("GEOJSON", () => {
@@ -275,6 +331,9 @@ describe("MapContextService", () => {
275
331
  (layerModel = MAP_CTX_LAYER_WMTS_FIXTURE),
276
332
  (layer = await createLayer(layerModel));
277
333
  });
334
+ afterEach(() => {
335
+ vi.clearAllMocks();
336
+ });
278
337
  it("create a tile layer", () => {
279
338
  expect(layer).toBeTruthy();
280
339
  expect(layer).toBeInstanceOf(TileLayer);
@@ -296,6 +355,24 @@ describe("MapContextService", () => {
296
355
  "https://services.geo.sg.ch/wss/service/SG00066_WMTS/guest/tile/1.0.0/SG00066/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}",
297
356
  ]);
298
357
  });
358
+ it("should NOT call handleEndpointError", () => {
359
+ expect(handleEndpointError).not.toHaveBeenCalled();
360
+ });
361
+ });
362
+ describe("WMTS error", () => {
363
+ beforeEach(async () => {
364
+ layerModel = {
365
+ ...MAP_CTX_LAYER_WMTS_FIXTURE,
366
+ url: "https://wmts/error",
367
+ };
368
+ layer = await createLayer(layerModel);
369
+ });
370
+ it("should call handleEndpointError", () => {
371
+ expect(handleEndpointError).toHaveBeenCalled();
372
+ });
373
+ afterEach(() => {
374
+ vi.clearAllMocks();
375
+ });
299
376
  });
300
377
 
301
378
  describe("Maplibre Style", () => {
@@ -30,6 +30,11 @@ import {
30
30
  WmtsEndpoint,
31
31
  } from "@camptocamp/ogc-client";
32
32
  import { MapboxVectorLayer } from "ol-mapbox-style";
33
+ import { Tile } from "ol";
34
+ import {
35
+ handleEndpointError,
36
+ tileLoadErrorCatchFunction,
37
+ } from "./handle-errors";
33
38
 
34
39
  const GEOJSON = new GeoJSON();
35
40
  const WFS_MAX_FEATURES = 10000;
@@ -39,16 +44,22 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
39
44
  let layer: Layer | undefined;
40
45
  switch (type) {
41
46
  case "xyz":
42
- layer = new TileLayer({
43
- source: new XYZ({
47
+ {
48
+ layer = new TileLayer({});
49
+ const source = new XYZ({
44
50
  url: layerModel.url,
45
51
  attributions: layerModel.attributions,
46
- }),
47
- });
52
+ });
53
+ source.setTileLoadFunction(function (tile: Tile, src: string) {
54
+ return tileLoadErrorCatchFunction(layer as TileLayer<XYZ>, tile, src);
55
+ });
56
+ layer.setSource(source);
57
+ }
48
58
  break;
49
59
  case "wms":
50
- layer = new TileLayer({
51
- source: new TileWMS({
60
+ {
61
+ layer = new TileLayer({});
62
+ const source = new TileWMS({
52
63
  url: removeSearchParams(layerModel.url, ["request", "service"]),
53
64
  params: {
54
65
  LAYERS: layerModel.name,
@@ -56,64 +67,83 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
56
67
  },
57
68
  gutter: 20,
58
69
  attributions: layerModel.attributions,
59
- }),
60
- });
70
+ });
71
+ source.setTileLoadFunction(function (tile: Tile, src: string) {
72
+ return tileLoadErrorCatchFunction(
73
+ layer as TileLayer<TileWMS>,
74
+ tile,
75
+ src,
76
+ );
77
+ });
78
+ layer.setSource(source);
79
+ }
80
+
61
81
  break;
62
82
  case "wmts": {
63
83
  const olLayer = new TileLayer({});
64
84
  const endpoint = new WmtsEndpoint(layerModel.url);
65
- endpoint.isReady().then(async (endpoint) => {
66
- const layerName = endpoint.getSingleLayerName() ?? layerModel.name;
67
- const layer = endpoint.getLayerByName(layerName);
68
- const matrixSet = layer.matrixSets[0];
69
- const tileGrid = await endpoint.getOpenLayersTileGrid(layer.name);
70
- if (tileGrid === null) {
71
- console.warn("A WMTS tile grid could not be created", layerModel);
72
- return;
73
- }
74
- const resourceUrl = layer.resourceLinks[0];
75
- const dimensions = endpoint.getDefaultDimensions(layer.name);
76
- olLayer.setSource(
77
- new WMTS({
78
- layer: layer.name,
79
- style: layer.defaultStyle,
80
- matrixSet: matrixSet.identifier,
81
- format: resourceUrl.format,
82
- url: resourceUrl.url,
83
- requestEncoding: resourceUrl.encoding,
84
- tileGrid,
85
- projection: matrixSet.crs,
86
- dimensions,
87
- attributions: layerModel.attributions,
88
- }),
89
- );
90
- });
85
+ endpoint
86
+ .isReady()
87
+ .then(async (endpoint) => {
88
+ const layerName = endpoint.getSingleLayerName() ?? layerModel.name;
89
+ const layer = endpoint.getLayerByName(layerName);
90
+ const matrixSet = layer.matrixSets[0];
91
+ const tileGrid = await endpoint.getOpenLayersTileGrid(layer.name);
92
+ if (tileGrid === null) {
93
+ console.warn("A WMTS tile grid could not be created", layerModel);
94
+ return;
95
+ }
96
+ const resourceUrl = layer.resourceLinks[0];
97
+ const dimensions = endpoint.getDefaultDimensions(layer.name);
98
+ olLayer.setSource(
99
+ new WMTS({
100
+ layer: layer.name,
101
+ style: layer.defaultStyle,
102
+ matrixSet: matrixSet.identifier,
103
+ format: resourceUrl.format,
104
+ url: resourceUrl.url,
105
+ requestEncoding: resourceUrl.encoding,
106
+ tileGrid,
107
+ projection: matrixSet.crs,
108
+ dimensions,
109
+ attributions: layerModel.attributions,
110
+ }),
111
+ );
112
+ })
113
+ .catch((e) => {
114
+ handleEndpointError(olLayer, e);
115
+ });
91
116
  return olLayer;
92
117
  }
93
118
  case "wfs": {
94
119
  const olLayer = new VectorLayer({
95
120
  style: layerModel.style ?? defaultStyle,
96
121
  });
97
- new WfsEndpoint(layerModel.url).isReady().then((endpoint) => {
98
- const featureType =
99
- endpoint.getSingleFeatureTypeName() ?? layerModel.featureType;
100
- olLayer.setSource(
101
- new VectorSource({
102
- format: new GeoJSON(),
103
- url: function (extent) {
104
- return endpoint.getFeatureUrl(featureType, {
105
- maxFeatures: WFS_MAX_FEATURES,
106
- asJson: true,
107
- outputCrs: "EPSG:3857",
108
- extent: extent as [number, number, number, number],
109
- extentCrs: "EPSG:3857",
110
- });
111
- },
112
- strategy: bboxStrategy,
113
- attributions: layerModel.attributions,
114
- }),
115
- );
116
- });
122
+ new WfsEndpoint(layerModel.url)
123
+ .isReady()
124
+ .then((endpoint) => {
125
+ const featureType =
126
+ endpoint.getSingleFeatureTypeName() ?? layerModel.featureType;
127
+ olLayer.setSource(
128
+ new VectorSource({
129
+ format: new GeoJSON(),
130
+ url: function (extent) {
131
+ return endpoint.getFeatureUrl(featureType, {
132
+ maxFeatures: WFS_MAX_FEATURES,
133
+ asJson: true,
134
+ outputCrs: "EPSG:3857",
135
+ extent: extent as [number, number, number, number],
136
+ extentCrs: "EPSG:3857",
137
+ });
138
+ },
139
+ strategy: bboxStrategy,
140
+ attributions: layerModel.attributions,
141
+ }),
142
+ );
143
+ })
144
+ .catch((e) => {
145
+ handleEndpointError(olLayer, e);
146
+ });
117
147
  layer = olLayer;
118
148
  break;
119
149
  }
@@ -0,0 +1,71 @@
1
+ import { ImageTile, Tile } from "ol";
2
+ import TileState from "ol/TileState.js";
3
+ import { SourceLoadErrorEvent } from "@geospatial-sdk/core";
4
+ import { handleTileError, tileLoadErrorCatchFunction } from "./handle-errors";
5
+ import { Map } from "ol";
6
+ import { describe } from "node:test";
7
+ import TileLayer from "ol/layer/Tile";
8
+ import VectorLayer from "ol/layer/Vector";
9
+ import { EndpointError } from "@camptocamp/ogc-client";
10
+
11
+ global.URL.createObjectURL = vi.fn(() => "blob:http://example.com/blob");
12
+
13
+ const mockBlob = new Blob();
14
+ const RESPONSE_OK = {
15
+ status: 200,
16
+ blob: vi.fn().mockResolvedValue(mockBlob),
17
+ };
18
+ const RESPONSE_ERROR = {
19
+ status: 404,
20
+ };
21
+ global.fetch = vi.fn().mockImplementation((url: string) => {
22
+ return url.includes("error")
23
+ ? Promise.reject(RESPONSE_ERROR as Response)
24
+ : Promise.resolve(RESPONSE_OK as Response);
25
+ });
26
+
27
+ describe("handle-errors", () => {
28
+ let map: Map;
29
+ let tile: Tile;
30
+
31
+ beforeEach(() => {
32
+ map = new Map();
33
+ tile = new ImageTile(
34
+ [0, 0, 0],
35
+ TileState.IDLE,
36
+ "",
37
+ null,
38
+ () => tileLoadErrorCatchFunction,
39
+ );
40
+ });
41
+
42
+ describe("handleEndpointError", () => {
43
+ it("should dispatch SourceLoadErrorEvent", () => {
44
+ const endpointErrorMock: EndpointError = {
45
+ name: "Error",
46
+ message: "FORBIDDEN",
47
+ httpStatus: 403,
48
+ };
49
+ const layer = new VectorLayer({});
50
+ const dispatchEventSpy = vi.spyOn(layer, "dispatchEvent");
51
+ handleTileError(endpointErrorMock, tile, layer);
52
+ expect(dispatchEventSpy).toHaveBeenCalledWith(
53
+ new SourceLoadErrorEvent(endpointErrorMock),
54
+ );
55
+ });
56
+ });
57
+
58
+ describe("handleTileError", () => {
59
+ it("should set tile state to ERROR and dispatch SourceLoadErrorEvent", () => {
60
+ const response = new Response("Forbidden", { status: 403 });
61
+ const layer = new TileLayer({});
62
+ const dispatchEventSpy = vi.spyOn(layer, "dispatchEvent");
63
+ const setStateEventSpy = vi.spyOn(tile, "setState");
64
+ handleTileError(response, tile, layer);
65
+ expect(setStateEventSpy).toHaveBeenCalledWith(TileState.ERROR);
66
+ expect(dispatchEventSpy).toHaveBeenCalledWith(
67
+ new SourceLoadErrorEvent(response),
68
+ );
69
+ });
70
+ });
71
+ });
@@ -0,0 +1,53 @@
1
+ import { EndpointError } from "@camptocamp/ogc-client";
2
+ import { SourceLoadErrorEvent } from "@geospatial-sdk/core";
3
+ import { ImageTile, Tile } from "ol";
4
+ import { Layer } from "ol/layer";
5
+ import TileLayer from "ol/layer/Tile";
6
+ import VectorLayer from "ol/layer/Vector";
7
+ import TileSource from "ol/source/Tile";
8
+ import VectorSource from "ol/source/Vector";
9
+ import TileState from "ol/TileState.js";
10
+
11
+ export function handleEndpointError(
12
+ layer: TileLayer<TileSource> | VectorLayer<VectorSource>,
13
+ error: EndpointError,
14
+ ) {
15
+ console.error("Error loading Endpoint", error);
16
+ layer.dispatchEvent(new SourceLoadErrorEvent(error));
17
+ }
18
+
19
+ export function handleTileError(
20
+ response: Response | Error,
21
+ tile: Tile,
22
+ layer: Layer,
23
+ ) {
24
+ console.error("Error loading tile", response);
25
+ tile.setState(TileState.ERROR);
26
+ layer.dispatchEvent(new SourceLoadErrorEvent(response));
27
+ }
28
+
29
+ export function tileLoadErrorCatchFunction(
30
+ layer: Layer,
31
+ tile: Tile,
32
+ src: string,
33
+ ) {
34
+ fetch(src)
35
+ .then((response) => {
36
+ if (response.ok) {
37
+ response
38
+ .blob()
39
+ .then((blob) => {
40
+ const image = (tile as ImageTile).getImage();
41
+ (image as HTMLImageElement).src = URL.createObjectURL(blob);
42
+ })
43
+ .catch(() => {
44
+ handleTileError(response, tile, layer);
45
+ });
46
+ } else {
47
+ handleTileError(response, tile, layer);
48
+ }
49
+ })
50
+ .catch((error) => {
51
+ handleTileError(error, tile, layer);
52
+ });
53
+ }
@@ -4,6 +4,7 @@ import {
4
4
  FeaturesHoverEventType,
5
5
  MapClickEventType,
6
6
  MapEventsByType,
7
+ SourceLoadErrorType,
7
8
  } from "@geospatial-sdk/core";
8
9
  import { toLonLat } from "ol/proj";
9
10
  import GeoJSON from "ol/format/GeoJSON";
@@ -17,6 +18,7 @@ import Layer from "ol/layer/Layer";
17
18
  import { Pixel } from "ol/pixel";
18
19
  import type { Feature, FeatureCollection } from "geojson";
19
20
  import throttle from "lodash.throttle";
21
+ import { BaseLayerObjectEventTypes } from "ol/layer/Base";
20
22
 
21
23
  const GEOJSON = new GeoJSON();
22
24
 
@@ -152,6 +154,41 @@ export function listen<T extends keyof MapEventsByType>(
152
154
  });
153
155
  });
154
156
  break;
157
+ case SourceLoadErrorType: {
158
+ const errorCallback = (event: BaseEvent) => {
159
+ (callback as (event: unknown) => void)(event);
160
+ };
161
+ //attach event listener to all existing layers
162
+ map.getLayers().forEach((layer) => {
163
+ if (layer) {
164
+ layer.on(
165
+ SourceLoadErrorType as unknown as BaseLayerObjectEventTypes,
166
+ errorCallback,
167
+ );
168
+ }
169
+ });
170
+ //attach event listener when layer is added
171
+ map.getLayers().on("add", (event) => {
172
+ const layer = event.element as Layer;
173
+ if (layer) {
174
+ layer.on(
175
+ SourceLoadErrorType as unknown as BaseLayerObjectEventTypes,
176
+ errorCallback,
177
+ );
178
+ }
179
+ });
180
+ //remove event listener when layer is removed
181
+ map.getLayers().on("remove", (event) => {
182
+ const layer = event.element as Layer;
183
+ if (layer) {
184
+ layer.un(
185
+ SourceLoadErrorType as unknown as BaseLayerObjectEventTypes,
186
+ errorCallback,
187
+ );
188
+ }
189
+ });
190
+ break;
191
+ }
155
192
  default:
156
193
  throw new Error(`Unrecognized event type: ${eventType}`);
157
194
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geospatial-sdk/openlayers",
3
- "version": "0.0.5-dev.32+04f31b7",
3
+ "version": "0.0.5-dev.34+fa36a68",
4
4
  "description": "OpenLayers-related utilities",
5
5
  "keywords": [
6
6
  "ol",
@@ -37,10 +37,10 @@
37
37
  "ol": ">6.x"
38
38
  },
39
39
  "dependencies": {
40
- "@geospatial-sdk/core": "^0.0.5-dev.32+04f31b7",
40
+ "@geospatial-sdk/core": "^0.0.5-dev.34+fa36a68",
41
41
  "chroma-js": "^2.4.2",
42
42
  "lodash.throttle": "^4.1.1",
43
43
  "ol-mapbox-style": "^12.3.5"
44
44
  },
45
- "gitHead": "04f31b7f16860b74a62c03bc5f2bac76f546ad22"
45
+ "gitHead": "fa36a68506520019014221820a92ce2ef8a10fd1"
46
46
  }