@geospatial-sdk/openlayers 0.0.5-dev.55 → 0.0.5-dev.57

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/apply-context-diff.d.ts.map +1 -1
  2. package/dist/map/apply-context-diff.js +9 -1
  3. package/dist/map/create-map.d.ts +1 -1
  4. package/dist/map/create-map.d.ts.map +1 -1
  5. package/dist/map/create-map.js +85 -44
  6. package/dist/map/feature-hover.js +1 -1
  7. package/dist/map/handle-errors.d.ts +0 -7
  8. package/dist/map/handle-errors.d.ts.map +1 -1
  9. package/dist/map/handle-errors.js +10 -14
  10. package/dist/map/index.d.ts +2 -1
  11. package/dist/map/index.d.ts.map +1 -1
  12. package/dist/map/index.js +2 -1
  13. package/dist/map/layer-update.d.ts.map +1 -1
  14. package/dist/map/layer-update.js +1 -1
  15. package/dist/map/listen.d.ts +4 -0
  16. package/dist/map/listen.d.ts.map +1 -0
  17. package/dist/map/listen.js +70 -0
  18. package/dist/map/register-events.d.ts +16 -2
  19. package/dist/map/register-events.d.ts.map +1 -1
  20. package/dist/map/register-events.js +172 -81
  21. package/dist/map/resolved-map-state.d.ts +8 -0
  22. package/dist/map/resolved-map-state.d.ts.map +1 -0
  23. package/dist/map/resolved-map-state.js +26 -0
  24. package/lib/map/apply-context-diff.ts +16 -5
  25. package/lib/map/create-map.test.ts +178 -40
  26. package/lib/map/create-map.ts +114 -55
  27. package/lib/map/feature-hover.ts +1 -1
  28. package/lib/map/handle-errors.test.ts +13 -36
  29. package/lib/map/handle-errors.ts +10 -28
  30. package/lib/map/index.ts +2 -1
  31. package/lib/map/layer-update.ts +3 -2
  32. package/lib/map/listen.test.ts +977 -0
  33. package/lib/map/listen.ts +123 -0
  34. package/lib/map/register-events.ts +229 -109
  35. package/lib/map/resolved-map-state.ts +38 -0
  36. package/package.json +3 -3
  37. package/lib/map/register-events.test.ts +0 -259
@@ -1,9 +1,9 @@
1
- import { FeaturesClickEventType, FeaturesHoverEventType, MapClickEventType, MapExtentChangeEventType, SourceLoadErrorType, } from "@geospatial-sdk/core";
1
+ import { FeaturesClickEventType, FeaturesHoverEventType, LayerCreationErrorEventType, LayerLoadingErrorEventType, MapLayerStateChangeEventType, MapStateChangeEventType, MapViewStateChangeEventType, SourceLoadErrorEvent, SourceLoadErrorType, } from "@geospatial-sdk/core";
2
+ import { readFeaturesAtPixel } from "./get-features.js";
2
3
  import { equals } from "ol/extent.js";
3
- import { toLonLat, transformExtent } from "ol/proj.js";
4
+ import { readMapViewState } from "./resolved-map-state.js";
4
5
  import { GEOSPATIAL_SDK_PREFIX } from "./constants.js";
5
- import { readFeaturesAtPixel } from "./get-features.js";
6
- function registerFeatureClickEvent(map) {
6
+ export function registerFeatureClickEvent(map) {
7
7
  if (map.get(FeaturesClickEventType))
8
8
  return;
9
9
  // Filter to only query clickable layers
@@ -12,99 +12,190 @@ function registerFeatureClickEvent(map) {
12
12
  const featuresByLayer = await readFeaturesAtPixel(map, event, layerFilter);
13
13
  const features = Array.from(featuresByLayer.values()).flat();
14
14
  map.dispatchEvent({
15
- type: FeaturesClickEventType,
15
+ type: `${GEOSPATIAL_SDK_PREFIX}${FeaturesClickEventType}`,
16
16
  features,
17
17
  featuresByLayer,
18
18
  });
19
19
  });
20
20
  map.set(FeaturesClickEventType, true);
21
21
  }
22
- function registerFeatureHoverEvent(map) {
22
+ export function registerFeatureHoverEvent(map) {
23
23
  if (map.get(FeaturesHoverEventType))
24
24
  return;
25
25
  map.set(FeaturesHoverEventType, true);
26
26
  }
27
- function registerMapExtentChangeEvent(map) {
28
- if (map.get(MapExtentChangeEventType))
27
+ export function registerMapLayerStateChangeEvent(map) {
28
+ if (map.get(MapLayerStateChangeEventType))
29
29
  return;
30
- let lastExtent = null;
31
- const handleExtentChange = () => {
32
- const extent = map.getView().calculateExtent(map.getSize());
33
- const reprojectedExtent = transformExtent(extent, map.getView().getProjection(), "EPSG:4326");
34
- if (lastExtent && equals(lastExtent, reprojectedExtent)) {
30
+ map.set(MapLayerStateChangeEventType, true);
31
+ }
32
+ export function emitLayerCreationError(layer, error) {
33
+ layer.dispatchEvent({
34
+ type: `${GEOSPATIAL_SDK_PREFIX}${LayerCreationErrorEventType}`,
35
+ error,
36
+ });
37
+ }
38
+ export function emitLayerLoadingStatusLoading(layer) {
39
+ layer.dispatchEvent({
40
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
41
+ layerState: { loading: true },
42
+ });
43
+ }
44
+ export function emitLayerLoadingStatusSuccess(layer) {
45
+ layer.dispatchEvent({
46
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-loading-status`,
47
+ layerState: { loaded: true },
48
+ });
49
+ }
50
+ export function emitLayerLoadingError(layer, error, httpStatus) {
51
+ layer.dispatchEvent({
52
+ type: `${GEOSPATIAL_SDK_PREFIX}${LayerLoadingErrorEventType}`,
53
+ error,
54
+ ...(httpStatus !== undefined ? { httpStatus } : {}),
55
+ });
56
+ }
57
+ export function emitLayerDataInfo(layer, dataInfo) {
58
+ layer.dispatchEvent({
59
+ type: `${GEOSPATIAL_SDK_PREFIX}layer-data-info`,
60
+ layerState: dataInfo,
61
+ });
62
+ }
63
+ export function propagateLayerStateChangeEventToMap(map, layer) {
64
+ let currentLayerState = {
65
+ created: true,
66
+ };
67
+ let currentLoadingStatus = {};
68
+ function updateStateAndEmit() {
69
+ if (!map.get(MapLayerStateChangeEventType)) {
35
70
  return;
36
71
  }
37
- lastExtent = reprojectedExtent;
72
+ const layerIndex = map.getLayers().getArray().indexOf(layer);
38
73
  map.dispatchEvent({
39
- type: MapExtentChangeEventType,
40
- extent: reprojectedExtent,
74
+ type: `${GEOSPATIAL_SDK_PREFIX}${MapLayerStateChangeEventType}`,
75
+ layerState: {
76
+ ...currentLayerState,
77
+ ...currentLoadingStatus,
78
+ },
79
+ layerIndex,
41
80
  });
42
- };
43
- map.getView().on("change:center", handleExtentChange);
44
- map.getView().on("change:resolution", handleExtentChange);
45
- map.getView().on("change:rotation", handleExtentChange);
46
- map.on("change:size", handleExtentChange);
47
- map.set(MapExtentChangeEventType, true);
81
+ }
82
+ // on layer creation error update layer state and redispatch on map
83
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}${LayerCreationErrorEventType}`, (event) => {
84
+ currentLayerState = {
85
+ creationError: true,
86
+ creationErrorMessage: event.error.message,
87
+ };
88
+ updateStateAndEmit();
89
+ if (map.get(LayerCreationErrorEventType)) {
90
+ map.dispatchEvent(event);
91
+ }
92
+ });
93
+ // on layer loading error update layer state and redispatch on map
94
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}${LayerLoadingErrorEventType}`, (event) => {
95
+ currentLoadingStatus = {
96
+ loadingError: true,
97
+ loadingErrorMessage: event.error.message,
98
+ ...(event.httpStatus !== undefined && {
99
+ loadingErrorHttpStatus: event.httpStatus,
100
+ }),
101
+ };
102
+ updateStateAndEmit();
103
+ if (map.get(LayerLoadingErrorEventType)) {
104
+ map.dispatchEvent(event);
105
+ }
106
+ // deprecated event
107
+ if (map.get(SourceLoadErrorType)) {
108
+ const sourceLoadEvent = new SourceLoadErrorEvent(event.error);
109
+ if (event.httpStatus) {
110
+ sourceLoadEvent.httpStatus = event.httpStatus;
111
+ }
112
+ map.dispatchEvent(sourceLoadEvent);
113
+ }
114
+ });
115
+ // When new information about a layer state is available, add it to the previous state & emit
116
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-data-info`, (event) => {
117
+ currentLayerState = {
118
+ ...currentLayerState,
119
+ ...event.layerState,
120
+ };
121
+ updateStateAndEmit();
122
+ });
123
+ // loading state can change over time
124
+ layer.on(`${GEOSPATIAL_SDK_PREFIX}layer-loading-status`, (event) => {
125
+ currentLoadingStatus = event.layerState;
126
+ updateStateAndEmit();
127
+ });
48
128
  }
49
- export function listen(map, eventType, callback) {
50
- switch (eventType) {
51
- case FeaturesClickEventType:
52
- registerFeatureClickEvent(map);
53
- // we're using a custom event type here so we need to cast to unknown first
54
- map.on(eventType, (event) => {
55
- callback(event);
56
- });
57
- break;
58
- case FeaturesHoverEventType:
59
- registerFeatureHoverEvent(map);
60
- // see comment above
61
- map.on(eventType, (event) => {
62
- callback(event);
63
- });
64
- break;
65
- case MapClickEventType:
66
- map.on("click", (event) => {
67
- const coordinate = toLonLat(event.coordinate, map.getView().getProjection());
68
- callback({
69
- type: "map-click",
70
- coordinate,
71
- });
72
- });
73
- break;
74
- case MapExtentChangeEventType:
75
- registerMapExtentChangeEvent(map);
76
- // see comment above
77
- map.on(eventType, (event) => {
78
- callback(event);
79
- });
80
- break;
81
- case SourceLoadErrorType: {
82
- const errorCallback = (event) => {
83
- callback(event);
84
- };
85
- //attach event listener to all existing layers
86
- map.getLayers().forEach((layer) => {
87
- if (layer) {
88
- layer.on(SourceLoadErrorType, errorCallback);
89
- }
90
- });
91
- //attach event listener when layer is added
92
- map.getLayers().on("add", (event) => {
93
- const layer = event.element;
94
- if (layer) {
95
- layer.on(SourceLoadErrorType, errorCallback);
96
- }
97
- });
98
- //remove event listener when layer is removed
99
- map.getLayers().on("remove", (event) => {
100
- const layer = event.element;
101
- if (layer) {
102
- layer.un(SourceLoadErrorType, errorCallback);
103
- }
104
- });
105
- break;
129
+ export function registerMapStateChangeEvent(map) {
130
+ if (map.get(MapStateChangeEventType))
131
+ return;
132
+ // the global map state requires both view and layers state
133
+ registerMapLayerStateChangeEvent(map);
134
+ registerMapViewStateChangeEvent(map);
135
+ let currentState = {
136
+ layers: [],
137
+ view: null,
138
+ };
139
+ function emitState() {
140
+ // we're making sure to have the right amount of layers in the state and to fill empty slots with null
141
+ currentState.layers.length = map.getLayers().getLength();
142
+ for (let i = 0; i < currentState.layers.length; i++) {
143
+ if (!currentState.layers[i]) {
144
+ currentState.layers[i] = null;
145
+ }
106
146
  }
107
- default:
108
- throw new Error(`Unrecognized event type: ${eventType}`);
147
+ map.dispatchEvent({
148
+ type: `${GEOSPATIAL_SDK_PREFIX}${MapStateChangeEventType}`,
149
+ mapState: currentState,
150
+ });
109
151
  }
152
+ // collect view and layer states to re-emit them as a global state
153
+ map.on(`${GEOSPATIAL_SDK_PREFIX}${MapLayerStateChangeEventType}`, (event) => {
154
+ const layers = [...currentState.layers];
155
+ layers[event.layerIndex] = event.layerState;
156
+ currentState = { ...currentState, layers };
157
+ emitState();
158
+ });
159
+ map.on(`${GEOSPATIAL_SDK_PREFIX}${MapViewStateChangeEventType}`, (event) => {
160
+ currentState = { ...currentState, view: event.viewState };
161
+ emitState();
162
+ });
163
+ map.set(MapStateChangeEventType, true);
164
+ }
165
+ export function registerLayerCreationErrorEvent(map) {
166
+ if (map.get(LayerCreationErrorEventType))
167
+ return;
168
+ map.set(LayerCreationErrorEventType, true);
169
+ }
170
+ export function registerLayerLoadingErrorEvent(map) {
171
+ if (map.get(LayerLoadingErrorEventType))
172
+ return;
173
+ map.set(LayerLoadingErrorEventType, true);
174
+ }
175
+ // DEPRECATED EVENTS
176
+ export function registerMapViewStateChangeEvent(map) {
177
+ if (map.get(MapViewStateChangeEventType))
178
+ return;
179
+ let lastExtent = null;
180
+ const handleViewChange = () => {
181
+ const viewState = readMapViewState(map);
182
+ if (lastExtent && equals(lastExtent, viewState.extent)) {
183
+ return;
184
+ }
185
+ lastExtent = viewState.extent;
186
+ map.dispatchEvent({
187
+ type: `${GEOSPATIAL_SDK_PREFIX}${MapViewStateChangeEventType}`,
188
+ viewState,
189
+ });
190
+ };
191
+ map.getView().on("change:center", handleViewChange);
192
+ map.getView().on("change:resolution", handleViewChange);
193
+ map.getView().on("change:rotation", handleViewChange);
194
+ map.on("change:size", handleViewChange);
195
+ map.set(MapViewStateChangeEventType, true);
196
+ }
197
+ export function registerSourceLoadErrorEvent(map) {
198
+ if (map.get(SourceLoadErrorType))
199
+ return;
200
+ map.set(SourceLoadErrorType, true);
110
201
  }
@@ -0,0 +1,8 @@
1
+ import Map from "ol/Map.js";
2
+ import { ResolvedMapViewState } from "@geospatial-sdk/core";
3
+ /**
4
+ * Reads the current view state of the map.
5
+ * @param map
6
+ */
7
+ export declare function readMapViewState(map: Map): ResolvedMapViewState;
8
+ //# sourceMappingURL=resolved-map-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolved-map-state.d.ts","sourceRoot":"","sources":["../../lib/map/resolved-map-state.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,WAAW,CAAC;AAE5B,OAAO,EAAsB,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAOhF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,oBAAoB,CAwB/D"}
@@ -0,0 +1,26 @@
1
+ import { transform as transformCoordinate, transformExtent } from "ol/proj.js";
2
+ /**
3
+ * This magic value is generally used across OGC services as a reasonable approximation for most displays
4
+ */
5
+ const PIXEL_SIZE_MM = 0.28;
6
+ /**
7
+ * Reads the current view state of the map.
8
+ * @param map
9
+ */
10
+ export function readMapViewState(map) {
11
+ const view = map.getView();
12
+ const projection = view.getProjection();
13
+ const extent = transformExtent(view.calculateExtent(map.getSize()), projection, "EPSG:4326");
14
+ const center = transformCoordinate(view.getCenter() ?? [0, 0], projection, "EPSG:4326");
15
+ const resolution = view.getResolution() ?? 1;
16
+ const metersPerUnit = projection.getMetersPerUnit() ?? 1;
17
+ const scaleDenominator = metersPerUnit * resolution * (1000 / PIXEL_SIZE_MM);
18
+ const bearing = view.getRotation() * (180 / Math.PI) + 90; // by default, bearing is North
19
+ return {
20
+ center,
21
+ extent,
22
+ resolution,
23
+ scaleDenominator,
24
+ bearing,
25
+ };
26
+ }
@@ -4,6 +4,7 @@ import { createLayer, createView, updateLayerInMap } from "./create-map.js";
4
4
  import { fromLonLat, transformExtent } from "ol/proj.js";
5
5
  import GeoJSON from "ol/format/GeoJSON.js";
6
6
  import SimpleGeometry from "ol/geom/SimpleGeometry.js";
7
+ import { propagateLayerStateChangeEventToMap } from "./register-events.js";
7
8
 
8
9
  const GEOJSON = new GeoJSON();
9
10
 
@@ -35,12 +36,16 @@ export async function applyContextDiffToMap(
35
36
  );
36
37
 
37
38
  newLayers.forEach((layer, index) => {
39
+ if (!layer) {
40
+ return;
41
+ }
38
42
  const position = contextDiff.layersAdded[index].position;
39
43
  if (position >= layers.getLength()) {
40
44
  layers.push(layer);
41
45
  } else {
42
46
  layers.insertAt(position, layer);
43
47
  }
48
+ propagateLayerStateChangeEventToMap(map, layer);
44
49
  });
45
50
 
46
51
  // move reordered layers (sorted by ascending new position)
@@ -59,12 +64,15 @@ export async function applyContextDiffToMap(
59
64
  }
60
65
 
61
66
  // update or recreate changed layers
67
+ const updatePromises = [];
62
68
  for (const layerChanged of contextDiff.layersChanged) {
63
- updateLayerInMap(
64
- map,
65
- layerChanged.layer,
66
- layerChanged.position,
67
- layerChanged.previousLayer,
69
+ updatePromises.push(
70
+ updateLayerInMap(
71
+ map,
72
+ layerChanged.layer,
73
+ layerChanged.position,
74
+ layerChanged.previousLayer,
75
+ ),
68
76
  );
69
77
  }
70
78
 
@@ -107,5 +115,8 @@ export async function applyContextDiffToMap(
107
115
  }
108
116
  }
109
117
 
118
+ // wait for all layers to have been updated
119
+ await Promise.all(updatePromises);
120
+
110
121
  return map;
111
122
  }