@geospatial-sdk/core 0.0.5-dev.13 → 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.
@@ -0,0 +1,2 @@
1
+ export declare const LONLAT_CRS_CODES: string[];
2
+ //# sourceMappingURL=projections.d.ts.map
@@ -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: MapContextView;
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;CAC7B"}
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: 'ogcapi';
38
+ type: "ogcapi";
39
39
  url: string;
40
40
  collection: string;
41
- useTiles?: 'vector' | 'map';
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 MapContextView {
79
- center?: Coordinate;
80
- zoom?: number;
81
- extent?: Extent;
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":"AACA,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEtD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAErD,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAElD,MAAM,WAAW,mBAAmB;IAClC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAoB,SAAQ,mBAAmB;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE,IAAI,EAAE,QAAQ,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,UAAU,YAAa,SAAQ,mBAAmB;IAChD,IAAI,EAAE,SAAS,CAAC;CACjB;AACD,UAAU,mBAAoB,SAAQ,YAAY;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,KAAK,CAAC;CACd;AACD,UAAU,oBAAqB,SAAQ,YAAY;IACjD,IAAI,EAAE,iBAAiB,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC;IAClD,GAAG,CAAC,EAAE,KAAK,CAAC;CACb;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,GAAG,oBAAoB,CAAC;AAEhF;;GAEG;AACH,MAAM,MAAM,eAAe,GACvB,kBAAkB,GAClB,mBAAmB,GACnB,kBAAkB,GAClB,kBAAkB,GAClB,sBAAsB,GACtB,qBAAqB,CAAA;AAEzB,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAEtD;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,IAAI,EAAE,cAAc,CAAC;CACtB"}
1
+ {"version":3,"file":"map-context.d.ts","sourceRoot":"","sources":["../../lib/model/map-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function getHash(input: unknown, ignoreKeys?: string[]): string;
2
+ //# sourceMappingURL=hash.d.ts.map
@@ -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
+ }
@@ -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,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
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"}
@@ -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,EAKf,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,UAAU,EACvB,eAAe,EAAE,UAAU,GAC1B,cAAc,CAqEhB"}
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
- return {
73
- layersAdded,
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;AAEvD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,eAAe,EACtB,aAAa,UAAQ,GACpB,MAAM,CAkBR;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,CAClC,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,eAAe,GACxB,UAAU,CAOhB;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
+ {"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
- function getHash(input) {
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,3 @@
1
+ import { MapContextLayer, MapContextView } from "../model";
2
+ export declare function createViewFromLayer(layer: MapContextLayer): Promise<MapContextView | null>;
3
+ //# sourceMappingURL=view.d.ts.map
@@ -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"];
@@ -33,5 +33,5 @@ export interface MapContextDiff {
33
33
  layersReordered: MapContextLayerReordered[];
34
34
  layersRemoved: MapContextLayerPositioned[];
35
35
  layersAdded: MapContextLayerPositioned[];
36
- viewChanges: MapContextView;
36
+ viewChanges?: MapContextView;
37
37
  }
@@ -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: 'ogcapi'
48
- url: string
49
- collection: string
50
- useTiles?: 'vector' | 'map'
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 MapContextView {
101
- center?: Coordinate;
102
- zoom?: number;
103
- extent?: Extent;
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
+ }
@@ -1,4 +1,11 @@
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 {
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
- addLayerToContext, changeLayerPositionInContext,
4
- getLayerHash,
5
- getLayerPosition,
6
- isLayerSame,
7
- isLayerSameAndUnchanged, removeLayerFromContext, replaceLayerInContext,
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
- SAMPLE_CONTEXT,
12
- SAMPLE_LAYER1,
13
- SAMPLE_LAYER2, SAMPLE_LAYER3,
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([SAMPLE_LAYER1, newLayer, SAMPLE_LAYER2]);
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
- const context: MapContext = {
337
- ...SAMPLE_CONTEXT,
338
- layers: [SAMPLE_LAYER1, SAMPLE_LAYER2],
339
- };
340
- const replacementLayer = { ...SAMPLE_LAYER3};
341
- const existingLayer = { ...SAMPLE_LAYER1};
342
- const newContext = replaceLayerInContext(context, existingLayer, replacementLayer);
343
- expect(newContext.layers).toEqual([replacementLayer, SAMPLE_LAYER2]);
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
- it("changes the position of a layer in the context", () => {
348
- const context: MapContext = {
349
- ...SAMPLE_CONTEXT,
350
- layers: [SAMPLE_LAYER1, SAMPLE_LAYER2],
351
- };
352
- const newContext = changeLayerPositionInContext(context, SAMPLE_LAYER1, 1);
353
- expect(newContext.layers).toEqual([SAMPLE_LAYER2, SAMPLE_LAYER1]);
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
  });
@@ -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
- function getHash(input: unknown): string {
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
- context: MapContext,
86
- layerModel: MapContextLayer,
87
- ): MapContext {
88
- const newContext = { ...context, layers: [...context.layers] };
89
- const position = getLayerPosition(context, layerModel);
90
- if (position >= 0) {
91
- newContext.layers.splice(position, 1);
92
- }
93
- return newContext;
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.13+4cd825d",
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": "4cd825d11d70758c227ca0b955bcf571282bfea7"
25
+ "gitHead": "cde890ff1c4f9dc9bb07b1c33bcbed68e06550c9"
26
26
  }