@masterportal/masterportalapi 2.7.0 → 2.8.0
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/CHANGELOG.md +11 -0
- package/README.md +7 -0
- package/example/config/services.json +1 -1
- package/example/index.js +17 -2
- package/package.json +6 -2
- package/src/index.js +2 -0
- package/src/lib/ping.js +63 -0
- package/src/maps/map.js +1 -0
- package/src/maps/ol/olMap.js +54 -5
- package/test/layer/wmts.test.js +1 -1
- package/test/lib/ping.test.js +69 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,13 +5,24 @@
|
|
|
5
5
|
|
|
6
6
|
## Unreleased - in development
|
|
7
7
|
### Added
|
|
8
|
+
|
|
8
9
|
### Changed
|
|
9
10
|
|
|
10
11
|
### Deprecated
|
|
11
12
|
|
|
12
13
|
### Removed
|
|
14
|
+
|
|
13
15
|
### Fixed
|
|
14
16
|
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 2.8.0 - 2022-10-04
|
|
20
|
+
### Added
|
|
21
|
+
- Issue #814: Adding optional error callbacks and availability detection to layers.
|
|
22
|
+
- Added possibility to set initial features for layer vectorBase.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
15
26
|
## 2.7.0 - 2022-09-14
|
|
16
27
|
### Changed
|
|
17
28
|
- The following packages have been updated:
|
package/README.md
CHANGED
|
@@ -10,6 +10,13 @@ If you want to create a 3d map, you have to provide the peer dependency cesium.
|
|
|
10
10
|
|
|
11
11
|
By importing the project by module name like ``import ... from "masterportalAPI"``, most bundlers and bundler configurations will include the whole masterportalAPI. If you only need a subset of the provided functions and want to keep your build clean, directly import the needed functions like ``import {createMap} from "masterportalAPI/src/map.js``.
|
|
12
12
|
|
|
13
|
+
## Error Handling
|
|
14
|
+
|
|
15
|
+
1. Error event callback: The methods `createMap` and `addLayer` both optionally accept error event callbacks that are called with OpenLayers error events (tileloaderror, imageloaderror, featuresloaderror, error).
|
|
16
|
+
2. Ping: A method that returns a Promise. This resolves to the status code of the layer's GetCapabilities request, which can be used to decide if the service is reachable and usable at all.
|
|
17
|
+
|
|
18
|
+
Both methods may be required for useful user feedback. For example, a WMS may answer a tile request with 404 if no tile is available for a region, but may be fully available within its specification. This may throw an error while the service itself is available as specified. It is up to the using implementation how to react, how often to ping, and so on.
|
|
19
|
+
|
|
13
20
|
## Scripts
|
|
14
21
|
|
|
15
22
|
|Script|Effect|
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
{
|
|
48
48
|
"id": "4001",
|
|
49
49
|
"name": "Gelaende",
|
|
50
|
-
"url": "https://daten-hamburg.de/gdi3d/datasource-data/Gelaende
|
|
50
|
+
"url": "https://daten-hamburg.de/gdi3d/datasource-data/Gelaende",
|
|
51
51
|
"typ": "Terrain3D",
|
|
52
52
|
"cesiumTerrainProviderOptions": {
|
|
53
53
|
"requestVertexNormals": true
|
package/example/index.js
CHANGED
|
@@ -11,6 +11,10 @@ import portalConfig from "./config/portal.json";
|
|
|
11
11
|
import localGeoJSON from "./config/localGeoJSON.js";
|
|
12
12
|
import {load3DScript} from "../src/lib/load3DScript";
|
|
13
13
|
|
|
14
|
+
function errorCallback (errorEvent) {
|
|
15
|
+
console.error("This is an error event:", errorEvent);
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
//* Add elements to window to play with API in console
|
|
15
19
|
window.mpapi = {
|
|
16
20
|
...mpapi,
|
|
@@ -94,7 +98,7 @@ document.getElementById("layer-visibility").addEventListener("click", function (
|
|
|
94
98
|
});
|
|
95
99
|
});
|
|
96
100
|
|
|
97
|
-
map2D = mapsAPI.map.createMap(config, "2D");
|
|
101
|
+
map2D = mapsAPI.map.createMap(config, "2D", {errorCallback});
|
|
98
102
|
services.push(localService);
|
|
99
103
|
// */
|
|
100
104
|
|
|
@@ -148,7 +152,18 @@ services.find(({id}) => id === "5001").style = styleOaf;
|
|
|
148
152
|
//* SYNCHRONOUS EXAMPLE: layerConf is known
|
|
149
153
|
window.mpapi.map = map2D;
|
|
150
154
|
|
|
151
|
-
["2001", "2002", "1002", "3001", "5001"].forEach(id => window.mpapi.map.addLayer(id));
|
|
155
|
+
["2001", "2002", "1002", "3001", "5001"].forEach(id => window.mpapi.map.addLayer(id, {errorCallback}));
|
|
156
|
+
|
|
157
|
+
// ping all layers
|
|
158
|
+
services
|
|
159
|
+
.map(service => ({
|
|
160
|
+
ping: mpapi.ping(service),
|
|
161
|
+
service
|
|
162
|
+
}))
|
|
163
|
+
.forEach(({ping, service}) => ping
|
|
164
|
+
// eslint-disable-next-line no-console
|
|
165
|
+
.then(statusCode => console.log(`Service ${service.id} pinged; returned ${statusCode}.`))
|
|
166
|
+
.catch(console.error));
|
|
152
167
|
|
|
153
168
|
// */
|
|
154
169
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@masterportal/masterportalapi",
|
|
3
3
|
"author": "Implementierungspartnerschaft Masterportal <info@masterportal.org> (https://www.masterportal.org)",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.8.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Basic functions of the Masterportal as api",
|
|
7
7
|
"repository": {
|
|
@@ -58,5 +58,9 @@
|
|
|
58
58
|
},
|
|
59
59
|
"bugs": {
|
|
60
60
|
"url": "https://bitbucket.org/geowerkstatt-hamburg/masterportalapi/issues"
|
|
61
|
-
}
|
|
61
|
+
},
|
|
62
|
+
"keywords": [
|
|
63
|
+
"geo",
|
|
64
|
+
"map"
|
|
65
|
+
]
|
|
62
66
|
}
|
package/src/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import Tileset from "./layer/tileset";
|
|
|
14
14
|
import * as layerLib from "./layer/lib";
|
|
15
15
|
import {search, setGazetteerUrl} from "./searchAddress";
|
|
16
16
|
import setBackgroundImage from "./lib/setBackgroundImage";
|
|
17
|
+
import ping from "./lib/ping";
|
|
17
18
|
|
|
18
19
|
export {
|
|
19
20
|
createMapView,
|
|
@@ -31,6 +32,7 @@ export {
|
|
|
31
32
|
setBackgroundImage,
|
|
32
33
|
setGazetteerUrl,
|
|
33
34
|
rawLayerList,
|
|
35
|
+
ping,
|
|
34
36
|
search,
|
|
35
37
|
crs
|
|
36
38
|
};
|
package/src/lib/ping.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper function to detect layer availability/usability. Depending on layer
|
|
3
|
+
* type, the method to infer availability broadly varies.
|
|
4
|
+
* @param {Object} layerSpecification services.json layer specification
|
|
5
|
+
* @returns {Promise} resolves to status code of a layer-specific default
|
|
6
|
+
* request as Number, or null if no request could be made, which should
|
|
7
|
+
* indicate misconfiguration; defaults to 900 if the request itself failed for
|
|
8
|
+
* arbitrary reasons (usually CORS-related)
|
|
9
|
+
*/
|
|
10
|
+
export default function ({url, typ, capabilitiesUrl, ...rest}) {
|
|
11
|
+
const statusCheckUrls = [];
|
|
12
|
+
|
|
13
|
+
if (capabilitiesUrl) {
|
|
14
|
+
statusCheckUrls.push(capabilitiesUrl);
|
|
15
|
+
}
|
|
16
|
+
else if (typ === "OAF") {
|
|
17
|
+
statusCheckUrls.push(url);
|
|
18
|
+
}
|
|
19
|
+
else if (typ === "Entities3D") {
|
|
20
|
+
rest.entities
|
|
21
|
+
.forEach(entity => entity.url ? statusCheckUrls.push(entity.url) : null);
|
|
22
|
+
}
|
|
23
|
+
else if (typ === "TileSet3D" || typ === "Terrain3D") {
|
|
24
|
+
statusCheckUrls.push(url);
|
|
25
|
+
}
|
|
26
|
+
else if (typ === "GeoJSON") {
|
|
27
|
+
if (!url && rest.features) {
|
|
28
|
+
// may be a local layer without source url – that's equal to 200
|
|
29
|
+
return Promise.resolve(200);
|
|
30
|
+
}
|
|
31
|
+
if (url) {
|
|
32
|
+
statusCheckUrls.push(url);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (typ === "VectorTile") {
|
|
36
|
+
statusCheckUrls.push(url.replaceAll(/\{[xyz]\}/ig, "0"));
|
|
37
|
+
}
|
|
38
|
+
else if (typ === "WMTS") {
|
|
39
|
+
const arbitraryTileRequests = (url ? [url] : rest.urls)
|
|
40
|
+
.map(entry => entry
|
|
41
|
+
.replace(/\{Style\}/ig, rest.style)
|
|
42
|
+
.replace(/\{TileMatrixSet\}/ig, rest.tileMatrixSet)
|
|
43
|
+
.replaceAll(/\{(TileMatrix|TileRow|TileCol)\}/ig, "0"));
|
|
44
|
+
|
|
45
|
+
statusCheckUrls.push(...arbitraryTileRequests);
|
|
46
|
+
}
|
|
47
|
+
else if (url) {
|
|
48
|
+
statusCheckUrls.push(`${url}?service=${typ}&request=GetCapabilities`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!statusCheckUrls.length) {
|
|
52
|
+
return Promise.resolve(null);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return Promise
|
|
56
|
+
.allSettled(statusCheckUrls
|
|
57
|
+
.map(statusCheckUrl => new Promise((resolve) => fetch(statusCheckUrl, {method: "HEAD"})
|
|
58
|
+
.then(({status}) => resolve(status))
|
|
59
|
+
// usually from CORS, treated as 900 here ("proprietary" status code)
|
|
60
|
+
.catch(() => resolve(900)))))
|
|
61
|
+
// return highest status code (will indicate erroneous behaviour)
|
|
62
|
+
.then(codes => Math.max(...codes.map(({value}) => value)));
|
|
63
|
+
}
|
package/src/maps/map.js
CHANGED
|
@@ -20,6 +20,7 @@ export default {
|
|
|
20
20
|
* @param {object} [settings={}] - settings object
|
|
21
21
|
* @param {object} [settings.mapParams] - additional parameter object that is spread into the ol.Map constructor object
|
|
22
22
|
* @param {function} [settings.callback] - optional callback for layer list loading
|
|
23
|
+
* @param {function} [settings.errorCallback] - optional callback for layer error events
|
|
23
24
|
* @returns {module:ol/Map~Map} The map.
|
|
24
25
|
*/
|
|
25
26
|
createMap: function (config, mapMode = "2D", settings = {}) {
|
package/src/maps/ol/olMap.js
CHANGED
|
@@ -32,6 +32,48 @@ const layerBuilderMap = {
|
|
|
32
32
|
},
|
|
33
33
|
originalAddLayer = Map.prototype.addLayer;
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Flattens a Layer/LayerGroup to an array of non-group instances.
|
|
37
|
+
* @param {ol/layer/Base} layer any layer, possibly a LayerGroup
|
|
38
|
+
* @returns {ol/layer/Base[]} array of all given layers
|
|
39
|
+
*/
|
|
40
|
+
function flattenLayerGroups (layer) {
|
|
41
|
+
return layer.getLayers
|
|
42
|
+
? layer
|
|
43
|
+
.getLayers()
|
|
44
|
+
.getArray()
|
|
45
|
+
.map(l => flattenLayerGroups(l))
|
|
46
|
+
.flat(1)
|
|
47
|
+
: [layer];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adds an error handling function to an arbitrary layer. The event will be
|
|
52
|
+
* registered to the tileloaderror, imageloaderror, featuresloaderror, and
|
|
53
|
+
* generic error event. Depending on the type of layer and error, only one of
|
|
54
|
+
* these events will fire.
|
|
55
|
+
* @param {ol/layer/Base} layer Any type of layer implemented.
|
|
56
|
+
* @param {Function} errorCallback Error callback, called from OL event.
|
|
57
|
+
* @returns {void} side-effect to layer
|
|
58
|
+
*/
|
|
59
|
+
function injectErrorCallback (layer, errorCallback) {
|
|
60
|
+
const layers = flattenLayerGroups(layer);
|
|
61
|
+
|
|
62
|
+
layers.forEach(l => {
|
|
63
|
+
const source = l.getSource?.();
|
|
64
|
+
|
|
65
|
+
if (source) {
|
|
66
|
+
source.on?.("tileloaderror", errorCallback);
|
|
67
|
+
source.on?.("imageloaderror", errorCallback);
|
|
68
|
+
source.on?.("featuresloaderror", errorCallback);
|
|
69
|
+
source.on?.("error", errorCallback);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.error("Could not register error callback on layer:", l);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
35
77
|
/**
|
|
36
78
|
* Adds a layer to the map, or adds a layer to the map by id.
|
|
37
79
|
* This id is looked up within the array of all known services.
|
|
@@ -40,14 +82,18 @@ const layerBuilderMap = {
|
|
|
40
82
|
* if you request the services from the internet.
|
|
41
83
|
*
|
|
42
84
|
* This function is available on all ol/Map instances.
|
|
43
|
-
* @param {(string|ol/
|
|
85
|
+
* @param {(string|ol/layer/Base)} layerOrId - if of layer to add to map
|
|
44
86
|
* @param {object} [params] - optional parameter object
|
|
45
87
|
* @param {boolean} [params.visibility=true] - whether added layer is initially visible
|
|
46
88
|
* @param {Number} [params.transparency=0] - how visible the layer is initially
|
|
89
|
+
* @param {Function} [params.errorCallback=console.error] - callback for layer source error events
|
|
47
90
|
* @returns {?ol.Layer} added layer
|
|
48
91
|
*/
|
|
49
|
-
function addLayer (layerOrId, params = {visibility: true, transparency: 0}) {
|
|
50
|
-
|
|
92
|
+
function addLayer (layerOrId, params = {visibility: true, transparency: 0, errorCallback: console.error}) {
|
|
93
|
+
const errorCallback = typeof params.errorCallback === "function"
|
|
94
|
+
? params.errorCallback
|
|
95
|
+
: console.error;
|
|
96
|
+
let layer, layerBuilder;
|
|
51
97
|
|
|
52
98
|
// if parameter is id, create and add layer with masterportalAPI mechanisms
|
|
53
99
|
if (typeof layerOrId === "string") {
|
|
@@ -65,11 +111,13 @@ function addLayer (layerOrId, params = {visibility: true, transparency: 0}) {
|
|
|
65
111
|
layer = layerBuilder.createLayer(rawLayer, {}, {map: this});
|
|
66
112
|
layer.setVisible(typeof params.visibility === "boolean" ? params.visibility : true);
|
|
67
113
|
layer.setOpacity(typeof params.transparency === "number" ? (100 - params.transparency) / 100 : 1);
|
|
114
|
+
injectErrorCallback(layer, errorCallback);
|
|
68
115
|
originalAddLayer.call(this, layer);
|
|
69
116
|
return layer;
|
|
70
117
|
}
|
|
71
118
|
|
|
72
119
|
// else use original function
|
|
120
|
+
injectErrorCallback(layerOrId, errorCallback);
|
|
73
121
|
return originalAddLayer.call(this, layerOrId);
|
|
74
122
|
}
|
|
75
123
|
|
|
@@ -93,10 +141,11 @@ Map.prototype.addLayer = addLayer;
|
|
|
93
141
|
* @param {object} [settings={}] - setings object
|
|
94
142
|
* @param {object} [settings.mapParams] - additional parameter object that is spread into the ol.Map constructor object
|
|
95
143
|
* @param {function} [settings.callback] - optional callback for layer list loading
|
|
144
|
+
* @param {function} [settings.errorCallback] – method called on error events
|
|
96
145
|
* @param {String} [mapMode = "2D"] The map mode. '2D' to craete a 2D-map and '3D' to create a 3D-map.
|
|
97
146
|
* @returns {object} map object from ol
|
|
98
147
|
*/
|
|
99
|
-
export function createMap (config = defaults, {mapParams, callback} = {}) {
|
|
148
|
+
export function createMap (config = defaults, {mapParams, callback, errorCallback} = {}) {
|
|
100
149
|
registerProjections(config.namedProjections);
|
|
101
150
|
setBackgroundImage(config);
|
|
102
151
|
setGazetteerUrl(config.gazetteerUrl);
|
|
@@ -114,7 +163,7 @@ export function createMap (config = defaults, {mapParams, callback} = {}) {
|
|
|
114
163
|
initializeLayerList(config.layerConf, (param, error) => {
|
|
115
164
|
getInitialLayers(config)
|
|
116
165
|
.forEach(layer => {
|
|
117
|
-
map.addLayer(layer.id,
|
|
166
|
+
map.addLayer(layer.id, {errorCallback});
|
|
118
167
|
});
|
|
119
168
|
|
|
120
169
|
if (typeof callback === "function") {
|
package/test/layer/wmts.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import {getWidth} from "ol/extent";
|
|
|
3
3
|
|
|
4
4
|
import * as wmts from "../../src/layer/wmts";
|
|
5
5
|
|
|
6
|
-
describe
|
|
6
|
+
describe("wmts.js", function () {
|
|
7
7
|
describe("generateArrays", function () {
|
|
8
8
|
it("should fill the arrays resolutions and matrixIds with numbers", function () {
|
|
9
9
|
const size = getWidth(getProjection("EPSG:3857").getExtent()) / 256,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import ping from "../../src/lib/ping";
|
|
2
|
+
|
|
3
|
+
const originalFetch = global.fetch;
|
|
4
|
+
|
|
5
|
+
describe("ping.js", function () {
|
|
6
|
+
let mockFetch;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
mockFetch = jest.fn(() => Promise.resolve({
|
|
10
|
+
status: 200
|
|
11
|
+
}));
|
|
12
|
+
global.fetch = mockFetch;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterAll(() => {
|
|
16
|
+
global.fetch = originalFetch;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("ping", function () {
|
|
20
|
+
it("calls an url from the service definition", async function () {
|
|
21
|
+
const status = await ping({url: "www.example.com", typ: "WMS"});
|
|
22
|
+
|
|
23
|
+
expect(status).toBe(200);
|
|
24
|
+
expect(mockFetch).toHaveBeenCalledWith("www.example.com?service=WMS&request=GetCapabilities", {method: "HEAD"});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("calls 0/0/0 tile for VectorTile services", async function () {
|
|
28
|
+
const status = await ping({url: "www.example.com/{x}/{y}/{z}", typ: "VectorTile"});
|
|
29
|
+
|
|
30
|
+
expect(status).toBe(200);
|
|
31
|
+
expect(mockFetch).toHaveBeenCalledWith("www.example.com/0/0/0", {method: "HEAD"});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("calls the URL directly for OAF services", async function () {
|
|
35
|
+
const status = await ping({url: "www.example.com", typ: "OAF"});
|
|
36
|
+
|
|
37
|
+
expect(status).toBe(200);
|
|
38
|
+
expect(mockFetch).toHaveBeenCalledWith("www.example.com", {method: "HEAD"});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns 900 on network errors", async function () {
|
|
42
|
+
mockFetch = jest.fn(() => Promise.resolve({status: 900}));
|
|
43
|
+
global.fetch = mockFetch;
|
|
44
|
+
|
|
45
|
+
const status = await ping({url: "www.example.com", typ: "OAF"});
|
|
46
|
+
|
|
47
|
+
expect(status).toBe(900);
|
|
48
|
+
expect(mockFetch).toHaveBeenCalledWith("www.example.com", {method: "HEAD"});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns highest status code on multiple urls", async function () {
|
|
52
|
+
let counter = 200;
|
|
53
|
+
|
|
54
|
+
mockFetch = jest.fn(() => Promise.resolve({status: counter++}));
|
|
55
|
+
global.fetch = mockFetch;
|
|
56
|
+
|
|
57
|
+
const status = await ping({urls: [
|
|
58
|
+
"www.example.com",
|
|
59
|
+
"www.example.com/but"
|
|
60
|
+
], typ: "WMTS"});
|
|
61
|
+
|
|
62
|
+
expect(status).toBe(201);
|
|
63
|
+
expect(mockFetch)
|
|
64
|
+
.toHaveBeenCalledWith("www.example.com", {method: "HEAD"});
|
|
65
|
+
expect(mockFetch)
|
|
66
|
+
.toHaveBeenCalledWith("www.example.com/but", {method: "HEAD"});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|