@geospatial-sdk/openlayers 0.0.5-dev.50 → 0.0.5-dev.51

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.
Files changed (37) hide show
  1. package/dist/map/constants.d.ts +2 -0
  2. package/dist/map/constants.d.ts.map +1 -0
  3. package/dist/map/constants.js +1 -0
  4. package/dist/map/create-map.d.ts.map +1 -1
  5. package/dist/map/create-map.js +3 -2
  6. package/dist/map/feature-hover.d.ts +7 -0
  7. package/dist/map/feature-hover.d.ts.map +1 -0
  8. package/dist/map/feature-hover.js +71 -0
  9. package/dist/map/get-features.d.ts +12 -0
  10. package/dist/map/get-features.d.ts.map +1 -0
  11. package/dist/map/get-features.js +50 -0
  12. package/dist/map/index.d.ts +0 -1
  13. package/dist/map/index.d.ts.map +1 -1
  14. package/dist/map/index.js +0 -1
  15. package/dist/map/layer-update.d.ts.map +1 -1
  16. package/dist/map/layer-update.js +10 -0
  17. package/dist/map/register-events.d.ts +0 -8
  18. package/dist/map/register-events.d.ts.map +1 -1
  19. package/dist/map/register-events.js +1 -57
  20. package/lib/map/constants.ts +1 -0
  21. package/lib/map/create-map.test.ts +16 -13
  22. package/lib/map/create-map.ts +3 -1
  23. package/lib/map/feature-hover.ts +96 -0
  24. package/lib/map/get-features.test.ts +109 -0
  25. package/lib/map/get-features.ts +89 -0
  26. package/lib/map/handle-errors.test.ts +0 -1
  27. package/lib/map/index.ts +0 -1
  28. package/lib/map/layer-update.test.ts +27 -1
  29. package/lib/map/layer-update.ts +27 -3
  30. package/lib/map/register-events.test.ts +41 -85
  31. package/lib/map/register-events.ts +2 -97
  32. package/package.json +5 -5
  33. package/dist/map/styles.d.ts +0 -16
  34. package/dist/map/styles.d.ts.map +0 -1
  35. package/dist/map/styles.js +0 -77
  36. package/lib/map/styles.test.ts +0 -217
  37. package/lib/map/styles.ts +0 -103
@@ -0,0 +1,2 @@
1
+ export declare const GEOSPATIAL_SDK_PREFIX = "--geospatial-sdk-";
2
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../lib/map/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,sBAAsB,CAAC"}
@@ -0,0 +1 @@
1
+ export const GEOSPATIAL_SDK_PREFIX = "--geospatial-sdk-";
@@ -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,WAAW,CAAC;AAC5B,OAAO,IAAI,MAAM,YAAY,CAAC;AAC9B,OAAO,KAAK,MAAM,mBAAmB,CAAC;AAsCtC,wBAAsB,WAAW,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAyN7E;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,eAAe,EAC3B,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,eAAe,QAgBpC;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAkC3E;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,UAAU,EACnB,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,GAC5B,OAAO,CAAC,GAAG,CAAC,CAKd;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,GAAG,CAAC,CAQd"}
1
+ {"version":3,"file":"create-map.d.ts","sourceRoot":"","sources":["../../lib/map/create-map.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,UAAU,EACV,eAAe,EACf,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,GAAG,MAAM,WAAW,CAAC;AAC5B,OAAO,IAAI,MAAM,YAAY,CAAC;AAC9B,OAAO,KAAK,MAAM,mBAAmB,CAAC;AAsCtC,wBAAsB,WAAW,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAyN7E;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,eAAe,EAC3B,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,eAAe,QAgBpC;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAkC3E;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,UAAU,EACnB,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,GAC5B,OAAO,CAAC,GAAG,CAAC,CAKd;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,GAAG,CAAC,CASd"}
@@ -1,4 +1,4 @@
1
- import { removeSearchParams, } from "@geospatial-sdk/core";
1
+ import { defaultStyle, removeSearchParams, } from "@geospatial-sdk/core";
2
2
  import Map from "ol/Map.js";
3
3
  import View from "ol/View.js";
4
4
  import TileLayer from "ol/layer/Tile.js";
@@ -9,7 +9,6 @@ import VectorSource from "ol/source/Vector.js";
9
9
  import GeoJSON from "ol/format/GeoJSON.js";
10
10
  import { fromLonLat, transformExtent } from "ol/proj.js";
11
11
  import { bbox as bboxStrategy } from "ol/loadingstrategy.js";
12
- import { defaultStyle } from "./styles.js";
13
12
  import VectorTileLayer from "ol/layer/VectorTile.js";
14
13
  import OGCMapTile from "ol/source/OGCMapTile.js";
15
14
  import OGCVectorTile from "ol/source/OGCVectorTile.js";
@@ -20,6 +19,7 @@ import { MapboxVectorLayer } from "ol-mapbox-style";
20
19
  import { handleEndpointError, tileLoadErrorCatchFunction, } from "./handle-errors.js";
21
20
  import VectorTile from "ol/source/VectorTile.js";
22
21
  import { canDoIncrementalUpdate, updateLayerProperties, } from "./layer-update.js";
22
+ import { initHoverLayer } from "./feature-hover.js";
23
23
  const GEOJSON = new GeoJSON();
24
24
  const WFS_MAX_FEATURES = 10000;
25
25
  export async function createLayer(layerModel) {
@@ -291,5 +291,6 @@ export async function resetMapFromContext(map, context) {
291
291
  const layer = await createLayer(layerModel);
292
292
  map.addLayer(layer);
293
293
  }
294
+ initHoverLayer(map);
294
295
  return map;
295
296
  }
@@ -0,0 +1,7 @@
1
+ import type Map from "ol/Map.js";
2
+ import VectorLayer from "ol/layer/Vector.js";
3
+ import VectorSource from "ol/source/Vector.js";
4
+ export declare function initHoverLayer(map: Map): void;
5
+ export declare function getHoverLayer(map: Map): VectorLayer<VectorSource>;
6
+ export declare function clearHoverLayer(map: Map): void;
7
+ //# sourceMappingURL=feature-hover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-hover.d.ts","sourceRoot":"","sources":["../../lib/map/feature-hover.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,WAAW,CAAC;AACjC,OAAO,WAAW,MAAM,oBAAoB,CAAC;AAC7C,OAAO,YAAY,MAAM,qBAAqB,CAAC;AAiB/C,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,QA+DtC;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,CAAC,YAAY,CAAC,CAEjE;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,QAMvC"}
@@ -0,0 +1,71 @@
1
+ import { GEOSPATIAL_SDK_PREFIX } from "./constants.js";
2
+ import VectorLayer from "ol/layer/Vector.js";
3
+ import VectorSource from "ol/source/Vector.js";
4
+ import { defaultHighlightStyle, FeaturesHoverEventType, } from "@geospatial-sdk/core";
5
+ import GeoJSON from "ol/format/GeoJSON.js";
6
+ import { unByKey } from "ol/Observable.js";
7
+ const GEOJSON = new GeoJSON();
8
+ const hoverLayerKey = `${GEOSPATIAL_SDK_PREFIX}hover-layer`;
9
+ const unsubscribeKey = `${GEOSPATIAL_SDK_PREFIX}hover-unsub`;
10
+ export function initHoverLayer(map) {
11
+ if (map.get(hoverLayerKey)) {
12
+ clearHoverLayer(map);
13
+ }
14
+ // create layer & add on top of everything else
15
+ const hoverLayer = new VectorLayer({
16
+ source: new VectorSource({
17
+ features: [],
18
+ useSpatialIndex: false,
19
+ }),
20
+ style: defaultHighlightStyle,
21
+ });
22
+ map.set(hoverLayerKey, hoverLayer);
23
+ hoverLayer.setMap(map);
24
+ // store original cursor style in order to change it later
25
+ const originalCursorStyle = map.getTargetElement()?.style.cursor ?? "";
26
+ const layerFilter = (layer) => layer !== hoverLayer && layer.get(`${GEOSPATIAL_SDK_PREFIX}enable-hover`);
27
+ const unKey = map.on("pointermove", async (event) => {
28
+ // skip hit detection if the view is moving as it can have an impact on performance
29
+ if (map.getView().getInteracting() || map.getView().getAnimating()) {
30
+ return;
31
+ }
32
+ // change cursor if above a feature
33
+ const hasFeature = map.hasFeatureAtPixel(event.pixel, {
34
+ layerFilter,
35
+ });
36
+ if (map.getTargetElement()) {
37
+ map.getTargetElement().style.cursor = hasFeature
38
+ ? "pointer"
39
+ : originalCursorStyle;
40
+ }
41
+ const hoveredSource = hoverLayer.getSource();
42
+ hoveredSource.clear(true);
43
+ if (!hasFeature) {
44
+ return;
45
+ }
46
+ // add hovered feature to the layer
47
+ const hovered = map.getFeaturesAtPixel(event.pixel, {
48
+ layerFilter,
49
+ });
50
+ hoveredSource.addFeature(hovered[0]);
51
+ // dispatch event if subscribed to
52
+ if (map.get(FeaturesHoverEventType)) {
53
+ const { features } = GEOJSON.writeFeaturesObject(hovered);
54
+ map.dispatchEvent({
55
+ type: FeaturesHoverEventType,
56
+ features: features ?? [],
57
+ });
58
+ }
59
+ });
60
+ map.set(unsubscribeKey, unKey);
61
+ }
62
+ export function getHoverLayer(map) {
63
+ return map.get(hoverLayerKey);
64
+ }
65
+ export function clearHoverLayer(map) {
66
+ const hoverLayer = getHoverLayer(map);
67
+ hoverLayer.setMap(null);
68
+ hoverLayer.dispose();
69
+ map.set(hoverLayerKey, null);
70
+ unByKey(map.get(unsubscribeKey));
71
+ }
@@ -0,0 +1,12 @@
1
+ import Map from "ol/Map.js";
2
+ import { Pixel } from "ol/pixel.js";
3
+ import type { Feature } from "geojson";
4
+ import TileWMS from "ol/source/TileWMS.js";
5
+ import ImageWMS from "ol/source/ImageWMS.js";
6
+ import { Coordinate } from "ol/coordinate.js";
7
+ import { MapBrowserEvent } from "ol";
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 readFeaturesAtPixel(map: Map, event: MapBrowserEvent<PointerEvent>): Promise<Feature<import("geojson").Geometry, import("geojson").GeoJsonProperties>[]>;
12
+ //# sourceMappingURL=get-features.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-features.d.ts","sourceRoot":"","sources":["../../lib/map/get-features.ts"],"names":[],"mappings":"AACA,OAAO,GAAG,MAAM,WAAW,CAAC;AAC5B,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAE1D,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,QAAQ,MAAM,uBAAuB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAIrC,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;AAOD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,eAAe,CAAC,YAAY,CAAC,uFAMrC"}
@@ -0,0 +1,50 @@
1
+ import GeoJSON from "ol/format/GeoJSON.js";
2
+ import TileWMS from "ol/source/TileWMS.js";
3
+ import ImageWMS from "ol/source/ImageWMS.js";
4
+ import Layer from "ol/layer/Layer.js";
5
+ import throttle from "lodash.throttle";
6
+ const GEOJSON = new GeoJSON();
7
+ export function getFeaturesFromVectorSources(olMap, pixel) {
8
+ const olFeatures = olMap.getFeaturesAtPixel(pixel);
9
+ const { features } = GEOJSON.writeFeaturesObject(olFeatures);
10
+ if (!features) {
11
+ return [];
12
+ }
13
+ return features;
14
+ }
15
+ export function getGFIUrl(source, map, coordinate) {
16
+ const view = map.getView();
17
+ const projection = view.getProjection();
18
+ const resolution = view.getResolution();
19
+ const params = {
20
+ ...source.getParams(),
21
+ INFO_FORMAT: "application/json",
22
+ };
23
+ return (source.getFeatureInfoUrl(coordinate, resolution, projection, params) ?? null);
24
+ }
25
+ export function getFeaturesFromWmsSources(olMap, coordinate) {
26
+ const wmsSources = olMap
27
+ .getLayers()
28
+ .getArray()
29
+ .filter((layer) => layer instanceof Layer &&
30
+ (layer.getSource() instanceof TileWMS ||
31
+ layer.getSource() instanceof ImageWMS))
32
+ .map((layer) => layer.getSource());
33
+ if (!wmsSources.length) {
34
+ return Promise.resolve([]);
35
+ }
36
+ const gfiUrls = wmsSources.reduce((urls, source) => {
37
+ const gfiUrl = getGFIUrl(source, olMap, coordinate);
38
+ return gfiUrl ? [...urls, gfiUrl] : urls;
39
+ }, []);
40
+ return Promise.all(gfiUrls.map((url) => fetch(url)
41
+ .then((response) => response.json())
42
+ .then((collection) => collection.features))).then((features) => features.flat());
43
+ }
44
+ const getFeaturesFromWmsSourcesThrottled = throttle(getFeaturesFromWmsSources, 250);
45
+ export async function readFeaturesAtPixel(map, event) {
46
+ return [
47
+ ...getFeaturesFromVectorSources(map, event.pixel),
48
+ ...(await getFeaturesFromWmsSourcesThrottled(map, event.coordinate)),
49
+ ];
50
+ }
@@ -2,7 +2,6 @@
2
2
  * @hideGroups
3
3
  * @packageDocumentation
4
4
  */
5
- export * from "./styles.js";
6
5
  export { createMapFromContext, resetMapFromContext } from "./create-map.js";
7
6
  export { applyContextDiffToMap } from "./apply-context-diff.js";
8
7
  export { listen } from "./register-events.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/map/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/map/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/map/index.js CHANGED
@@ -2,7 +2,6 @@
2
2
  * @hideGroups
3
3
  * @packageDocumentation
4
4
  */
5
- export * from "./styles.js";
6
5
  export { createMapFromContext, resetMapFromContext } from "./create-map.js";
7
6
  export { applyContextDiffToMap } from "./apply-context-diff.js";
8
7
  export { listen } from "./register-events.js";
@@ -1 +1 @@
1
- {"version":3,"file":"layer-update.d.ts","sourceRoot":"","sources":["../../lib/map/layer-update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEhE,OAAO,KAAK,MAAM,mBAAmB,CAAC;AAYtC;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAIT;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,eAAe,EAC3B,OAAO,EAAE,KAAK,EACd,kBAAkB,CAAC,EAAE,eAAe,QA6BrC"}
1
+ {"version":3,"file":"layer-update.d.ts","sourceRoot":"","sources":["../../lib/map/layer-update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAKhE,OAAO,KAAK,MAAM,mBAAmB,CAAC;AAmBtC;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAIT;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,eAAe,EAC3B,OAAO,EAAE,KAAK,EACd,kBAAkB,CAAC,EAAE,eAAe,QA2CrC"}
@@ -1,4 +1,5 @@
1
1
  import { getHash } from "@geospatial-sdk/core";
2
+ import { GEOSPATIAL_SDK_PREFIX } from "./constants.js";
2
3
  const UPDATABLE_PROPERTIES = [
3
4
  "opacity",
4
5
  "visibility",
@@ -6,6 +7,8 @@ const UPDATABLE_PROPERTIES = [
6
7
  "attributions",
7
8
  "extras",
8
9
  "version",
10
+ "enableHover",
11
+ "style",
9
12
  // TODO (when available) "zIndex"
10
13
  ];
11
14
  /**
@@ -55,5 +58,12 @@ export function updateLayerProperties(layerModel, olLayer, previousLayerModel) {
55
58
  if (shouldApplyProperty("label")) {
56
59
  olLayer.set("label", layerModel.label);
57
60
  }
61
+ if (shouldApplyProperty("enableHover")) {
62
+ olLayer.set(`${GEOSPATIAL_SDK_PREFIX}enable-hover`, layerModel.enableHover);
63
+ }
64
+ if (shouldApplyProperty("style") &&
65
+ "setStyle" in olLayer) {
66
+ olLayer.setStyle(layerModel.style);
67
+ }
58
68
  // TODO: z-index
59
69
  }
@@ -1,12 +1,4 @@
1
1
  import Map from "ol/Map.js";
2
2
  import { MapEventsByType } from "@geospatial-sdk/core";
3
- import { Coordinate } from "ol/coordinate.js";
4
- import TileWMS from "ol/source/TileWMS.js";
5
- import ImageWMS from "ol/source/ImageWMS.js";
6
- import { Pixel } from "ol/pixel.js";
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
3
  export declare function listen<T extends keyof MapEventsByType>(map: Map, eventType: T, callback: (event: MapEventsByType[T]) => void): void;
12
4
  //# sourceMappingURL=register-events.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"register-events.d.ts","sourceRoot":"","sources":["../../lib/map/register-events.ts"],"names":[],"mappings":"AAAA,OAAO,GAA4B,MAAM,WAAW,CAAC;AACrD,OAAO,EAKL,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,QAAQ,MAAM,uBAAuB,CAAC;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAO1D,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;AA8ED,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,eAAe,EACpD,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,QA0E9C"}
1
+ {"version":3,"file":"register-events.d.ts","sourceRoot":"","sources":["../../lib/map/register-events.ts"],"names":[],"mappings":"AAAA,OAAO,GAA4B,MAAM,WAAW,CAAC;AACrD,OAAO,EAIL,eAAe,EAGhB,MAAM,sBAAsB,CAAC;AA4D9B,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,eAAe,EACpD,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,QA0E9C"}
@@ -1,56 +1,7 @@
1
1
  import { FeaturesClickEventType, FeaturesHoverEventType, MapClickEventType, MapExtentChangeEventType, SourceLoadErrorType, } from "@geospatial-sdk/core";
2
2
  import { toLonLat, transformExtent } from "ol/proj.js";
3
- import GeoJSON from "ol/format/GeoJSON.js";
4
- import TileWMS from "ol/source/TileWMS.js";
5
- import ImageWMS from "ol/source/ImageWMS.js";
6
- import Layer from "ol/layer/Layer.js";
7
- import throttle from "lodash.throttle";
8
3
  import { equals } from "ol/extent.js";
9
- const GEOJSON = new GeoJSON();
10
- export function getFeaturesFromVectorSources(olMap, pixel) {
11
- const olFeatures = olMap.getFeaturesAtPixel(pixel);
12
- const { features } = GEOJSON.writeFeaturesObject(olFeatures);
13
- if (!features) {
14
- return [];
15
- }
16
- return features;
17
- }
18
- export function getGFIUrl(source, map, coordinate) {
19
- const view = map.getView();
20
- const projection = view.getProjection();
21
- const resolution = view.getResolution();
22
- const params = {
23
- ...source.getParams(),
24
- INFO_FORMAT: "application/json",
25
- };
26
- return (source.getFeatureInfoUrl(coordinate, resolution, projection, params) ?? null);
27
- }
28
- export function getFeaturesFromWmsSources(olMap, coordinate) {
29
- const wmsSources = olMap
30
- .getLayers()
31
- .getArray()
32
- .filter((layer) => layer instanceof Layer &&
33
- (layer.getSource() instanceof TileWMS ||
34
- layer.getSource() instanceof ImageWMS))
35
- .map((layer) => layer.getSource());
36
- if (!wmsSources.length) {
37
- return Promise.resolve([]);
38
- }
39
- const gfiUrls = wmsSources.reduce((urls, source) => {
40
- const gfiUrl = getGFIUrl(source, olMap, coordinate);
41
- return gfiUrl ? [...urls, gfiUrl] : urls;
42
- }, []);
43
- return Promise.all(gfiUrls.map((url) => fetch(url)
44
- .then((response) => response.json())
45
- .then((collection) => collection.features))).then((features) => features.flat());
46
- }
47
- const getFeaturesFromWmsSourcesThrottled = throttle(getFeaturesFromWmsSources, 250);
48
- async function readFeaturesAtPixel(map, event) {
49
- return [
50
- ...getFeaturesFromVectorSources(map, event.pixel),
51
- ...(await getFeaturesFromWmsSourcesThrottled(map, event.coordinate)),
52
- ];
53
- }
4
+ import { readFeaturesAtPixel } from "./get-features.js";
54
5
  function registerFeatureClickEvent(map) {
55
6
  if (map.get(FeaturesClickEventType))
56
7
  return;
@@ -66,13 +17,6 @@ function registerFeatureClickEvent(map) {
66
17
  function registerFeatureHoverEvent(map) {
67
18
  if (map.get(FeaturesHoverEventType))
68
19
  return;
69
- map.on("pointermove", async (event) => {
70
- const features = await readFeaturesAtPixel(map, event);
71
- map.dispatchEvent({
72
- type: FeaturesHoverEventType,
73
- features,
74
- });
75
- });
76
20
  map.set(FeaturesHoverEventType, true);
77
21
  }
78
22
  function registerMapExtentChangeEvent(map) {
@@ -0,0 +1 @@
1
+ export const GEOSPATIAL_SDK_PREFIX = "--geospatial-sdk-";
@@ -44,6 +44,7 @@ import ImageTile from "ol/ImageTile.js";
44
44
  import TileState from "ol/TileState.js";
45
45
  import VectorTileLayer from "ol/layer/VectorTile.js";
46
46
  import { FeatureUrlFunction } from "ol/featureloader.js";
47
+ import { beforeEach } from "vitest";
47
48
 
48
49
  vi.mock("./handle-errors", async (importOriginal) => {
49
50
  const actual =
@@ -480,9 +481,10 @@ describe("MapContextService", () => {
480
481
  });
481
482
  });
482
483
  describe("#resetMapFromContext", () => {
483
- const map = new Map({});
484
- const mapContext = MAP_CTX_FIXTURE;
484
+ let map: Map;
485
485
  beforeEach(() => {
486
+ map = new Map({});
487
+ const mapContext = MAP_CTX_FIXTURE;
486
488
  resetMapFromContext(map, mapContext);
487
489
  });
488
490
  it("create a map", () => {
@@ -500,17 +502,18 @@ describe("MapContextService", () => {
500
502
  });
501
503
  describe("uses default fallback view", () => {
502
504
  let view: View;
503
- const map = new Map({});
504
- const mapContext: MapContext = {
505
- view: null,
506
- layers: [
507
- MAP_CTX_LAYER_XYZ_FIXTURE,
508
- MAP_CTX_LAYER_WMS_FIXTURE,
509
- MAP_CTX_LAYER_GEOJSON_FIXTURE,
510
- ],
511
- };
512
- beforeEach(() => {
513
- resetMapFromContext(map, mapContext);
505
+ let map: Map;
506
+ beforeEach(async () => {
507
+ map = new Map({});
508
+ const mapContext: MapContext = {
509
+ view: null,
510
+ layers: [
511
+ MAP_CTX_LAYER_XYZ_FIXTURE,
512
+ MAP_CTX_LAYER_WMS_FIXTURE,
513
+ MAP_CTX_LAYER_GEOJSON_FIXTURE,
514
+ ],
515
+ };
516
+ await resetMapFromContext(map, mapContext);
514
517
  });
515
518
  it("create a map", () => {
516
519
  expect(map).toBeTruthy();
@@ -1,4 +1,5 @@
1
1
  import {
2
+ defaultStyle,
2
3
  MapContext,
3
4
  MapContextLayer,
4
5
  MapContextView,
@@ -18,7 +19,6 @@ import Geometry from "ol/geom/Geometry.js";
18
19
  import SimpleGeometry from "ol/geom/SimpleGeometry.js";
19
20
  import { fromLonLat, transformExtent } from "ol/proj.js";
20
21
  import { bbox as bboxStrategy } from "ol/loadingstrategy.js";
21
- import { defaultStyle } from "./styles.js";
22
22
  import VectorTileLayer from "ol/layer/VectorTile.js";
23
23
  import OGCMapTile from "ol/source/OGCMapTile.js";
24
24
  import OGCVectorTile from "ol/source/OGCVectorTile.js";
@@ -40,6 +40,7 @@ import {
40
40
  canDoIncrementalUpdate,
41
41
  updateLayerProperties,
42
42
  } from "./layer-update.js";
43
+ import { initHoverLayer } from "./feature-hover.js";
43
44
 
44
45
  const GEOJSON = new GeoJSON();
45
46
  const WFS_MAX_FEATURES = 10000;
@@ -351,5 +352,6 @@ export async function resetMapFromContext(
351
352
  const layer = await createLayer(layerModel);
352
353
  map.addLayer(layer);
353
354
  }
355
+ initHoverLayer(map);
354
356
  return map;
355
357
  }
@@ -0,0 +1,96 @@
1
+ import { GEOSPATIAL_SDK_PREFIX } from "./constants.js";
2
+ import type Map from "ol/Map.js";
3
+ import VectorLayer from "ol/layer/Vector.js";
4
+ import VectorSource from "ol/source/Vector.js";
5
+ import {
6
+ defaultHighlightStyle,
7
+ FeaturesHoverEventType,
8
+ } from "@geospatial-sdk/core";
9
+ import BaseEvent from "ol/events/Event.js";
10
+ import { MapBrowserEvent } from "ol";
11
+ import GeoJSON from "ol/format/GeoJSON.js";
12
+ import Feature from "ol/Feature.js";
13
+ import BaseLayer from "ol/layer/Base.js";
14
+ import { unByKey } from "ol/Observable.js";
15
+
16
+ const GEOJSON = new GeoJSON();
17
+
18
+ const hoverLayerKey = `${GEOSPATIAL_SDK_PREFIX}hover-layer`;
19
+ const unsubscribeKey = `${GEOSPATIAL_SDK_PREFIX}hover-unsub`;
20
+
21
+ export function initHoverLayer(map: Map) {
22
+ if (map.get(hoverLayerKey)) {
23
+ clearHoverLayer(map);
24
+ }
25
+
26
+ // create layer & add on top of everything else
27
+ const hoverLayer = new VectorLayer({
28
+ source: new VectorSource({
29
+ features: [],
30
+ useSpatialIndex: false,
31
+ }),
32
+ style: defaultHighlightStyle,
33
+ });
34
+ map.set(hoverLayerKey, hoverLayer);
35
+ hoverLayer.setMap(map);
36
+
37
+ // store original cursor style in order to change it later
38
+ const originalCursorStyle = map.getTargetElement()?.style.cursor ?? "";
39
+
40
+ const layerFilter = (layer: BaseLayer) =>
41
+ layer !== hoverLayer && layer.get(`${GEOSPATIAL_SDK_PREFIX}enable-hover`);
42
+
43
+ const unKey = map.on(
44
+ "pointermove",
45
+ async (event: MapBrowserEvent<PointerEvent>) => {
46
+ // skip hit detection if the view is moving as it can have an impact on performance
47
+ if (map.getView().getInteracting() || map.getView().getAnimating()) {
48
+ return;
49
+ }
50
+
51
+ // change cursor if above a feature
52
+ const hasFeature = map.hasFeatureAtPixel(event.pixel, {
53
+ layerFilter,
54
+ });
55
+ if (map.getTargetElement()) {
56
+ map.getTargetElement().style.cursor = hasFeature
57
+ ? "pointer"
58
+ : originalCursorStyle;
59
+ }
60
+
61
+ const hoveredSource = hoverLayer.getSource() as VectorSource;
62
+ hoveredSource.clear(true);
63
+ if (!hasFeature) {
64
+ return;
65
+ }
66
+
67
+ // add hovered feature to the layer
68
+ const hovered = map.getFeaturesAtPixel(event.pixel, {
69
+ layerFilter,
70
+ }) as Feature[];
71
+ hoveredSource.addFeature(hovered[0]);
72
+
73
+ // dispatch event if subscribed to
74
+ if (map.get(FeaturesHoverEventType)) {
75
+ const { features } = GEOJSON.writeFeaturesObject(hovered);
76
+ map.dispatchEvent({
77
+ type: FeaturesHoverEventType,
78
+ features: features ?? [],
79
+ } as unknown as BaseEvent);
80
+ }
81
+ },
82
+ );
83
+ map.set(unsubscribeKey, unKey);
84
+ }
85
+
86
+ export function getHoverLayer(map: Map): VectorLayer<VectorSource> {
87
+ return map.get(hoverLayerKey) as VectorLayer<VectorSource>;
88
+ }
89
+
90
+ export function clearHoverLayer(map: Map) {
91
+ const hoverLayer = getHoverLayer(map);
92
+ hoverLayer.setMap(null);
93
+ hoverLayer.dispose();
94
+ map.set(hoverLayerKey, null);
95
+ unByKey(map.get(unsubscribeKey));
96
+ }
@@ -0,0 +1,109 @@
1
+ import {
2
+ getFeaturesFromVectorSources,
3
+ getFeaturesFromWmsSources,
4
+ getGFIUrl,
5
+ } from "./get-features.js";
6
+ import Map from "ol/Map.js";
7
+ import TileWMS from "ol/source/TileWMS.js";
8
+ import TileLayer from "ol/layer/Tile.js";
9
+ import OlFeature from "ol/Feature.js";
10
+ import Point from "ol/geom/Point.js";
11
+ import VectorLayer from "ol/layer/Vector.js";
12
+ import VectorSource from "ol/source/Vector.js";
13
+ import View from "ol/View.js";
14
+ import { Collection, Object as BaseObject } from "ol";
15
+
16
+ const gfiResult = {
17
+ type: "Feature",
18
+ properties: {
19
+ density: 123,
20
+ },
21
+ geometry: null,
22
+ };
23
+ function createWmsSource() {
24
+ return new TileWMS({
25
+ url: "http://my.service.org/wms?SERVICE=WMS",
26
+ params: {
27
+ LAYERS: "layerName",
28
+ FORMAT: "image/png",
29
+ TRANSPARENT: true,
30
+ },
31
+ });
32
+ }
33
+ function createMap(): Map {
34
+ const wmsLayer = new TileLayer({
35
+ source: createWmsSource(),
36
+ });
37
+ const feature = new OlFeature({
38
+ geometry: new Point([100, 200]),
39
+ });
40
+ const vectorLayer = new VectorLayer({
41
+ source: new VectorSource({
42
+ features: [feature],
43
+ }),
44
+ });
45
+ const view = new View({
46
+ projection: "EPSG:3857",
47
+ resolution: 1,
48
+ center: [0, 0],
49
+ });
50
+ const map = new BaseObject() as Map;
51
+ Object.defineProperties(map, {
52
+ getView: { value: vi.fn(() => view) },
53
+ getLayers: { value: vi.fn(() => new Collection([wmsLayer, vectorLayer])) },
54
+ getEventPixel: { value: vi.fn(() => [10, 10]) },
55
+ getCoordinateFromPixel: { value: vi.fn(() => [123, 123]) },
56
+ getFeaturesAtPixel: { value: vi.fn(() => [feature]) },
57
+ getSize: { value: vi.fn(() => [800, 600]) },
58
+ });
59
+ return map;
60
+ }
61
+
62
+ describe("get features utils", () => {
63
+ let map: Map;
64
+ beforeEach(() => {
65
+ map = createMap();
66
+ vi.spyOn(global, "fetch").mockImplementation(() =>
67
+ Promise.resolve({
68
+ ok: true,
69
+ json: () => Promise.resolve({ features: [gfiResult] }),
70
+ } as Response),
71
+ );
72
+ vi.useFakeTimers();
73
+ });
74
+
75
+ describe("getFeaturesFromVectorSources", () => {
76
+ it("returns an array of features", () => {
77
+ const features = getFeaturesFromVectorSources(map, [0, 0]);
78
+ expect(features).toEqual([
79
+ {
80
+ geometry: {
81
+ coordinates: [100, 200],
82
+ type: "Point",
83
+ },
84
+ properties: null,
85
+ type: "Feature",
86
+ },
87
+ ]);
88
+ });
89
+ });
90
+ describe("getGFIUrl", () => {
91
+ let url: string;
92
+ const coordinate = [-182932.49329334166, 6125319.813853541];
93
+ beforeEach(() => {
94
+ const wmsSource = createWmsSource();
95
+ url = getGFIUrl(wmsSource, map, coordinate) as string;
96
+ });
97
+ it("returns true", () => {
98
+ expect(url).toEqual(
99
+ "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",
100
+ );
101
+ });
102
+ });
103
+ describe("getFeaturesFromWmsSources", () => {
104
+ it("queries the WMS sources", async () => {
105
+ const features = await getFeaturesFromWmsSources(map, [0, 0]);
106
+ expect(features).toEqual([gfiResult]);
107
+ });
108
+ });
109
+ });