@geospatial-sdk/openlayers 0.0.5-dev.33 → 0.0.5-dev.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/map/create-map.d.ts.map +1 -1
- package/dist/map/create-map.js +34 -13
- package/dist/map/handle-errors.d.ts +11 -0
- package/dist/map/handle-errors.d.ts.map +1 -0
- package/dist/map/handle-errors.js +33 -0
- package/dist/map/register-events.d.ts.map +1 -1
- package/dist/map/register-events.js +27 -1
- package/lib/map/apply-context-diff.test.ts +2 -2
- package/lib/map/create-map.test.ts +78 -1
- package/lib/map/create-map.ts +91 -58
- package/lib/map/handle-errors.test.ts +71 -0
- package/lib/map/handle-errors.ts +53 -0
- package/lib/map/register-events.ts +37 -0
- package/package.json +3 -3
|
@@ -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;
|
|
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,CAkC3E;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"}
|
package/dist/map/create-map.js
CHANGED
|
@@ -16,7 +16,7 @@ import TileWMS from "ol/source/TileWMS";
|
|
|
16
16
|
import VectorLayer from "ol/layer/Vector";
|
|
17
17
|
import VectorSource from "ol/source/Vector";
|
|
18
18
|
import GeoJSON from "ol/format/GeoJSON";
|
|
19
|
-
import { fromLonLat } from "ol/proj";
|
|
19
|
+
import { fromLonLat, transformExtent } from "ol/proj";
|
|
20
20
|
import { bbox as bboxStrategy } from "ol/loadingstrategy";
|
|
21
21
|
import { defaultStyle } from "./styles";
|
|
22
22
|
import VectorTileLayer from "ol/layer/VectorTile";
|
|
@@ -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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
|
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)
|
|
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;
|
|
@@ -218,7 +239,7 @@ export function createView(viewModel, map) {
|
|
|
218
239
|
});
|
|
219
240
|
}
|
|
220
241
|
else if ("extent" in viewModel) {
|
|
221
|
-
view.fit(viewModel.extent, {
|
|
242
|
+
view.fit(transformExtent(viewModel.extent, "EPSG:4326", view.getProjection()), {
|
|
222
243
|
size: map.getSize(),
|
|
223
244
|
});
|
|
224
245
|
}
|
|
@@ -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,
|
|
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", () => {
|
|
@@ -357,7 +434,7 @@ describe("MapContextService", () => {
|
|
|
357
434
|
});
|
|
358
435
|
it("set center", () => {
|
|
359
436
|
const center = view.getCenter();
|
|
360
|
-
expect(center).toEqual([
|
|
437
|
+
expect(center).toEqual([317260.5487608297, 6623200.647707232]);
|
|
361
438
|
});
|
|
362
439
|
it("set zoom", () => {
|
|
363
440
|
const zoom = view.getZoom();
|
package/lib/map/create-map.ts
CHANGED
|
@@ -16,7 +16,7 @@ import GeoJSON from "ol/format/GeoJSON";
|
|
|
16
16
|
import Feature from "ol/Feature";
|
|
17
17
|
import Geometry from "ol/geom/Geometry";
|
|
18
18
|
import SimpleGeometry from "ol/geom/SimpleGeometry";
|
|
19
|
-
import { fromLonLat } from "ol/proj";
|
|
19
|
+
import { fromLonLat, transformExtent } from "ol/proj";
|
|
20
20
|
import { bbox as bboxStrategy } from "ol/loadingstrategy";
|
|
21
21
|
import { defaultStyle } from "./styles";
|
|
22
22
|
import VectorTileLayer from "ol/layer/VectorTile";
|
|
@@ -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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
}
|
|
@@ -238,9 +268,12 @@ export function createView(viewModel: MapContextView | null, map: Map): View {
|
|
|
238
268
|
size: map.getSize(),
|
|
239
269
|
});
|
|
240
270
|
} else if ("extent" in viewModel) {
|
|
241
|
-
view.fit(
|
|
242
|
-
|
|
243
|
-
|
|
271
|
+
view.fit(
|
|
272
|
+
transformExtent(viewModel.extent, "EPSG:4326", view.getProjection()),
|
|
273
|
+
{
|
|
274
|
+
size: map.getSize(),
|
|
275
|
+
},
|
|
276
|
+
);
|
|
244
277
|
} else {
|
|
245
278
|
const { center: centerInViewProj, zoom } = viewModel;
|
|
246
279
|
const center = centerInViewProj
|
|
@@ -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.
|
|
3
|
+
"version": "0.0.5-dev.35+06fc94d",
|
|
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.
|
|
40
|
+
"@geospatial-sdk/core": "^0.0.5-dev.35+06fc94d",
|
|
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": "
|
|
45
|
+
"gitHead": "06fc94d3d5eb61f5d4bff87cf37e500b57e0a4b4"
|
|
46
46
|
}
|