@geospatial-sdk/openlayers 0.0.5-dev.15 → 0.0.5-dev.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/map/create-map.d.ts.map +1 -1
- package/dist/map/create-map.js +10 -2
- package/dist/map/index.d.ts +1 -0
- package/dist/map/index.d.ts.map +1 -1
- package/dist/map/index.js +1 -0
- package/dist/map/register-events.d.ts +12 -0
- package/dist/map/register-events.d.ts.map +1 -0
- package/dist/map/register-events.js +111 -0
- package/lib/map/create-map.test.ts +24 -0
- package/lib/map/create-map.ts +13 -2
- package/lib/map/index.ts +1 -0
- package/lib/map/register-events.test.ts +196 -0
- package/lib/map/register-events.ts +156 -0
- package/package.json +8 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-map.d.ts","sourceRoot":"","sources":["../../lib/map/create-map.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,eAAe,EACf,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,GAAG,MAAM,QAAQ,CAAC;AACzB,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,KAAK,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-map.d.ts","sourceRoot":"","sources":["../../lib/map/create-map.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,eAAe,EACf,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,GAAG,MAAM,QAAQ,CAAC;AACzB,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,KAAK,MAAM,gBAAgB,CAAC;AA0BnC,wBAAsB,WAAW,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAwL7E;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAyBpE;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,UAAU,EACnB,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,GAC5B,OAAO,CAAC,GAAG,CAAC,CAKd;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,GAAG,CAAC,CAQd"}
|
package/dist/map/create-map.js
CHANGED
|
@@ -23,6 +23,7 @@ import VectorTileLayer from "ol/layer/VectorTile";
|
|
|
23
23
|
import { OGCMapTile, OGCVectorTile, WMTS } from "ol/source";
|
|
24
24
|
import { MVT } from "ol/format";
|
|
25
25
|
import { OgcApiEndpoint, WfsEndpoint, WmtsEndpoint, } from "@camptocamp/ogc-client";
|
|
26
|
+
import { MapboxVectorLayer } from "ol-mapbox-style";
|
|
26
27
|
const GEOJSON = new GeoJSON();
|
|
27
28
|
const WFS_MAX_FEATURES = 10000;
|
|
28
29
|
export function createLayer(layerModel) {
|
|
@@ -44,7 +45,7 @@ export function createLayer(layerModel) {
|
|
|
44
45
|
layer = new TileLayer({
|
|
45
46
|
source: new TileWMS({
|
|
46
47
|
url: removeSearchParams(layerModel.url, ["request", "service"]),
|
|
47
|
-
params: { LAYERS: layerModel.name },
|
|
48
|
+
params: Object.assign({ LAYERS: layerModel.name }, (layerModel.style && { STYLES: layerModel.style })),
|
|
48
49
|
gutter: 20,
|
|
49
50
|
attributions: layerModel.attributions,
|
|
50
51
|
}),
|
|
@@ -105,6 +106,13 @@ export function createLayer(layerModel) {
|
|
|
105
106
|
layer = olLayer;
|
|
106
107
|
break;
|
|
107
108
|
}
|
|
109
|
+
case "maplibre-style": {
|
|
110
|
+
layer = new MapboxVectorLayer({
|
|
111
|
+
styleUrl: layerModel.styleUrl,
|
|
112
|
+
accessToken: layerModel.accessToken,
|
|
113
|
+
});
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
108
116
|
case "geojson": {
|
|
109
117
|
if (layerModel.url !== undefined) {
|
|
110
118
|
layer = new VectorLayer({
|
|
@@ -142,7 +150,7 @@ export function createLayer(layerModel) {
|
|
|
142
150
|
break;
|
|
143
151
|
}
|
|
144
152
|
case "ogcapi": {
|
|
145
|
-
const ogcEndpoint =
|
|
153
|
+
const ogcEndpoint = new OgcApiEndpoint(layerModel.url);
|
|
146
154
|
let layerUrl;
|
|
147
155
|
if (layerModel.useTiles) {
|
|
148
156
|
if (layerModel.useTiles === "vector") {
|
package/dist/map/index.d.ts
CHANGED
package/dist/map/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/map/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/map/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/map/index.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Map from "ol/Map";
|
|
2
|
+
import { MapEvent, MapEventType } from "@geospatial-sdk/core";
|
|
3
|
+
import { Coordinate } from "ol/coordinate";
|
|
4
|
+
import TileWMS from "ol/source/TileWMS";
|
|
5
|
+
import ImageWMS from "ol/source/ImageWMS";
|
|
6
|
+
import { Pixel } from "ol/pixel";
|
|
7
|
+
import type { Feature } from "geojson";
|
|
8
|
+
export declare function getFeaturesFromVectorSources(olMap: Map, pixel: Pixel): Feature[];
|
|
9
|
+
export declare function getGFIUrl(source: TileWMS | ImageWMS, map: Map, coordinate: Coordinate): string | null;
|
|
10
|
+
export declare function getFeaturesFromWmsSources(olMap: Map, coordinate: Coordinate): Promise<Feature[]>;
|
|
11
|
+
export declare function listen(map: Map, event: MapEventType, callback: (event: MapEvent) => void): void;
|
|
12
|
+
//# sourceMappingURL=register-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-events.d.ts","sourceRoot":"","sources":["../../lib/map/register-events.ts"],"names":[],"mappings":"AAAA,OAAO,GAA4B,MAAM,QAAQ,CAAC;AAClD,OAAO,EAIL,QAAQ,EACR,YAAY,EACb,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,OAAO,MAAM,mBAAmB,CAAC;AACxC,OAAO,QAAQ,MAAM,oBAAoB,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAK1D,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,GAAG,EACV,KAAK,EAAE,KAAK,GACX,OAAO,EAAE,CAOX;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,OAAO,GAAG,QAAQ,EAC1B,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,UAAU,GACrB,MAAM,GAAG,IAAI,CAWf;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,GAAG,EACV,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,OAAO,EAAE,CAAC,CA2BpB;AAyCD,wBAAgB,MAAM,CACpB,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,QA6BpC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
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 { FeaturesClickEventType, FeaturesHoverEventType, MapClickEventType, } from "@geospatial-sdk/core";
|
|
11
|
+
import { toLonLat } from "ol/proj";
|
|
12
|
+
import GeoJSON from "ol/format/GeoJSON";
|
|
13
|
+
import TileWMS from "ol/source/TileWMS";
|
|
14
|
+
import ImageWMS from "ol/source/ImageWMS";
|
|
15
|
+
import Layer from "ol/layer/Layer";
|
|
16
|
+
import throttle from "lodash.throttle";
|
|
17
|
+
const GEOJSON = new GeoJSON();
|
|
18
|
+
export function getFeaturesFromVectorSources(olMap, pixel) {
|
|
19
|
+
const olFeatures = olMap.getFeaturesAtPixel(pixel);
|
|
20
|
+
const { features } = GEOJSON.writeFeaturesObject(olFeatures);
|
|
21
|
+
if (!features) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return features;
|
|
25
|
+
}
|
|
26
|
+
export function getGFIUrl(source, map, coordinate) {
|
|
27
|
+
var _a;
|
|
28
|
+
const view = map.getView();
|
|
29
|
+
const projection = view.getProjection();
|
|
30
|
+
const resolution = view.getResolution();
|
|
31
|
+
const params = Object.assign(Object.assign({}, source.getParams()), { INFO_FORMAT: "application/json" });
|
|
32
|
+
return ((_a = source.getFeatureInfoUrl(coordinate, resolution, projection, params)) !== null && _a !== void 0 ? _a : null);
|
|
33
|
+
}
|
|
34
|
+
export function getFeaturesFromWmsSources(olMap, coordinate) {
|
|
35
|
+
const wmsSources = olMap
|
|
36
|
+
.getLayers()
|
|
37
|
+
.getArray()
|
|
38
|
+
.filter((layer) => layer instanceof Layer &&
|
|
39
|
+
(layer.getSource() instanceof TileWMS ||
|
|
40
|
+
layer.getSource() instanceof ImageWMS))
|
|
41
|
+
.map((layer) => layer.getSource());
|
|
42
|
+
if (!wmsSources.length) {
|
|
43
|
+
return Promise.resolve([]);
|
|
44
|
+
}
|
|
45
|
+
const gfiUrls = wmsSources.reduce((urls, source) => {
|
|
46
|
+
const gfiUrl = getGFIUrl(source, olMap, coordinate);
|
|
47
|
+
return gfiUrl ? [...urls, gfiUrl] : urls;
|
|
48
|
+
}, []);
|
|
49
|
+
return Promise.all(gfiUrls.map((url) => fetch(url)
|
|
50
|
+
.then((response) => response.json())
|
|
51
|
+
.then((collection) => collection.features))).then((features) => features.flat());
|
|
52
|
+
}
|
|
53
|
+
const getFeaturesFromWmsSourcesThrottled = throttle(getFeaturesFromWmsSources, 250);
|
|
54
|
+
function readFeaturesAtPixel(map, event) {
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
return [
|
|
57
|
+
...getFeaturesFromVectorSources(map, event.pixel),
|
|
58
|
+
...(yield getFeaturesFromWmsSourcesThrottled(map, event.coordinate)),
|
|
59
|
+
];
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function registerFeatureClickEvent(map) {
|
|
63
|
+
if (map.get(FeaturesClickEventType))
|
|
64
|
+
return;
|
|
65
|
+
map.on("click", (event) => __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
const features = yield readFeaturesAtPixel(map, event);
|
|
67
|
+
map.dispatchEvent({
|
|
68
|
+
type: FeaturesClickEventType,
|
|
69
|
+
features,
|
|
70
|
+
});
|
|
71
|
+
}));
|
|
72
|
+
map.set(FeaturesClickEventType, true);
|
|
73
|
+
}
|
|
74
|
+
function registerFeatureHoverEvent(map) {
|
|
75
|
+
if (map.get(FeaturesHoverEventType))
|
|
76
|
+
return;
|
|
77
|
+
map.on("pointermove", (event) => __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
const features = yield readFeaturesAtPixel(map, event);
|
|
79
|
+
map.dispatchEvent({
|
|
80
|
+
type: FeaturesHoverEventType,
|
|
81
|
+
features,
|
|
82
|
+
});
|
|
83
|
+
}));
|
|
84
|
+
map.set(FeaturesHoverEventType, true);
|
|
85
|
+
}
|
|
86
|
+
export function listen(map, event, callback) {
|
|
87
|
+
switch (event) {
|
|
88
|
+
case FeaturesClickEventType:
|
|
89
|
+
registerFeatureClickEvent(map);
|
|
90
|
+
// we're using a custom event type here so we need to cast to unknown first
|
|
91
|
+
map.on(event, (event) => {
|
|
92
|
+
callback(event);
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
case FeaturesHoverEventType:
|
|
96
|
+
registerFeatureHoverEvent(map);
|
|
97
|
+
// see comment above
|
|
98
|
+
map.on(event, (event) => {
|
|
99
|
+
callback(event);
|
|
100
|
+
});
|
|
101
|
+
break;
|
|
102
|
+
case MapClickEventType:
|
|
103
|
+
map.on("click", (event) => {
|
|
104
|
+
const coordinate = toLonLat(event.pixel, map.getView().getProjection());
|
|
105
|
+
callback({ coordinate });
|
|
106
|
+
});
|
|
107
|
+
break;
|
|
108
|
+
default:
|
|
109
|
+
throw new Error(`Unrecognized event type: ${event}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
MAP_CTX_FIXTURE,
|
|
13
13
|
MAP_CTX_LAYER_GEOJSON_FIXTURE,
|
|
14
14
|
MAP_CTX_LAYER_GEOJSON_REMOTE_FIXTURE,
|
|
15
|
+
MAP_CTX_LAYER_MAPBLIBRE_STYLE_FIXTURE,
|
|
15
16
|
MAP_CTX_LAYER_OGCAPI_FIXTURE,
|
|
16
17
|
MAP_CTX_LAYER_WFS_FIXTURE,
|
|
17
18
|
MAP_CTX_LAYER_WMS_FIXTURE,
|
|
@@ -32,6 +33,8 @@ import {
|
|
|
32
33
|
resetMapFromContext,
|
|
33
34
|
} from "./create-map";
|
|
34
35
|
import WMTS from "ol/source/WMTS";
|
|
36
|
+
import { VectorTile } from "ol/source";
|
|
37
|
+
import { MapboxVectorLayer } from "ol-mapbox-style";
|
|
35
38
|
|
|
36
39
|
describe("MapContextService", () => {
|
|
37
40
|
describe("#createLayer", () => {
|
|
@@ -116,6 +119,7 @@ describe("MapContextService", () => {
|
|
|
116
119
|
const source = layer.getSource() as TileWMS;
|
|
117
120
|
const params = source.getParams();
|
|
118
121
|
expect(params.LAYERS).toBe((layerModel as MapContextLayerWms).name);
|
|
122
|
+
expect(params.STYLES).toBe((layerModel as MapContextLayerWms).style);
|
|
119
123
|
});
|
|
120
124
|
it("set correct url without existing REQUEST and SERVICE params", () => {
|
|
121
125
|
const source = layer.getSource() as TileWMS;
|
|
@@ -291,6 +295,26 @@ describe("MapContextService", () => {
|
|
|
291
295
|
]);
|
|
292
296
|
});
|
|
293
297
|
});
|
|
298
|
+
|
|
299
|
+
describe("Maplibre Style", () => {
|
|
300
|
+
beforeEach(async () => {
|
|
301
|
+
(layerModel = MAP_CTX_LAYER_MAPBLIBRE_STYLE_FIXTURE),
|
|
302
|
+
(layer = await createLayer(layerModel));
|
|
303
|
+
});
|
|
304
|
+
it("create a tile layer", () => {
|
|
305
|
+
expect(layer).toBeTruthy();
|
|
306
|
+
expect(layer).toBeInstanceOf(MapboxVectorLayer);
|
|
307
|
+
});
|
|
308
|
+
it("set correct layer properties", () => {
|
|
309
|
+
expect(layer.getVisible()).toBe(true);
|
|
310
|
+
expect(layer.getOpacity()).toBe(1);
|
|
311
|
+
expect(layer.get("label")).toBeUndefined();
|
|
312
|
+
});
|
|
313
|
+
it("create a Vector Tile source", () => {
|
|
314
|
+
const source = layer.getSource();
|
|
315
|
+
expect(source).toBeInstanceOf(VectorTile);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
294
318
|
});
|
|
295
319
|
|
|
296
320
|
describe("#createView", () => {
|
package/lib/map/create-map.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
WfsEndpoint,
|
|
28
28
|
WmtsEndpoint,
|
|
29
29
|
} from "@camptocamp/ogc-client";
|
|
30
|
+
import { MapboxVectorLayer } from "ol-mapbox-style";
|
|
30
31
|
|
|
31
32
|
const GEOJSON = new GeoJSON();
|
|
32
33
|
const WFS_MAX_FEATURES = 10000;
|
|
@@ -48,7 +49,10 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
|
|
|
48
49
|
layer = new TileLayer({
|
|
49
50
|
source: new TileWMS({
|
|
50
51
|
url: removeSearchParams(layerModel.url, ["request", "service"]),
|
|
51
|
-
params: {
|
|
52
|
+
params: {
|
|
53
|
+
LAYERS: layerModel.name,
|
|
54
|
+
...(layerModel.style && { STYLES: layerModel.style }),
|
|
55
|
+
},
|
|
52
56
|
gutter: 20,
|
|
53
57
|
attributions: layerModel.attributions,
|
|
54
58
|
}),
|
|
@@ -112,6 +116,13 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
|
|
|
112
116
|
layer = olLayer;
|
|
113
117
|
break;
|
|
114
118
|
}
|
|
119
|
+
case "maplibre-style": {
|
|
120
|
+
layer = new MapboxVectorLayer({
|
|
121
|
+
styleUrl: layerModel.styleUrl,
|
|
122
|
+
accessToken: layerModel.accessToken,
|
|
123
|
+
}) as unknown as Layer;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
115
126
|
case "geojson": {
|
|
116
127
|
if (layerModel.url !== undefined) {
|
|
117
128
|
layer = new VectorLayer({
|
|
@@ -147,7 +158,7 @@ export async function createLayer(layerModel: MapContextLayer): Promise<Layer> {
|
|
|
147
158
|
break;
|
|
148
159
|
}
|
|
149
160
|
case "ogcapi": {
|
|
150
|
-
const ogcEndpoint =
|
|
161
|
+
const ogcEndpoint = new OgcApiEndpoint(layerModel.url);
|
|
151
162
|
let layerUrl: string;
|
|
152
163
|
if (layerModel.useTiles) {
|
|
153
164
|
if (layerModel.useTiles === "vector") {
|
package/lib/map/index.ts
CHANGED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import Map from "ol/Map";
|
|
2
|
+
import { Mock } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
getFeaturesFromVectorSources,
|
|
5
|
+
getFeaturesFromWmsSources,
|
|
6
|
+
getGFIUrl,
|
|
7
|
+
listen,
|
|
8
|
+
} from "./register-events";
|
|
9
|
+
import { Collection, MapBrowserEvent, Object as BaseObject } from "ol";
|
|
10
|
+
import View from "ol/View";
|
|
11
|
+
import { toLonLat } from "ol/proj";
|
|
12
|
+
import TileWMS from "ol/source/TileWMS";
|
|
13
|
+
import VectorSource from "ol/source/Vector";
|
|
14
|
+
import VectorLayer from "ol/layer/Vector";
|
|
15
|
+
import Point from "ol/geom/Point";
|
|
16
|
+
import TileLayer from "ol/layer/Tile";
|
|
17
|
+
import OlFeature from "ol/Feature";
|
|
18
|
+
|
|
19
|
+
const gfiResult = {
|
|
20
|
+
type: "Feature",
|
|
21
|
+
properties: {
|
|
22
|
+
density: 123,
|
|
23
|
+
},
|
|
24
|
+
geometry: null,
|
|
25
|
+
};
|
|
26
|
+
function createWmsSource() {
|
|
27
|
+
return new TileWMS({
|
|
28
|
+
url: "http://my.service.org/wms?SERVICE=WMS",
|
|
29
|
+
params: {
|
|
30
|
+
LAYERS: "layerName",
|
|
31
|
+
FORMAT: "image/png",
|
|
32
|
+
TRANSPARENT: true,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function createMap(): Map {
|
|
37
|
+
const wmsLayer = new TileLayer({
|
|
38
|
+
source: createWmsSource(),
|
|
39
|
+
});
|
|
40
|
+
const feature = new OlFeature({
|
|
41
|
+
geometry: new Point([100, 200]),
|
|
42
|
+
});
|
|
43
|
+
const vectorLayer = new VectorLayer({
|
|
44
|
+
source: new VectorSource({
|
|
45
|
+
features: [feature],
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
const view = new View({
|
|
49
|
+
projection: "EPSG:3857",
|
|
50
|
+
resolution: 1,
|
|
51
|
+
center: [0, 0],
|
|
52
|
+
});
|
|
53
|
+
const map = new BaseObject() as Map;
|
|
54
|
+
Object.defineProperties(map, {
|
|
55
|
+
getView: { value: vi.fn(() => view) },
|
|
56
|
+
getLayers: { value: vi.fn(() => new Collection([wmsLayer, vectorLayer])) },
|
|
57
|
+
getEventPixel: { value: vi.fn(() => [10, 10]) },
|
|
58
|
+
getCoordinateFromPixel: { value: vi.fn(() => [123, 123]) },
|
|
59
|
+
getFeaturesAtPixel: { value: vi.fn(() => [feature]) },
|
|
60
|
+
});
|
|
61
|
+
return map;
|
|
62
|
+
}
|
|
63
|
+
function createMapEvent(map: Map, type: string) {
|
|
64
|
+
return new MapBrowserEvent(
|
|
65
|
+
type,
|
|
66
|
+
map,
|
|
67
|
+
new MouseEvent(type, {
|
|
68
|
+
clientX: 10,
|
|
69
|
+
clientY: 10,
|
|
70
|
+
}),
|
|
71
|
+
false,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe("event registration", () => {
|
|
76
|
+
let map: Map;
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
map = createMap();
|
|
79
|
+
vi.spyOn(global, "fetch").mockImplementation(() =>
|
|
80
|
+
Promise.resolve({
|
|
81
|
+
ok: true,
|
|
82
|
+
json: () => Promise.resolve({ features: [gfiResult] }),
|
|
83
|
+
} as Response),
|
|
84
|
+
);
|
|
85
|
+
vi.useFakeTimers();
|
|
86
|
+
});
|
|
87
|
+
describe("getFeaturesFromVectorSources", () => {
|
|
88
|
+
it("returns an array of features", () => {
|
|
89
|
+
const features = getFeaturesFromVectorSources(map, [0, 0]);
|
|
90
|
+
expect(features).toEqual([
|
|
91
|
+
{
|
|
92
|
+
geometry: {
|
|
93
|
+
coordinates: [100, 200],
|
|
94
|
+
type: "Point",
|
|
95
|
+
},
|
|
96
|
+
properties: null,
|
|
97
|
+
type: "Feature",
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe("getGFIUrl", () => {
|
|
103
|
+
let url: string;
|
|
104
|
+
const coordinate = [-182932.49329334166, 6125319.813853541];
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
const wmsSource = createWmsSource();
|
|
107
|
+
url = getGFIUrl(wmsSource, map, coordinate) as string;
|
|
108
|
+
});
|
|
109
|
+
it("returns true", () => {
|
|
110
|
+
expect(url).toEqual(
|
|
111
|
+
"http://my.service.org/wms?SERVICE=WMS&REQUEST=GetFeatureInfo&QUERY_LAYERS=layerName&SERVICE=WMS&VERSION=1.3.0&FORMAT=image%2Fpng&STYLES=&TRANSPARENT=true&LAYERS=layerName&INFO_FORMAT=application%2Fjson&I=176&J=31&WIDTH=256&HEIGHT=256&CRS=EPSG%3A3857&BBOX=-183143.11977128312%2C6125051.950547744%2C-182837.37165814242%2C6125357.698660884",
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe("getFeaturesFromWmsSources", () => {
|
|
116
|
+
it("queries the WMS sources", async () => {
|
|
117
|
+
const features = await getFeaturesFromWmsSources(map, [0, 0]);
|
|
118
|
+
expect(features).toEqual([gfiResult]);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe("features hover event", () => {
|
|
122
|
+
let callback: Mock;
|
|
123
|
+
beforeEach(async () => {
|
|
124
|
+
callback = vi.fn();
|
|
125
|
+
listen(map, "features-hover", callback);
|
|
126
|
+
map.dispatchEvent(createMapEvent(map, "pointermove"));
|
|
127
|
+
await vi.runAllTimersAsync();
|
|
128
|
+
});
|
|
129
|
+
it("registers the event on the map", () => {
|
|
130
|
+
expect(callback).toHaveBeenCalledWith({
|
|
131
|
+
type: "features-hover",
|
|
132
|
+
features: [
|
|
133
|
+
{
|
|
134
|
+
geometry: {
|
|
135
|
+
coordinates: [100, 200],
|
|
136
|
+
type: "Point",
|
|
137
|
+
},
|
|
138
|
+
properties: null,
|
|
139
|
+
type: "Feature",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
geometry: null,
|
|
143
|
+
properties: {
|
|
144
|
+
density: 123,
|
|
145
|
+
},
|
|
146
|
+
type: "Feature",
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
target: expect.anything(),
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe("features click event", () => {
|
|
154
|
+
let callback: Mock;
|
|
155
|
+
beforeEach(async () => {
|
|
156
|
+
callback = vi.fn();
|
|
157
|
+
listen(map, "features-click", callback);
|
|
158
|
+
map.dispatchEvent(createMapEvent(map, "click"));
|
|
159
|
+
await vi.runAllTimersAsync();
|
|
160
|
+
});
|
|
161
|
+
it("registers the event on the map", () => {
|
|
162
|
+
expect(callback).toHaveBeenCalledWith({
|
|
163
|
+
type: "features-click",
|
|
164
|
+
features: [
|
|
165
|
+
{
|
|
166
|
+
geometry: {
|
|
167
|
+
coordinates: [100, 200],
|
|
168
|
+
type: "Point",
|
|
169
|
+
},
|
|
170
|
+
properties: null,
|
|
171
|
+
type: "Feature",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
geometry: null,
|
|
175
|
+
properties: {
|
|
176
|
+
density: 123,
|
|
177
|
+
},
|
|
178
|
+
type: "Feature",
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
target: expect.anything(),
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
describe("map click event", () => {
|
|
186
|
+
let callback: Mock;
|
|
187
|
+
beforeEach(() => {
|
|
188
|
+
callback = vi.fn();
|
|
189
|
+
listen(map, "map-click", callback);
|
|
190
|
+
map.dispatchEvent(createMapEvent(map, "click"));
|
|
191
|
+
});
|
|
192
|
+
it("registers the event on the map", () => {
|
|
193
|
+
expect(callback).toHaveBeenCalledWith({ coordinate: toLonLat([10, 10]) });
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import Map, { MapObjectEventTypes } from "ol/Map";
|
|
2
|
+
import {
|
|
3
|
+
FeaturesClickEventType,
|
|
4
|
+
FeaturesHoverEventType,
|
|
5
|
+
MapClickEventType,
|
|
6
|
+
MapEvent,
|
|
7
|
+
MapEventType,
|
|
8
|
+
} from "@geospatial-sdk/core";
|
|
9
|
+
import { toLonLat } from "ol/proj";
|
|
10
|
+
import GeoJSON from "ol/format/GeoJSON";
|
|
11
|
+
import OlFeature from "ol/Feature";
|
|
12
|
+
import BaseEvent from "ol/events/Event";
|
|
13
|
+
import { MapBrowserEvent } from "ol";
|
|
14
|
+
import { Coordinate } from "ol/coordinate";
|
|
15
|
+
import TileWMS from "ol/source/TileWMS";
|
|
16
|
+
import ImageWMS from "ol/source/ImageWMS";
|
|
17
|
+
import Layer from "ol/layer/Layer";
|
|
18
|
+
import { Pixel } from "ol/pixel";
|
|
19
|
+
import type { Feature, FeatureCollection } from "geojson";
|
|
20
|
+
import throttle from "lodash.throttle";
|
|
21
|
+
|
|
22
|
+
const GEOJSON = new GeoJSON();
|
|
23
|
+
|
|
24
|
+
export function getFeaturesFromVectorSources(
|
|
25
|
+
olMap: Map,
|
|
26
|
+
pixel: Pixel,
|
|
27
|
+
): Feature[] {
|
|
28
|
+
const olFeatures = olMap.getFeaturesAtPixel(pixel);
|
|
29
|
+
const { features } = GEOJSON.writeFeaturesObject(olFeatures as OlFeature[]);
|
|
30
|
+
if (!features) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
return features;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getGFIUrl(
|
|
37
|
+
source: TileWMS | ImageWMS,
|
|
38
|
+
map: Map,
|
|
39
|
+
coordinate: Coordinate,
|
|
40
|
+
): string | null {
|
|
41
|
+
const view = map.getView();
|
|
42
|
+
const projection = view.getProjection();
|
|
43
|
+
const resolution = view.getResolution() as number;
|
|
44
|
+
const params = {
|
|
45
|
+
...source.getParams(),
|
|
46
|
+
INFO_FORMAT: "application/json",
|
|
47
|
+
};
|
|
48
|
+
return (
|
|
49
|
+
source.getFeatureInfoUrl(coordinate, resolution, projection, params) ?? null
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getFeaturesFromWmsSources(
|
|
54
|
+
olMap: Map,
|
|
55
|
+
coordinate: Coordinate,
|
|
56
|
+
): Promise<Feature[]> {
|
|
57
|
+
const wmsSources: (ImageWMS | TileWMS)[] = olMap
|
|
58
|
+
.getLayers()
|
|
59
|
+
.getArray()
|
|
60
|
+
.filter(
|
|
61
|
+
(layer): layer is Layer<ImageWMS | TileWMS> =>
|
|
62
|
+
layer instanceof Layer &&
|
|
63
|
+
(layer.getSource() instanceof TileWMS ||
|
|
64
|
+
layer.getSource() instanceof ImageWMS),
|
|
65
|
+
)
|
|
66
|
+
.map((layer) => layer.getSource()!);
|
|
67
|
+
|
|
68
|
+
if (!wmsSources.length) {
|
|
69
|
+
return Promise.resolve([]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const gfiUrls = wmsSources.reduce((urls, source) => {
|
|
73
|
+
const gfiUrl = getGFIUrl(source, olMap, coordinate);
|
|
74
|
+
return gfiUrl ? [...urls, gfiUrl] : urls;
|
|
75
|
+
}, [] as string[]);
|
|
76
|
+
return Promise.all(
|
|
77
|
+
gfiUrls.map((url) =>
|
|
78
|
+
fetch(url)
|
|
79
|
+
.then((response) => response.json())
|
|
80
|
+
.then((collection: FeatureCollection) => collection.features),
|
|
81
|
+
),
|
|
82
|
+
).then((features) => features.flat());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const getFeaturesFromWmsSourcesThrottled = throttle(
|
|
86
|
+
getFeaturesFromWmsSources,
|
|
87
|
+
250,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
async function readFeaturesAtPixel(
|
|
91
|
+
map: Map,
|
|
92
|
+
event: MapBrowserEvent<PointerEvent>,
|
|
93
|
+
) {
|
|
94
|
+
return [
|
|
95
|
+
...getFeaturesFromVectorSources(map, event.pixel),
|
|
96
|
+
...(await getFeaturesFromWmsSourcesThrottled(map, event.coordinate)),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function registerFeatureClickEvent(map: Map) {
|
|
101
|
+
if (map.get(FeaturesClickEventType)) return;
|
|
102
|
+
map.on("click", async (event) => {
|
|
103
|
+
const features = await readFeaturesAtPixel(map, event);
|
|
104
|
+
map.dispatchEvent({
|
|
105
|
+
type: FeaturesClickEventType,
|
|
106
|
+
features,
|
|
107
|
+
} as unknown as BaseEvent);
|
|
108
|
+
});
|
|
109
|
+
map.set(FeaturesClickEventType, true);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function registerFeatureHoverEvent(map: Map) {
|
|
113
|
+
if (map.get(FeaturesHoverEventType)) return;
|
|
114
|
+
map.on("pointermove", async (event) => {
|
|
115
|
+
const features = await readFeaturesAtPixel(map, event);
|
|
116
|
+
map.dispatchEvent({
|
|
117
|
+
type: FeaturesHoverEventType,
|
|
118
|
+
features,
|
|
119
|
+
} as unknown as BaseEvent);
|
|
120
|
+
});
|
|
121
|
+
map.set(FeaturesHoverEventType, true);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function listen(
|
|
125
|
+
map: Map,
|
|
126
|
+
event: MapEventType,
|
|
127
|
+
callback: (event: MapEvent) => void,
|
|
128
|
+
) {
|
|
129
|
+
switch (event) {
|
|
130
|
+
case FeaturesClickEventType:
|
|
131
|
+
registerFeatureClickEvent(map);
|
|
132
|
+
// we're using a custom event type here so we need to cast to unknown first
|
|
133
|
+
map.on(event as unknown as MapObjectEventTypes, (event) => {
|
|
134
|
+
callback(event as unknown as MapEvent);
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
case FeaturesHoverEventType:
|
|
138
|
+
registerFeatureHoverEvent(map);
|
|
139
|
+
// see comment above
|
|
140
|
+
map.on(event as unknown as MapObjectEventTypes, (event) => {
|
|
141
|
+
callback(event as unknown as MapEvent);
|
|
142
|
+
});
|
|
143
|
+
break;
|
|
144
|
+
case MapClickEventType:
|
|
145
|
+
map.on("click", (event) => {
|
|
146
|
+
const coordinate = toLonLat(
|
|
147
|
+
event.pixel,
|
|
148
|
+
map.getView().getProjection(),
|
|
149
|
+
) as [number, number];
|
|
150
|
+
callback({ coordinate });
|
|
151
|
+
});
|
|
152
|
+
break;
|
|
153
|
+
default:
|
|
154
|
+
throw new Error(`Unrecognized event type: ${event}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geospatial-sdk/openlayers",
|
|
3
|
-
"version": "0.0.5-dev.
|
|
3
|
+
"version": "0.0.5-dev.17+e758ce3",
|
|
4
4
|
"description": "OpenLayers-related utilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ol",
|
|
@@ -29,14 +29,17 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/chroma-js": "^2.4.3",
|
|
32
|
-
"
|
|
32
|
+
"@types/lodash.throttle": "^4.1.9",
|
|
33
|
+
"ol": "^8.2.0",
|
|
34
|
+
"ol-mapbox-style": "^12.3.5"
|
|
33
35
|
},
|
|
34
36
|
"peerDependencies": {
|
|
35
37
|
"ol": ">6.x"
|
|
36
38
|
},
|
|
37
39
|
"dependencies": {
|
|
38
|
-
"@geospatial-sdk/core": "^0.0.5-dev.
|
|
39
|
-
"chroma-js": "^2.4.2"
|
|
40
|
+
"@geospatial-sdk/core": "^0.0.5-dev.17+e758ce3",
|
|
41
|
+
"chroma-js": "^2.4.2",
|
|
42
|
+
"lodash.throttle": "^4.1.1"
|
|
40
43
|
},
|
|
41
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "e758ce3d88f26443f0680907277ae4fde3c748f9"
|
|
42
45
|
}
|