@geospatial-sdk/core 0.0.5-dev.12 → 0.0.5-dev.14
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/constant/projections.d.ts +2 -0
- package/dist/constant/projections.d.ts.map +1 -0
- package/dist/constant/projections.js +1 -0
- package/dist/model/map-context-diff.d.ts +1 -1
- package/dist/model/map-context-diff.d.ts.map +1 -1
- package/dist/model/map-context.d.ts +20 -8
- package/dist/model/map-context.d.ts.map +1 -1
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +18 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/map-context-diff.d.ts.map +1 -1
- package/dist/utils/map-context-diff.js +5 -6
- package/dist/utils/map-context.d.ts.map +1 -1
- package/dist/utils/map-context.js +2 -19
- package/dist/utils/view.d.ts +3 -0
- package/dist/utils/view.d.ts.map +1 -0
- package/dist/utils/view.js +98 -0
- package/lib/constant/projections.ts +1 -0
- package/lib/model/map-context-diff.ts +1 -1
- package/lib/model/map-context.ts +33 -15
- package/lib/utils/hash.test.ts +26 -0
- package/lib/utils/hash.ts +16 -0
- package/lib/utils/index.ts +8 -1
- package/lib/utils/map-context-diff.test.ts +37 -7
- package/lib/utils/map-context-diff.ts +7 -3
- package/lib/utils/map-context.test.ts +42 -26
- package/lib/utils/map-context.ts +12 -27
- package/lib/utils/view.test.ts +146 -0
- package/lib/utils/view.ts +107 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projections.d.ts","sourceRoot":"","sources":["../../lib/constant/projections.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,UAA0B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const LONLAT_CRS_CODES = ["EPSG:4326", "CRS:84"];
|
|
@@ -30,6 +30,6 @@ export interface MapContextDiff {
|
|
|
30
30
|
layersReordered: MapContextLayerReordered[];
|
|
31
31
|
layersRemoved: MapContextLayerPositioned[];
|
|
32
32
|
layersAdded: MapContextLayerPositioned[];
|
|
33
|
-
viewChanges
|
|
33
|
+
viewChanges?: MapContextView;
|
|
34
34
|
}
|
|
35
35
|
//# sourceMappingURL=map-context-diff.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-context-diff.d.ts","sourceRoot":"","sources":["../../lib/model/map-context-diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEhE;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,eAAe,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,yBAAyB,EAAE,CAAC;IAC3C,eAAe,EAAE,wBAAwB,EAAE,CAAC;IAC5C,aAAa,EAAE,yBAAyB,EAAE,CAAC;IAC3C,WAAW,EAAE,yBAAyB,EAAE,CAAC;IACzC,WAAW,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"map-context-diff.d.ts","sourceRoot":"","sources":["../../lib/model/map-context-diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEhE;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,eAAe,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,yBAAyB,EAAE,CAAC;IAC3C,eAAe,EAAE,wBAAwB,EAAE,CAAC;IAC5C,aAAa,EAAE,yBAAyB,EAAE,CAAC;IAC3C,WAAW,EAAE,yBAAyB,EAAE,CAAC;IACzC,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B"}
|
|
@@ -35,10 +35,10 @@ export interface MapContextLayerWfs extends MapContextBaseLayer {
|
|
|
35
35
|
featureType: string;
|
|
36
36
|
}
|
|
37
37
|
export interface MapContextLayerOgcApi extends MapContextBaseLayer {
|
|
38
|
-
type:
|
|
38
|
+
type: "ogcapi";
|
|
39
39
|
url: string;
|
|
40
40
|
collection: string;
|
|
41
|
-
useTiles?:
|
|
41
|
+
useTiles?: "vector" | "map";
|
|
42
42
|
tileMatrixSet?: string;
|
|
43
43
|
options?: Record<string, string>;
|
|
44
44
|
}
|
|
@@ -72,16 +72,28 @@ export type Coordinate = [number, number];
|
|
|
72
72
|
export type Extent = [number, number, number, number];
|
|
73
73
|
/**
|
|
74
74
|
* @property center Expressed in longitude/latitude
|
|
75
|
+
* @property zoom
|
|
76
|
+
*/
|
|
77
|
+
export interface ViewByZoomAndCenter {
|
|
78
|
+
center: Coordinate;
|
|
79
|
+
zoom: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
75
82
|
* @property extent Expressed in longitude/latitude
|
|
76
|
-
* @property maxExtent Expressed in longitude/latitude
|
|
77
83
|
*/
|
|
78
|
-
export interface
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
export interface ViewByExtent {
|
|
85
|
+
extent: Extent;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* @property geometry Expressed in longitude/latitude
|
|
89
|
+
*/
|
|
90
|
+
export interface ViewByGeometry {
|
|
91
|
+
geometry: Geometry;
|
|
92
|
+
}
|
|
93
|
+
export type MapContextView = (ViewByZoomAndCenter | ViewByExtent | ViewByGeometry) & {
|
|
82
94
|
maxZoom?: number;
|
|
83
95
|
maxExtent?: Extent;
|
|
84
|
-
}
|
|
96
|
+
};
|
|
85
97
|
export interface MapContext {
|
|
86
98
|
layers: MapContextLayer[];
|
|
87
99
|
view: MapContextView;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-context.d.ts","sourceRoot":"","sources":["../../lib/model/map-context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"map-context.d.ts","sourceRoot":"","sources":["../../lib/model/map-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../lib/utils/hash.ts"],"names":[],"mappings":"AAAA,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,GAAE,MAAM,EAAO,GAAG,MAAM,CAezE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function getHash(input, ignoreKeys = []) {
|
|
2
|
+
if (input instanceof Object) {
|
|
3
|
+
const obj = {};
|
|
4
|
+
const keys = Object.keys(input).sort();
|
|
5
|
+
for (const key of keys) {
|
|
6
|
+
if (ignoreKeys.includes(key))
|
|
7
|
+
continue;
|
|
8
|
+
obj[key] = getHash(input[key]);
|
|
9
|
+
}
|
|
10
|
+
const hash = JSON.stringify(obj)
|
|
11
|
+
.split("")
|
|
12
|
+
.reduce((prev, curr) => (prev << 5) - prev + curr.charCodeAt(0), 0);
|
|
13
|
+
return (hash >>> 0).toString();
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
return JSON.stringify(input);
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./url";
|
|
2
2
|
export * from "./freeze";
|
|
3
3
|
export { computeMapContextDiff } from "./map-context-diff";
|
|
4
|
-
export { getLayerPosition } from "./map-context";
|
|
4
|
+
export { getLayerPosition, addLayerToContext, removeLayerFromContext, replaceLayerInContext, changeLayerPositionInContext, } from "./map-context";
|
|
5
|
+
export { createViewFromLayer } from "./view";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,4BAA4B,GAC7B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from "./url";
|
|
2
2
|
export * from "./freeze";
|
|
3
3
|
export { computeMapContextDiff } from "./map-context-diff";
|
|
4
|
-
export { getLayerPosition } from "./map-context";
|
|
4
|
+
export { getLayerPosition, addLayerToContext, removeLayerFromContext, replaceLayerInContext, changeLayerPositionInContext, } from "./map-context";
|
|
5
|
+
export { createViewFromLayer } from "./view";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-context-diff.d.ts","sourceRoot":"","sources":["../../lib/utils/map-context-diff.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,cAAc,
|
|
1
|
+
{"version":3,"file":"map-context-diff.d.ts","sourceRoot":"","sources":["../../lib/utils/map-context-diff.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,cAAc,EAIf,MAAM,UAAU,CAAC;AAIlB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,UAAU,EACvB,eAAe,EAAE,UAAU,GAC1B,cAAc,CAyEhB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isLayerSame, isLayerSameAndUnchanged } from "./map-context";
|
|
2
|
+
import { getHash } from "./hash";
|
|
2
3
|
/**
|
|
3
4
|
* The following logic is produced by identifying layers in both context
|
|
4
5
|
* and determining whether they have been added, removed, changed or reordered.
|
|
@@ -31,7 +32,6 @@ export function computeMapContextDiff(nextContext, previousContext) {
|
|
|
31
32
|
const layersReordered = [];
|
|
32
33
|
const layersRemoved = [];
|
|
33
34
|
const layersAdded = [];
|
|
34
|
-
const viewChanges = {};
|
|
35
35
|
// loop on prev context layers (for removed layers)
|
|
36
36
|
for (let i = 0; i < previousContext.layers.length; i++) {
|
|
37
37
|
const layer = previousContext.layers[i];
|
|
@@ -69,11 +69,10 @@ export function computeMapContextDiff(nextContext, previousContext) {
|
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
const viewChanges = getHash(nextContext.view) !== getHash(previousContext.view)
|
|
73
|
+
? Object.assign({}, nextContext.view) : undefined;
|
|
74
|
+
return Object.assign({ layersAdded,
|
|
74
75
|
layersChanged,
|
|
75
76
|
layersRemoved,
|
|
76
|
-
layersReordered,
|
|
77
|
-
viewChanges,
|
|
78
|
-
};
|
|
77
|
+
layersReordered }, (viewChanges && { viewChanges }));
|
|
79
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map-context.d.ts","sourceRoot":"","sources":["../../lib/utils/map-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"map-context.d.ts","sourceRoot":"","sources":["../../lib/utils/map-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAGvD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,eAAe,EACtB,aAAa,UAAQ,GACpB,MAAM,CAER;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,eAAe,GACtB,OAAO,CAKT;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,eAAe,GACtB,OAAO,CAKT;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,eAAe,GAC1B,MAAM,CAER;AAED;;;;;;;GAOG;AAEH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,eAAe,EAC3B,QAAQ,CAAC,EAAE,MAAM,GAChB,UAAU,CAQZ;AAED;;;;;;GAMG;AAEH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,eAAe,GAC1B,UAAU,CAOZ;AAED;;;;;;;GAOG;AAEH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,eAAe,GAC3B,UAAU,CAOZ;AAED;;;;;;;GAOG;AAEH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,eAAe,EAC3B,QAAQ,EAAE,MAAM,GACf,UAAU,CAGZ"}
|
|
@@ -1,23 +1,6 @@
|
|
|
1
|
+
import { getHash } from "./hash";
|
|
1
2
|
export function getLayerHash(layer, includeExtras = false) {
|
|
2
|
-
|
|
3
|
-
if (input instanceof Object) {
|
|
4
|
-
const obj = {};
|
|
5
|
-
const keys = Object.keys(input).sort();
|
|
6
|
-
for (const key of keys) {
|
|
7
|
-
if (!includeExtras && key === "extras")
|
|
8
|
-
continue;
|
|
9
|
-
obj[key] = getHash(input[key]);
|
|
10
|
-
}
|
|
11
|
-
const hash = JSON.stringify(obj)
|
|
12
|
-
.split("")
|
|
13
|
-
.reduce((prev, curr) => (prev << 5) - prev + curr.charCodeAt(0), 0);
|
|
14
|
-
return (hash >>> 0).toString();
|
|
15
|
-
}
|
|
16
|
-
else {
|
|
17
|
-
return JSON.stringify(input);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return getHash(layer);
|
|
3
|
+
return getHash(layer, includeExtras ? [] : ["extras"]);
|
|
21
4
|
}
|
|
22
5
|
export function isLayerSame(layerA, layerB) {
|
|
23
6
|
if ("id" in layerA && "id" in layerB) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../lib/utils/view.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,eAAe,EAGf,cAAc,EAEf,MAAM,UAAU,CAAC;AAKlB,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAYhC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { transformExtent } from "ol/proj";
|
|
11
|
+
import { WfsEndpoint, WmsEndpoint, WmtsEndpoint } from "@camptocamp/ogc-client";
|
|
12
|
+
import { LONLAT_CRS_CODES } from "../constant/projections";
|
|
13
|
+
import { fromEPSGCode, register } from "ol/proj/proj4";
|
|
14
|
+
import GeoJSON from "ol/format/GeoJSON";
|
|
15
|
+
import { extend } from "ol/extent";
|
|
16
|
+
import proj4 from "proj4";
|
|
17
|
+
const GEOJSON = new GeoJSON();
|
|
18
|
+
export function createViewFromLayer(layer) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
if (layer.type === "wms") {
|
|
21
|
+
return yield getWmsLayerExtent(layer);
|
|
22
|
+
}
|
|
23
|
+
else if (layer.type === "wmts") {
|
|
24
|
+
return yield getWmtsLayerExtent(layer);
|
|
25
|
+
}
|
|
26
|
+
else if (layer.type === "geojson" && layer.data) {
|
|
27
|
+
return computeExtentFromGeojson(layer.data);
|
|
28
|
+
}
|
|
29
|
+
else if (layer.type === "wfs") {
|
|
30
|
+
return yield getWfsLayerExtent(layer);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
throw new Error(`Unsupported layer type: ${layer.type}`);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function computeExtentFromGeojson(data) {
|
|
38
|
+
const geojson = typeof data === "string" ? JSON.parse(data) : data;
|
|
39
|
+
const features = GEOJSON.readFeatures(geojson);
|
|
40
|
+
const extent = features.reduce((prev, curr) => {
|
|
41
|
+
const geom = curr.getGeometry();
|
|
42
|
+
if (!geom)
|
|
43
|
+
return prev;
|
|
44
|
+
return extend(prev, geom.getExtent());
|
|
45
|
+
}, [Infinity, Infinity, -Infinity, -Infinity]);
|
|
46
|
+
return {
|
|
47
|
+
extent,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function getWmsLayerExtent(layer) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const endpoint = yield new WmsEndpoint(layer.url).isReady();
|
|
53
|
+
const { boundingBoxes } = endpoint.getLayerByName(layer.name);
|
|
54
|
+
if (!Object.keys(boundingBoxes).length) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const lonLatCRS = Object.keys(boundingBoxes).find((crs) => LONLAT_CRS_CODES.includes(crs));
|
|
58
|
+
if (lonLatCRS) {
|
|
59
|
+
return {
|
|
60
|
+
extent: boundingBoxes[lonLatCRS],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const availableEPSGCode = Object.keys(boundingBoxes)[0];
|
|
65
|
+
register(proj4);
|
|
66
|
+
const proj = yield fromEPSGCode(availableEPSGCode);
|
|
67
|
+
return {
|
|
68
|
+
extent: transformExtent(boundingBoxes[availableEPSGCode], proj, "EPSG:4326"),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function getWmtsLayerExtent(layer) {
|
|
74
|
+
var _a;
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
const endpoint = yield new WmtsEndpoint(layer.url).isReady();
|
|
77
|
+
const layerName = (_a = endpoint.getSingleLayerName()) !== null && _a !== void 0 ? _a : layer.name;
|
|
78
|
+
const wmtsLayer = endpoint.getLayerByName(layerName);
|
|
79
|
+
return wmtsLayer.latLonBoundingBox
|
|
80
|
+
? {
|
|
81
|
+
extent: wmtsLayer.latLonBoundingBox,
|
|
82
|
+
}
|
|
83
|
+
: null;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function getWfsLayerExtent(layer) {
|
|
87
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
88
|
+
const endpoint = yield new WfsEndpoint(layer.url).isReady();
|
|
89
|
+
const featureTypeSummary = endpoint.getFeatureTypeSummary(layer.featureType);
|
|
90
|
+
const boundingBox = featureTypeSummary === null || featureTypeSummary === void 0 ? void 0 : featureTypeSummary.boundingBox;
|
|
91
|
+
if (!boundingBox) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
extent: boundingBox,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const LONLAT_CRS_CODES = ["EPSG:4326", "CRS:84"];
|
package/lib/model/map-context.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import { FeatureCollection, Geometry } from "geojson";
|
|
3
2
|
|
|
4
3
|
export type LayerDimensions = Record<string, string>;
|
|
@@ -43,13 +42,13 @@ export interface MapContextLayerWfs extends MapContextBaseLayer {
|
|
|
43
42
|
featureType: string;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
export interface MapContextLayerOgcApi extends MapContextBaseLayer{
|
|
47
|
-
type:
|
|
48
|
-
url: string
|
|
49
|
-
collection: string
|
|
50
|
-
useTiles?:
|
|
51
|
-
tileMatrixSet?: string
|
|
52
|
-
options?: Record<string, string
|
|
45
|
+
export interface MapContextLayerOgcApi extends MapContextBaseLayer {
|
|
46
|
+
type: "ogcapi";
|
|
47
|
+
url: string;
|
|
48
|
+
collection: string;
|
|
49
|
+
useTiles?: "vector" | "map";
|
|
50
|
+
tileMatrixSet?: string;
|
|
51
|
+
options?: Record<string, string>;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
export interface MapContextLayerXyz extends MapContextBaseLayer {
|
|
@@ -83,7 +82,7 @@ export type MapContextLayer =
|
|
|
83
82
|
| MapContextLayerWfs
|
|
84
83
|
| MapContextLayerXyz
|
|
85
84
|
| MapContextLayerGeojson
|
|
86
|
-
| MapContextLayerOgcApi
|
|
85
|
+
| MapContextLayerOgcApi;
|
|
87
86
|
|
|
88
87
|
export type Coordinate = [number, number];
|
|
89
88
|
|
|
@@ -94,16 +93,35 @@ export type Extent = [number, number, number, number];
|
|
|
94
93
|
|
|
95
94
|
/**
|
|
96
95
|
* @property center Expressed in longitude/latitude
|
|
96
|
+
* @property zoom
|
|
97
|
+
*/
|
|
98
|
+
export interface ViewByZoomAndCenter {
|
|
99
|
+
center: Coordinate;
|
|
100
|
+
zoom: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
97
104
|
* @property extent Expressed in longitude/latitude
|
|
98
|
-
* @property maxExtent Expressed in longitude/latitude
|
|
99
105
|
*/
|
|
100
|
-
export interface
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
export interface ViewByExtent {
|
|
107
|
+
extent: Extent;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @property geometry Expressed in longitude/latitude
|
|
112
|
+
*/
|
|
113
|
+
export interface ViewByGeometry {
|
|
114
|
+
geometry: Geometry;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type MapContextView = (
|
|
118
|
+
| ViewByZoomAndCenter
|
|
119
|
+
| ViewByExtent
|
|
120
|
+
| ViewByGeometry
|
|
121
|
+
) & {
|
|
104
122
|
maxZoom?: number;
|
|
105
123
|
maxExtent?: Extent;
|
|
106
|
-
}
|
|
124
|
+
};
|
|
107
125
|
|
|
108
126
|
export interface MapContext {
|
|
109
127
|
layers: MapContextLayer[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getHash } from "./hash";
|
|
2
|
+
|
|
3
|
+
describe("getHash", () => {
|
|
4
|
+
it("generates a hash representing the deep value of an object", () => {
|
|
5
|
+
const hashA = getHash({ a: 1, b: 2, c: "abcd", d: ["a", "b", "c"] });
|
|
6
|
+
const hashB = getHash({ a: 1, b: 200, c: "abcd", d: ["a", "b", "c"] });
|
|
7
|
+
expect(hashB).not.toEqual(hashA);
|
|
8
|
+
});
|
|
9
|
+
it("returns a stable hash regardless of properties order", () => {
|
|
10
|
+
const hashA = getHash({ a: 1, b: 2, c: "abcd" });
|
|
11
|
+
const hashB = getHash({ c: "abcd", b: 2, a: 1 });
|
|
12
|
+
expect(hashB).toEqual(hashA);
|
|
13
|
+
});
|
|
14
|
+
it("takes into account array order", () => {
|
|
15
|
+
const hashA = getHash({ a: ["a", "b", "c"] });
|
|
16
|
+
const hashB = getHash({ a: ["b", "a", "c"] });
|
|
17
|
+
expect(hashB).not.toEqual(hashA);
|
|
18
|
+
});
|
|
19
|
+
it("ignores properties on demand", () => {
|
|
20
|
+
const hashA = getHash({ a: 1, b: 2, c: "abcd", d: ["a", "b", "c"] }, ["b"]);
|
|
21
|
+
const hashB = getHash({ c: "abcd", b: 2000, a: 1, d: ["a", "b", "c"] }, [
|
|
22
|
+
"b",
|
|
23
|
+
]);
|
|
24
|
+
expect(hashB).toEqual(hashA);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function getHash(input: unknown, ignoreKeys: string[] = []): string {
|
|
2
|
+
if (input instanceof Object) {
|
|
3
|
+
const obj: Record<string, string> = {};
|
|
4
|
+
const keys = Object.keys(input).sort();
|
|
5
|
+
for (const key of keys) {
|
|
6
|
+
if (ignoreKeys.includes(key)) continue;
|
|
7
|
+
obj[key] = getHash(input[key as keyof typeof input]);
|
|
8
|
+
}
|
|
9
|
+
const hash = JSON.stringify(obj)
|
|
10
|
+
.split("")
|
|
11
|
+
.reduce((prev, curr) => (prev << 5) - prev + curr.charCodeAt(0), 0);
|
|
12
|
+
return (hash >>> 0).toString();
|
|
13
|
+
} else {
|
|
14
|
+
return JSON.stringify(input);
|
|
15
|
+
}
|
|
16
|
+
}
|
package/lib/utils/index.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export * from "./url";
|
|
2
2
|
export * from "./freeze";
|
|
3
3
|
export { computeMapContextDiff } from "./map-context-diff";
|
|
4
|
-
export {
|
|
4
|
+
export {
|
|
5
|
+
getLayerPosition,
|
|
6
|
+
addLayerToContext,
|
|
7
|
+
removeLayerFromContext,
|
|
8
|
+
replaceLayerInContext,
|
|
9
|
+
changeLayerPositionInContext,
|
|
10
|
+
} from "./map-context";
|
|
11
|
+
export { createViewFromLayer } from "./view";
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
SAMPLE_LAYER4,
|
|
9
9
|
SAMPLE_LAYER5,
|
|
10
10
|
} from "../../fixtures/map-context.fixtures";
|
|
11
|
+
import { describe } from "vitest";
|
|
11
12
|
|
|
12
13
|
describe("Context diff utils", () => {
|
|
13
14
|
describe("computeMapContextDiff", () => {
|
|
@@ -33,7 +34,6 @@ describe("Context diff utils", () => {
|
|
|
33
34
|
layersChanged: [],
|
|
34
35
|
layersRemoved: [],
|
|
35
36
|
layersReordered: [],
|
|
36
|
-
viewChanges: {},
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
39
|
});
|
|
@@ -65,7 +65,6 @@ describe("Context diff utils", () => {
|
|
|
65
65
|
layersChanged: [],
|
|
66
66
|
layersRemoved: [],
|
|
67
67
|
layersReordered: [],
|
|
68
|
-
viewChanges: {},
|
|
69
68
|
});
|
|
70
69
|
});
|
|
71
70
|
});
|
|
@@ -97,7 +96,6 @@ describe("Context diff utils", () => {
|
|
|
97
96
|
},
|
|
98
97
|
],
|
|
99
98
|
layersReordered: [],
|
|
100
|
-
viewChanges: {},
|
|
101
99
|
});
|
|
102
100
|
});
|
|
103
101
|
});
|
|
@@ -132,7 +130,6 @@ describe("Context diff utils", () => {
|
|
|
132
130
|
],
|
|
133
131
|
layersRemoved: [],
|
|
134
132
|
layersReordered: [],
|
|
135
|
-
viewChanges: {},
|
|
136
133
|
});
|
|
137
134
|
});
|
|
138
135
|
});
|
|
@@ -167,7 +164,6 @@ describe("Context diff utils", () => {
|
|
|
167
164
|
previousPosition: 0,
|
|
168
165
|
},
|
|
169
166
|
],
|
|
170
|
-
viewChanges: {},
|
|
171
167
|
});
|
|
172
168
|
});
|
|
173
169
|
});
|
|
@@ -211,12 +207,41 @@ describe("Context diff utils", () => {
|
|
|
211
207
|
previousPosition: 0,
|
|
212
208
|
},
|
|
213
209
|
],
|
|
214
|
-
viewChanges: {},
|
|
215
210
|
});
|
|
216
211
|
});
|
|
217
212
|
});
|
|
218
213
|
});
|
|
219
214
|
|
|
215
|
+
describe("view changes", () => {
|
|
216
|
+
beforeEach(() => {
|
|
217
|
+
contextOld = {
|
|
218
|
+
...SAMPLE_CONTEXT,
|
|
219
|
+
view: {
|
|
220
|
+
center: [0, 0],
|
|
221
|
+
zoom: 1,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
contextNew = {
|
|
225
|
+
...SAMPLE_CONTEXT,
|
|
226
|
+
view: {
|
|
227
|
+
center: [1, 1],
|
|
228
|
+
zoom: 2,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
it("outputs the correct diff", () => {
|
|
233
|
+
diff = computeMapContextDiff(contextNew, contextOld);
|
|
234
|
+
expect(diff).toEqual({
|
|
235
|
+
layersAdded: [],
|
|
236
|
+
layersChanged: [],
|
|
237
|
+
layersRemoved: [],
|
|
238
|
+
layersReordered: [],
|
|
239
|
+
viewChanges: { ...contextNew.view },
|
|
240
|
+
});
|
|
241
|
+
expect(diff.viewChanges).not.toBe(contextNew.view); // the object reference should be different
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
220
245
|
describe("combined changes", () => {
|
|
221
246
|
let changedLayer: MapContextLayer;
|
|
222
247
|
beforeEach(() => {
|
|
@@ -228,6 +253,9 @@ describe("Context diff utils", () => {
|
|
|
228
253
|
contextNew = {
|
|
229
254
|
...SAMPLE_CONTEXT,
|
|
230
255
|
layers: [SAMPLE_LAYER2, changedLayer, SAMPLE_LAYER5],
|
|
256
|
+
view: {
|
|
257
|
+
extent: [0, 1, 2, 3],
|
|
258
|
+
},
|
|
231
259
|
};
|
|
232
260
|
});
|
|
233
261
|
it("outputs the correct diff", () => {
|
|
@@ -267,7 +295,9 @@ describe("Context diff utils", () => {
|
|
|
267
295
|
previousPosition: 1,
|
|
268
296
|
},
|
|
269
297
|
],
|
|
270
|
-
viewChanges: {
|
|
298
|
+
viewChanges: {
|
|
299
|
+
extent: [0, 1, 2, 3],
|
|
300
|
+
},
|
|
271
301
|
});
|
|
272
302
|
});
|
|
273
303
|
});
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
MapContextLayer,
|
|
5
5
|
MapContextLayerPositioned,
|
|
6
6
|
MapContextLayerReordered,
|
|
7
|
-
MapContextView,
|
|
8
7
|
} from "../model";
|
|
9
8
|
import { isLayerSame, isLayerSameAndUnchanged } from "./map-context";
|
|
9
|
+
import { getHash } from "./hash";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* The following logic is produced by identifying layers in both context
|
|
@@ -47,7 +47,6 @@ export function computeMapContextDiff(
|
|
|
47
47
|
const layersReordered: MapContextLayerReordered[] = [];
|
|
48
48
|
const layersRemoved: MapContextLayerPositioned[] = [];
|
|
49
49
|
const layersAdded: MapContextLayerPositioned[] = [];
|
|
50
|
-
const viewChanges: MapContextView = {};
|
|
51
50
|
|
|
52
51
|
// loop on prev context layers (for removed layers)
|
|
53
52
|
for (let i = 0; i < previousContext.layers.length; i++) {
|
|
@@ -92,11 +91,16 @@ export function computeMapContextDiff(
|
|
|
92
91
|
}
|
|
93
92
|
}
|
|
94
93
|
|
|
94
|
+
const viewChanges =
|
|
95
|
+
getHash(nextContext.view) !== getHash(previousContext.view)
|
|
96
|
+
? { ...nextContext.view }
|
|
97
|
+
: undefined;
|
|
98
|
+
|
|
95
99
|
return {
|
|
96
100
|
layersAdded,
|
|
97
101
|
layersChanged,
|
|
98
102
|
layersRemoved,
|
|
99
103
|
layersReordered,
|
|
100
|
-
viewChanges,
|
|
104
|
+
...(viewChanges && { viewChanges }),
|
|
101
105
|
};
|
|
102
106
|
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { describe } from "vitest";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
addLayerToContext,
|
|
4
|
+
changeLayerPositionInContext,
|
|
5
|
+
getLayerHash,
|
|
6
|
+
getLayerPosition,
|
|
7
|
+
isLayerSame,
|
|
8
|
+
isLayerSameAndUnchanged,
|
|
9
|
+
removeLayerFromContext,
|
|
10
|
+
replaceLayerInContext,
|
|
8
11
|
} from "./map-context";
|
|
9
12
|
import { MapContext } from "../model";
|
|
10
13
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
SAMPLE_CONTEXT,
|
|
15
|
+
SAMPLE_LAYER1,
|
|
16
|
+
SAMPLE_LAYER2,
|
|
17
|
+
SAMPLE_LAYER3,
|
|
14
18
|
} from "../../fixtures/map-context.fixtures";
|
|
15
19
|
|
|
16
20
|
describe("Map context utils", () => {
|
|
@@ -318,10 +322,14 @@ describe("Map context utils", () => {
|
|
|
318
322
|
};
|
|
319
323
|
const newLayer = { ...SAMPLE_LAYER2, name: "newLayer" };
|
|
320
324
|
const newContext = addLayerToContext(context, newLayer, 1);
|
|
321
|
-
expect(newContext.layers).toEqual([
|
|
325
|
+
expect(newContext.layers).toEqual([
|
|
326
|
+
SAMPLE_LAYER1,
|
|
327
|
+
newLayer,
|
|
328
|
+
SAMPLE_LAYER2,
|
|
329
|
+
]);
|
|
322
330
|
});
|
|
323
331
|
});
|
|
324
|
-
describe("removeLayerFromContext", ()
|
|
332
|
+
describe("removeLayerFromContext", () => {
|
|
325
333
|
it("removes a layer from the context", () => {
|
|
326
334
|
const context: MapContext = {
|
|
327
335
|
...SAMPLE_CONTEXT,
|
|
@@ -333,24 +341,32 @@ describe("Map context utils", () => {
|
|
|
333
341
|
});
|
|
334
342
|
describe("replaceLayerInContext", () => {
|
|
335
343
|
it("replaces a layer in the context", () => {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
+
const context: MapContext = {
|
|
345
|
+
...SAMPLE_CONTEXT,
|
|
346
|
+
layers: [SAMPLE_LAYER1, SAMPLE_LAYER2],
|
|
347
|
+
};
|
|
348
|
+
const replacementLayer = { ...SAMPLE_LAYER3 };
|
|
349
|
+
const existingLayer = { ...SAMPLE_LAYER1 };
|
|
350
|
+
const newContext = replaceLayerInContext(
|
|
351
|
+
context,
|
|
352
|
+
existingLayer,
|
|
353
|
+
replacementLayer,
|
|
354
|
+
);
|
|
355
|
+
expect(newContext.layers).toEqual([replacementLayer, SAMPLE_LAYER2]);
|
|
344
356
|
});
|
|
345
357
|
});
|
|
346
358
|
describe("changeLayerPositionInContext", () => {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
359
|
+
it("changes the position of a layer in the context", () => {
|
|
360
|
+
const context: MapContext = {
|
|
361
|
+
...SAMPLE_CONTEXT,
|
|
362
|
+
layers: [SAMPLE_LAYER1, SAMPLE_LAYER2],
|
|
363
|
+
};
|
|
364
|
+
const newContext = changeLayerPositionInContext(
|
|
365
|
+
context,
|
|
366
|
+
SAMPLE_LAYER1,
|
|
367
|
+
1,
|
|
368
|
+
);
|
|
369
|
+
expect(newContext.layers).toEqual([SAMPLE_LAYER2, SAMPLE_LAYER1]);
|
|
370
|
+
});
|
|
355
371
|
});
|
|
356
372
|
});
|
package/lib/utils/map-context.ts
CHANGED
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
import { MapContext, MapContextLayer } from "../model";
|
|
2
|
+
import { getHash } from "./hash";
|
|
2
3
|
|
|
3
4
|
export function getLayerHash(
|
|
4
5
|
layer: MapContextLayer,
|
|
5
6
|
includeExtras = false,
|
|
6
7
|
): string {
|
|
7
|
-
|
|
8
|
-
if (input instanceof Object) {
|
|
9
|
-
const obj: Record<string, string> = {};
|
|
10
|
-
const keys = Object.keys(input).sort();
|
|
11
|
-
for (const key of keys) {
|
|
12
|
-
if (!includeExtras && key === "extras") continue;
|
|
13
|
-
obj[key] = getHash(input[key as keyof typeof input]);
|
|
14
|
-
}
|
|
15
|
-
const hash = JSON.stringify(obj)
|
|
16
|
-
.split("")
|
|
17
|
-
.reduce((prev, curr) => (prev << 5) - prev + curr.charCodeAt(0), 0);
|
|
18
|
-
return (hash >>> 0).toString();
|
|
19
|
-
} else {
|
|
20
|
-
return JSON.stringify(input);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return getHash(layer);
|
|
8
|
+
return getHash(layer, includeExtras ? [] : ["extras"]);
|
|
24
9
|
}
|
|
25
10
|
|
|
26
11
|
export function isLayerSame(
|
|
@@ -64,7 +49,7 @@ export function addLayerToContext(
|
|
|
64
49
|
layerModel: MapContextLayer,
|
|
65
50
|
position?: number,
|
|
66
51
|
): MapContext {
|
|
67
|
-
const newContext = { ...context, layers: [...context.layers]};
|
|
52
|
+
const newContext = { ...context, layers: [...context.layers] };
|
|
68
53
|
if (position !== undefined) {
|
|
69
54
|
newContext.layers.splice(position, 0, layerModel);
|
|
70
55
|
} else {
|
|
@@ -82,15 +67,15 @@ export function addLayerToContext(
|
|
|
82
67
|
*/
|
|
83
68
|
|
|
84
69
|
export function removeLayerFromContext(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
70
|
+
context: MapContext,
|
|
71
|
+
layerModel: MapContextLayer,
|
|
72
|
+
): MapContext {
|
|
73
|
+
const newContext = { ...context, layers: [...context.layers] };
|
|
74
|
+
const position = getLayerPosition(context, layerModel);
|
|
75
|
+
if (position >= 0) {
|
|
76
|
+
newContext.layers.splice(position, 1);
|
|
77
|
+
}
|
|
78
|
+
return newContext;
|
|
94
79
|
}
|
|
95
80
|
|
|
96
81
|
/**
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { createViewFromLayer } from "./view";
|
|
2
|
+
import {
|
|
3
|
+
MapContextLayerGeojson,
|
|
4
|
+
MapContextLayerWfs,
|
|
5
|
+
MapContextLayerWms,
|
|
6
|
+
MapContextLayerWmts,
|
|
7
|
+
} from "../model";
|
|
8
|
+
|
|
9
|
+
vitest.mock("@camptocamp/ogc-client", () => ({
|
|
10
|
+
WmsEndpoint: class {
|
|
11
|
+
constructor() {}
|
|
12
|
+
isReady() {
|
|
13
|
+
return Promise.resolve({
|
|
14
|
+
getLayerByName: (name: string) => {
|
|
15
|
+
if (name.includes("error")) {
|
|
16
|
+
throw new Error("Something went wrong");
|
|
17
|
+
}
|
|
18
|
+
let boundingBoxes;
|
|
19
|
+
if (name.includes("nobbox")) {
|
|
20
|
+
boundingBoxes = {};
|
|
21
|
+
} else if (name.includes("4326")) {
|
|
22
|
+
boundingBoxes = {
|
|
23
|
+
"EPSG:4326": [1, 2.6, 3.3, 4.2],
|
|
24
|
+
"CRS:84": [2.3, 50.6, 2.8, 50.9],
|
|
25
|
+
};
|
|
26
|
+
} else if (name.includes("2154")) {
|
|
27
|
+
boundingBoxes = {
|
|
28
|
+
"EPSG:2154": [650796.4, 7060330.6, 690891.3, 7090402.2],
|
|
29
|
+
};
|
|
30
|
+
} else {
|
|
31
|
+
boundingBoxes = {
|
|
32
|
+
"CRS:84": [2.3, 50.6, 2.8, 50.9],
|
|
33
|
+
"EPSG:2154": [650796.4, 7060330.6, 690891.3, 7090402.2],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
name,
|
|
38
|
+
boundingBoxes,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
WmtsEndpoint: class {
|
|
45
|
+
constructor() {}
|
|
46
|
+
isReady() {
|
|
47
|
+
return Promise.resolve({
|
|
48
|
+
getSingleLayerName: () => "layerName",
|
|
49
|
+
getLayerByName: (name: string) => ({
|
|
50
|
+
latLonBoundingBox: [1, 2.6, 3.3, 4.2],
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
WfsEndpoint: class {
|
|
56
|
+
constructor(private url: string) {}
|
|
57
|
+
isReady() {
|
|
58
|
+
return Promise.resolve({
|
|
59
|
+
getFeatureTypeSummary: (featureType: string) => {
|
|
60
|
+
return {
|
|
61
|
+
name: featureType,
|
|
62
|
+
boundingBox: [1.33, 48.81, 4.3, 51.1],
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
describe("view", () => {
|
|
71
|
+
describe("createViewFromLayer", () => {
|
|
72
|
+
it("should return view for WMS layer", async () => {
|
|
73
|
+
const layer = {
|
|
74
|
+
type: "wms",
|
|
75
|
+
name: "mock_4326",
|
|
76
|
+
url: "http://mock/wms",
|
|
77
|
+
} as MapContextLayerWms;
|
|
78
|
+
const view = await createViewFromLayer(layer);
|
|
79
|
+
expect(view).toEqual({
|
|
80
|
+
extent: [1, 2.6, 3.3, 4.2],
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should return view for WMTS layer", async () => {
|
|
85
|
+
const layer = {
|
|
86
|
+
type: "wmts",
|
|
87
|
+
name: "mock",
|
|
88
|
+
url: "http://mock/wmts",
|
|
89
|
+
} as MapContextLayerWmts;
|
|
90
|
+
const view = await createViewFromLayer(layer);
|
|
91
|
+
expect(view).toEqual({
|
|
92
|
+
extent: [1, 2.6, 3.3, 4.2],
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should return view for WFS layer", async () => {
|
|
97
|
+
const layer = {
|
|
98
|
+
type: "wfs",
|
|
99
|
+
featureType: "mock",
|
|
100
|
+
url: "http://mock/wfs",
|
|
101
|
+
} as MapContextLayerWfs;
|
|
102
|
+
const view = await createViewFromLayer(layer);
|
|
103
|
+
expect(view).toEqual({
|
|
104
|
+
extent: [1.33, 48.81, 4.3, 51.1],
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should return view for GeoJSON layer", async () => {
|
|
109
|
+
const layer = {
|
|
110
|
+
type: "geojson",
|
|
111
|
+
data: {
|
|
112
|
+
type: "FeatureCollection",
|
|
113
|
+
features: [
|
|
114
|
+
{
|
|
115
|
+
type: "Feature",
|
|
116
|
+
properties: {},
|
|
117
|
+
geometry: {
|
|
118
|
+
type: "Point",
|
|
119
|
+
coordinates: [125.6, 10.1],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: "Feature",
|
|
124
|
+
properties: {},
|
|
125
|
+
geometry: {
|
|
126
|
+
type: "Point",
|
|
127
|
+
coordinates: [100, 200],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
} as MapContextLayerGeojson;
|
|
133
|
+
const view = await createViewFromLayer(layer);
|
|
134
|
+
expect(view).toEqual({
|
|
135
|
+
extent: [100, 10.1, 125.6, 200],
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should throw error for unsupported layer type", async () => {
|
|
140
|
+
const layer = { type: "unsupported" } as any;
|
|
141
|
+
await expect(createViewFromLayer(layer)).rejects.toThrow(
|
|
142
|
+
"Unsupported layer type: unsupported",
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { transformExtent } from "ol/proj";
|
|
2
|
+
import { WfsEndpoint, WmsEndpoint, WmtsEndpoint } from "@camptocamp/ogc-client";
|
|
3
|
+
import { LONLAT_CRS_CODES } from "../constant/projections";
|
|
4
|
+
import { fromEPSGCode, register } from "ol/proj/proj4";
|
|
5
|
+
import GeoJSON from "ol/format/GeoJSON";
|
|
6
|
+
import { extend } from "ol/extent";
|
|
7
|
+
import Feature from "ol/Feature";
|
|
8
|
+
import proj4 from "proj4";
|
|
9
|
+
import {
|
|
10
|
+
Extent,
|
|
11
|
+
MapContextLayer,
|
|
12
|
+
MapContextLayerWms,
|
|
13
|
+
MapContextLayerWmts,
|
|
14
|
+
MapContextView,
|
|
15
|
+
ViewByExtent,
|
|
16
|
+
} from "../model";
|
|
17
|
+
import { FeatureCollection, Geometry } from "geojson";
|
|
18
|
+
|
|
19
|
+
const GEOJSON = new GeoJSON();
|
|
20
|
+
|
|
21
|
+
export async function createViewFromLayer(
|
|
22
|
+
layer: MapContextLayer,
|
|
23
|
+
): Promise<MapContextView | null> {
|
|
24
|
+
if (layer.type === "wms") {
|
|
25
|
+
return await getWmsLayerExtent(layer);
|
|
26
|
+
} else if (layer.type === "wmts") {
|
|
27
|
+
return await getWmtsLayerExtent(layer);
|
|
28
|
+
} else if (layer.type === "geojson" && layer.data) {
|
|
29
|
+
return computeExtentFromGeojson(layer.data);
|
|
30
|
+
} else if (layer.type === "wfs") {
|
|
31
|
+
return await getWfsLayerExtent(layer);
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error(`Unsupported layer type: ${layer.type}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function computeExtentFromGeojson(
|
|
38
|
+
data: FeatureCollection<Geometry | null> | string,
|
|
39
|
+
): ViewByExtent {
|
|
40
|
+
const geojson = typeof data === "string" ? JSON.parse(data) : data;
|
|
41
|
+
const features = GEOJSON.readFeatures(geojson) as Feature[];
|
|
42
|
+
const extent = features.reduce(
|
|
43
|
+
(prev, curr) => {
|
|
44
|
+
const geom = curr.getGeometry();
|
|
45
|
+
if (!geom) return prev;
|
|
46
|
+
return extend(prev, geom.getExtent()) as Extent;
|
|
47
|
+
},
|
|
48
|
+
[Infinity, Infinity, -Infinity, -Infinity] as Extent,
|
|
49
|
+
) as Extent;
|
|
50
|
+
return {
|
|
51
|
+
extent,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function getWmsLayerExtent(
|
|
56
|
+
layer: MapContextLayerWms,
|
|
57
|
+
): Promise<ViewByExtent | null> {
|
|
58
|
+
const endpoint = await new WmsEndpoint(layer.url).isReady();
|
|
59
|
+
const { boundingBoxes } = endpoint.getLayerByName(layer.name);
|
|
60
|
+
if (!Object.keys(boundingBoxes).length) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const lonLatCRS = Object.keys(boundingBoxes).find((crs) =>
|
|
64
|
+
LONLAT_CRS_CODES.includes(crs),
|
|
65
|
+
);
|
|
66
|
+
if (lonLatCRS) {
|
|
67
|
+
return {
|
|
68
|
+
extent: boundingBoxes[lonLatCRS] as Extent,
|
|
69
|
+
};
|
|
70
|
+
} else {
|
|
71
|
+
const availableEPSGCode = Object.keys(boundingBoxes)[0];
|
|
72
|
+
register(proj4);
|
|
73
|
+
const proj = await fromEPSGCode(availableEPSGCode);
|
|
74
|
+
return {
|
|
75
|
+
extent: transformExtent(
|
|
76
|
+
boundingBoxes[availableEPSGCode],
|
|
77
|
+
proj,
|
|
78
|
+
"EPSG:4326",
|
|
79
|
+
) as Extent,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function getWmtsLayerExtent(
|
|
85
|
+
layer: MapContextLayerWmts,
|
|
86
|
+
): Promise<ViewByExtent | null> {
|
|
87
|
+
const endpoint = await new WmtsEndpoint(layer.url).isReady();
|
|
88
|
+
const layerName = endpoint.getSingleLayerName() ?? layer.name;
|
|
89
|
+
const wmtsLayer = endpoint.getLayerByName(layerName);
|
|
90
|
+
return wmtsLayer.latLonBoundingBox
|
|
91
|
+
? {
|
|
92
|
+
extent: wmtsLayer.latLonBoundingBox as Extent,
|
|
93
|
+
}
|
|
94
|
+
: null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function getWfsLayerExtent(layer: any): Promise<ViewByExtent | null> {
|
|
98
|
+
const endpoint = await new WfsEndpoint(layer.url).isReady();
|
|
99
|
+
const featureTypeSummary = endpoint.getFeatureTypeSummary(layer.featureType);
|
|
100
|
+
const boundingBox = featureTypeSummary?.boundingBox;
|
|
101
|
+
if (!boundingBox) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
extent: boundingBox,
|
|
106
|
+
};
|
|
107
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geospatial-sdk/core",
|
|
3
|
-
"version": "0.0.5-dev.
|
|
3
|
+
"version": "0.0.5-dev.14+cde890f",
|
|
4
4
|
"description": "Core functions and models for the SDK",
|
|
5
5
|
"author": "Olivia <olivia.guyot@camptocamp.com>",
|
|
6
6
|
"homepage": "",
|
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
"test": "vitest",
|
|
23
23
|
"build": "tsc"
|
|
24
24
|
},
|
|
25
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "cde890ff1c4f9dc9bb07b1c33bcbed68e06550c9"
|
|
26
26
|
}
|