mobility-toolbox-js 2.0.0-beta.33 → 2.0.0-beta.36

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 (116) hide show
  1. package/api/RealtimeAPI.d.ts +6 -6
  2. package/api/RealtimeAPI.d.ts.map +1 -1
  3. package/api/RealtimeAPI.js +613 -0
  4. package/api/RoutingAPI.d.ts.map +1 -1
  5. package/api/RoutingAPI.js +35 -0
  6. package/api/StopsAPI.d.ts +1 -1
  7. package/api/StopsAPI.d.ts.map +1 -1
  8. package/api/StopsAPI.js +38 -0
  9. package/api/index.d.ts +3 -4
  10. package/api/index.d.ts.map +1 -1
  11. package/api/index.js +3 -0
  12. package/api/typedefs.js +73 -0
  13. package/common/api/HttpAPI.d.ts.map +1 -1
  14. package/common/api/HttpAPI.js +57 -0
  15. package/common/api/WebSocketAPI.d.ts +2 -2
  16. package/common/api/WebSocketAPI.d.ts.map +1 -1
  17. package/common/api/WebSocketAPI.js +290 -0
  18. package/common/controls/Control.d.ts +5 -5
  19. package/common/controls/Control.d.ts.map +1 -1
  20. package/common/controls/Control.js +137 -0
  21. package/common/index.js +2 -0
  22. package/common/layers/Layer.d.ts +11 -11
  23. package/common/layers/Layer.d.ts.map +1 -1
  24. package/common/layers/Layer.js +223 -0
  25. package/common/mixins/CopyrightMixin.js +43 -0
  26. package/common/mixins/MapboxLayerMixin.js +198 -0
  27. package/common/mixins/RealtimeLayerMixin.js +650 -0
  28. package/common/mixins/StopFinderMixin.d.ts +3 -3
  29. package/common/mixins/StopFinderMixin.d.ts.map +1 -1
  30. package/common/mixins/StopFinderMixin.js +156 -0
  31. package/common/mixins/UserInteractionsLayerMixin.js +192 -0
  32. package/common/styles/index.js +4 -0
  33. package/common/styles/realtimeDefaultStyle.js +239 -0
  34. package/common/styles/realtimeDelayStyle.js +13 -0
  35. package/common/styles/realtimeSimpleStyle.js +22 -0
  36. package/common/typedefs.js +22 -0
  37. package/common/utils/cleanStopTime.js +28 -0
  38. package/common/utils/compareDepartures.d.ts +1 -1
  39. package/common/utils/compareDepartures.d.ts.map +1 -1
  40. package/common/utils/compareDepartures.js +34 -0
  41. package/common/utils/createCanvas.js +27 -0
  42. package/common/utils/createTrackerFilters.d.ts +1 -1
  43. package/common/utils/createTrackerFilters.d.ts.map +1 -1
  44. package/common/utils/createTrackerFilters.js +67 -0
  45. package/common/utils/getLayersAsFlatArray.js +14 -0
  46. package/common/utils/getMapboxMapCopyrights.js +24 -0
  47. package/common/utils/getMapboxRender.js +74 -0
  48. package/common/utils/getMaplibreRender.js +35 -0
  49. package/common/utils/getRealtimeModeSuffix.js +7 -0
  50. package/common/utils/getUrlWithParams.js +18 -0
  51. package/common/utils/getVehiclePosition.js +63 -0
  52. package/common/utils/index.js +12 -0
  53. package/common/utils/removeDuplicate.d.ts +1 -1
  54. package/common/utils/removeDuplicate.d.ts.map +1 -1
  55. package/common/utils/removeDuplicate.js +15 -0
  56. package/common/utils/renderTrajectories.js +107 -0
  57. package/common/utils/sortByDelay.js +20 -0
  58. package/common/utils/timeUtils.js +39 -0
  59. package/common/utils/trackerConfig.js +170 -0
  60. package/iife.js +5 -0
  61. package/index.d.ts +4 -0
  62. package/index.js +10 -0
  63. package/mapbox/controls/CopyrightControl.d.ts +0 -1
  64. package/mapbox/controls/CopyrightControl.d.ts.map +1 -1
  65. package/mapbox/controls/CopyrightControl.js +53 -0
  66. package/mapbox/controls/index.js +2 -0
  67. package/mapbox/index.js +4 -0
  68. package/mapbox/layers/Layer.d.ts +1 -1
  69. package/mapbox/layers/Layer.d.ts.map +1 -1
  70. package/mapbox/layers/Layer.js +97 -0
  71. package/mapbox/layers/RealtimeLayer.d.ts +4 -4
  72. package/mapbox/layers/RealtimeLayer.d.ts.map +1 -1
  73. package/mapbox/layers/RealtimeLayer.js +270 -0
  74. package/mapbox/layers/index.js +2 -0
  75. package/mapbox/utils.js +43 -0
  76. package/mbt.js +6 -5
  77. package/mbt.js.map +2 -2
  78. package/mbt.min.js +2 -2
  79. package/mbt.min.js.map +2 -2
  80. package/ol/controls/CopyrightControl.js +69 -0
  81. package/ol/controls/RoutingControl.d.ts +6 -5
  82. package/ol/controls/RoutingControl.d.ts.map +1 -1
  83. package/ol/controls/RoutingControl.js +622 -0
  84. package/ol/controls/StopFinderControl.js +36 -0
  85. package/ol/controls/index.js +3 -0
  86. package/ol/index.js +5 -0
  87. package/ol/layers/Layer.d.ts +1 -1
  88. package/ol/layers/Layer.d.ts.map +1 -1
  89. package/ol/layers/Layer.js +148 -0
  90. package/ol/layers/MapboxLayer.d.ts +7 -7
  91. package/ol/layers/MapboxLayer.d.ts.map +1 -1
  92. package/ol/layers/MapboxLayer.js +101 -0
  93. package/ol/layers/MapboxStyleLayer.d.ts +3 -3
  94. package/ol/layers/MapboxStyleLayer.d.ts.map +1 -1
  95. package/ol/layers/MapboxStyleLayer.js +340 -0
  96. package/ol/layers/MaplibreLayer.d.ts +1 -1
  97. package/ol/layers/MaplibreLayer.d.ts.map +1 -1
  98. package/ol/layers/MaplibreLayer.js +35 -0
  99. package/ol/layers/RealtimeLayer.d.ts +2 -2
  100. package/ol/layers/RealtimeLayer.d.ts.map +1 -1
  101. package/ol/layers/RealtimeLayer.js +294 -0
  102. package/ol/layers/RoutingLayer.d.ts +2 -2
  103. package/ol/layers/RoutingLayer.d.ts.map +1 -1
  104. package/ol/layers/RoutingLayer.js +84 -0
  105. package/ol/layers/VectorLayer.d.ts +1 -1
  106. package/ol/layers/VectorLayer.d.ts.map +1 -1
  107. package/ol/layers/VectorLayer.js +38 -0
  108. package/ol/layers/WMSLayer.d.ts +1 -1
  109. package/ol/layers/WMSLayer.d.ts.map +1 -1
  110. package/ol/layers/WMSLayer.js +72 -0
  111. package/ol/layers/index.js +8 -0
  112. package/ol/styles/fullTrajectoryDelayStyle.js +33 -0
  113. package/ol/styles/fullTrajectoryStyle.js +44 -0
  114. package/ol/styles/index.js +2 -0
  115. package/package.json +2 -2
  116. package/setupTests.js +13 -0
@@ -0,0 +1,622 @@
1
+ import { Feature } from 'ol';
2
+ import { LineString, Point } from 'ol/geom';
3
+ import { Modify } from 'ol/interaction';
4
+ import { unByKey } from 'ol/Observable';
5
+ import { click } from 'ol/events/condition';
6
+ import { GeoJSON } from 'ol/format';
7
+ import { buffer } from 'ol/extent';
8
+ import { fromLonLat, toLonLat } from 'ol/proj';
9
+ import GeomType from 'ol/geom/GeometryType';
10
+ import { RoutingAPI } from '../../api';
11
+ import Control from '../../common/controls/Control';
12
+ import RoutingLayer from '../layers/RoutingLayer';
13
+ // Examples for a single hop:
14
+ // basel sbb a station named "basel sbb"
15
+ // ZUE, station "Zürich HB" by its common abbreviation
16
+ // Zürich Hauptbahnhof or HBF Zürich are all valid synonyms für "Zürich HB"
17
+ // @47.37811,8.53935 a station at position 47.37811, 8.53935
18
+ // @47.37811,8.53935$4 track 4 in a station at position 47.37811, 8.53935
19
+ // zürich hb@47.37811,8.53935$8 track 8 in station "Zürich HB" at position 47.37811, 8.53935
20
+ /** @private */
21
+ const REGEX_VIA_POINT = /^([^@$!\n]*)(@?([\d.]+),([\d.]+))?(\$?([a-zA-Z0-9]{0,2}))$/;
22
+ // Examples for a single hop:
23
+ //
24
+ // 47.37811,8.53935 a position 47.37811, 8.53935
25
+ /** @private */
26
+ const REGEX_VIA_POINT_COORD = /^([\d.]+),([\d.]+)$/;
27
+ // Examples for a single hop:
28
+ //
29
+ // !8596126 a station with id 8596126
30
+ // !8596126$4 a station with id 8596126
31
+ /** @private */
32
+ const REGEX_VIA_POINT_STATION_ID = /^!([^$]*)(\$?([a-zA-Z0-9]{0,2}))$/;
33
+ /** @private */
34
+ const STOP_FETCH_ABORT_CONTROLLER_KEY = 'stop-fetch';
35
+ /** @private */
36
+ const getFlatCoordinatesFromSegments = (segmentArray) => {
37
+ const coords = [];
38
+ segmentArray.forEach((seg) => {
39
+ coords.push(...seg.getGeometry().getCoordinates());
40
+ });
41
+ return coords;
42
+ };
43
+ /**
44
+ * Display a route of a specified mean of transport.
45
+ *
46
+ * @example
47
+ * import { Map } from 'ol';
48
+ * import { RoutingControl } from 'mobility-toolbox-js/ol';
49
+ *
50
+ * const map = new Map({
51
+ * target: 'map'
52
+ * });
53
+ *
54
+ * const control = new RoutingControl();
55
+ *
56
+ * control.attachToMap(map)
57
+ *
58
+ * @classproperty {string} apiKey - Key used for RoutingApi requests.
59
+ * @classproperty {string} stopsApiKey - Key used for Stop lookup requests (defaults to apiKey).
60
+ * @classproperty {string} stopsApiUrl - Url used for Stop lookup requests (defaults to https://api.geops.io/stops/v1/lookup/).
61
+ * @classproperty {Array.<Array<graph="osm", minZoom=0, maxZoom=99>>} graphs - Array of routing graphs and min/max zoom levels. If you use the control in combination with the [geOps Maps API](https://developer.geops.io/apis/maps/), you may want to use the optimal level of generalizations: "[['gen4', 0, 8], ['gen3', 8, 9], ['gen2', 9, 11], ['gen1', 11, 13], ['osm', 13, 99]]"
62
+ * @classproperty {string} mot - Mean of transport to be used for routing.
63
+ * @classproperty {object} routingApiParams - object of additional parameters to pass to the routing api request.
64
+ * @classproperty {object} snapToClosestStation - If true, the routing will snap the coordinate to the closest station. Default to false.
65
+ * @classproperty {boolean} useRawViaPoints - Experimental property. Wen true, it allows the user to add via points using different kind of string. See "via" parameter defined by the [geOps Routing API](https://developer.geops.io/apis/routing/). Default to false, only array of coordinates and station's id are supported as via points.
66
+ * @classproperty {RoutingLayer|Layer} routingLayer - Layer for adding route features.
67
+ * @classproperty {function} onRouteError - Callback on error.
68
+ * @classproperty {boolean} loading - True if the control is requesting the backend.
69
+ * @see <a href="/example/ol-routing">Openlayers routing example</a>
70
+ *
71
+ * @extends {Control}
72
+ * @implements {RoutingInterface}
73
+ */
74
+ class RoutingControl extends Control {
75
+ constructor(options = {}) {
76
+ super(options);
77
+ Object.defineProperties(this, {
78
+ mot: {
79
+ get: () => this.get('mot'),
80
+ set: (newMot) => {
81
+ if (newMot) {
82
+ this.set('mot', newMot);
83
+ if (this.viaPoints) {
84
+ this.drawRoute();
85
+ }
86
+ }
87
+ },
88
+ },
89
+ loading: {
90
+ get: () => this.get('loading'),
91
+ set: (newLoading) => {
92
+ this.set('loading', newLoading);
93
+ },
94
+ },
95
+ modify: {
96
+ get: () => this.get('modify'),
97
+ set: (modify) => {
98
+ this.set('modify', modify);
99
+ },
100
+ },
101
+ });
102
+ /** True if the control is requesting the backend. */
103
+ this.loading = false;
104
+ /** @ignore */
105
+ this.graphs = options.graphs || [['osm', 0, 99]];
106
+ /** @ignore */
107
+ this.mot = options.mot || 'bus';
108
+ /** @ignore */
109
+ this.modify = options.modify !== false;
110
+ /** @ignore */
111
+ this.routingApiParams = options.routingApiParams || {};
112
+ /** @ignore */
113
+ this.useRawViaPoints = options.useRawViaPoints || false;
114
+ /** @ignore */
115
+ this.snapToClosestStation = options.snapToClosestStation || false;
116
+ /** @ignore */
117
+ this.cacheStationData = {};
118
+ /** @ignore */
119
+ this.abortControllers = [];
120
+ /** @ignore */
121
+ this.apiKey = options.apiKey;
122
+ /** @ignore */
123
+ this.stopsApiKey = options.stopsApiKey || this.apiKey;
124
+ /** @ignore */
125
+ this.segments = [];
126
+ /** @ignore */
127
+ this.stopsApiUrl = options.stopsApiUrl || 'https://api.geops.io/stops/v1/';
128
+ /** @ignore */
129
+ this.api = new RoutingAPI(Object.assign({}, options));
130
+ /** @ignore */
131
+ this.routingLayer =
132
+ options.routingLayer ||
133
+ new RoutingLayer({
134
+ name: 'routing-layer',
135
+ style: options.style,
136
+ });
137
+ /** @ignore */
138
+ this.onRouteError =
139
+ options.onRouteError ||
140
+ ((error) => {
141
+ this.dispatchEvent({
142
+ type: 'change:route',
143
+ target: this,
144
+ });
145
+ this.reset();
146
+ // eslint-disable-next-line no-console
147
+ console.error(error);
148
+ });
149
+ /** @ignore */
150
+ this.viaPoints = [];
151
+ /** @ignore */
152
+ this.onMapClick = this.onMapClick.bind(this);
153
+ /** @ignore */
154
+ this.onModifyEnd = this.onModifyEnd.bind(this);
155
+ /** @ignore */
156
+ this.onModifyStart = this.onModifyStart.bind(this);
157
+ /** @ignore */
158
+ this.apiChangeListener = () => this.drawRoute();
159
+ /** @ignore */
160
+ this.createModifyInteraction();
161
+ }
162
+ /**
163
+ * Calculate at which resolutions corresponds each generalizations.
164
+ *
165
+ * @private
166
+ */
167
+ static getGraphsResolutions(graphs, map) {
168
+ const view = map.getView();
169
+ return graphs.map(([, minZoom, maxZoom]) => [
170
+ view.getResolutionForZoom(minZoom),
171
+ view.getResolutionForZoom(maxZoom || minZoom + 1),
172
+ ]);
173
+ }
174
+ /**
175
+ * Adds/Replaces a viaPoint to the viaPoints array and redraws route:
176
+ * Adds a viaPoint at end of array by default.
177
+ * If an index is passed a viaPoint is added at the specified index.
178
+ * If an index is passed and overwrite x is > 0, x viaPoints at the specified
179
+ * index are replaced with a single new viaPoint.
180
+ * @param {number[]|string} coordinates Array of coordinates
181
+ * @param {number} [index=-1] Integer representing the index of the added viaPoint. If not specified, the viaPoint is added at the end of the array.
182
+ * @param {number} [overwrite=0] Marks the number of viaPoints that are removed at the specified index on add.
183
+ */
184
+ addViaPoint(coordinatesOrString, index = -1, overwrite = 0) {
185
+ /* Add/Insert/Overwrite viapoint and redraw route */
186
+ this.viaPoints.splice(index === -1 ? this.viaPoints.length : index, overwrite, coordinatesOrString);
187
+ this.drawRoute();
188
+ this.dispatchEvent({
189
+ type: 'change:route',
190
+ target: this,
191
+ });
192
+ }
193
+ /**
194
+ * Removes a viaPoint at the passed array index and redraws route
195
+ * By default the last viaPoint is removed.
196
+ * @param {number} index Integer representing the index of the viaPoint to delete.
197
+ */
198
+ removeViaPoint(index = this.viaPoints.length - 1) {
199
+ /* Remove viapoint and redraw route */
200
+ if (this.viaPoints.length && this.viaPoints[index]) {
201
+ this.viaPoints.splice(index, 1);
202
+ }
203
+ this.drawRoute();
204
+ this.dispatchEvent({
205
+ type: 'change:route',
206
+ target: this,
207
+ });
208
+ }
209
+ /**
210
+ * Replaces the current viaPoints with a new coordinate array.
211
+ * @param {Array<Array<number>>} coordinateArray Array of nested coordinates
212
+ */
213
+ setViaPoints(coordinateArray) {
214
+ this.viaPoints = [...coordinateArray];
215
+ this.drawRoute();
216
+ this.dispatchEvent({
217
+ type: 'change:route',
218
+ target: this,
219
+ });
220
+ }
221
+ /**
222
+ * Removes all viaPoints, clears the source and triggers a change event
223
+ */
224
+ reset() {
225
+ // Clear viaPoints and source
226
+ this.abortRequests();
227
+ this.viaPoints = [];
228
+ this.routingLayer.olLayer.getSource().clear();
229
+ this.dispatchEvent({
230
+ type: 'change:route',
231
+ target: this,
232
+ });
233
+ }
234
+ /**
235
+ * Aborts viapoint and route requests
236
+ * @private
237
+ */
238
+ abortRequests() {
239
+ var _a;
240
+ // Abort Routing API requests
241
+ this.graphs.forEach(([graph]) => {
242
+ if (this.abortControllers[graph]) {
243
+ this.abortControllers[graph].abort();
244
+ }
245
+ this.abortControllers[graph] = new AbortController();
246
+ });
247
+ // Abort Stops API requests
248
+ (_a = this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY]) === null || _a === void 0 ? void 0 : _a.abort();
249
+ this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY] =
250
+ new AbortController();
251
+ this.loading = false;
252
+ }
253
+ /**
254
+ * Draws route on map using an array of coordinates:
255
+ * If a single coordinate is passed a single point feature is added to map.
256
+ * If two or more coordinates are passed a request to the RoutingAPI fetches
257
+ * the route using the passed coordinates and the current mot.
258
+ * @private
259
+ */
260
+ drawRoute() {
261
+ /* Calls RoutingAPI to draw a route using the viaPoints array */
262
+ this.abortRequests();
263
+ this.routingLayer.olLayer.getSource().clear();
264
+ if (!this.viaPoints.length) {
265
+ return null;
266
+ }
267
+ if (this.viaPoints.length === 1) {
268
+ // Add point for first node
269
+ return this.drawViaPoint(this.viaPoints[0], 0, this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY]);
270
+ }
271
+ const formattedViaPoints = this.viaPoints.map((viaPoint) => {
272
+ if (Array.isArray(viaPoint)) {
273
+ const projection = this.map.getView().getProjection();
274
+ // viaPoint is a coordinate
275
+ // Coordinates need to be reversed as required by the backend RoutingAPI
276
+ const [lon, lat] = toLonLat(viaPoint, projection);
277
+ return this.snapToClosestStation ? [`@${lat}`, lon] : [lat, lon];
278
+ }
279
+ // viaPoint is a string to use as it is
280
+ return this.useRawViaPoints ? viaPoint : `!${viaPoint}`;
281
+ });
282
+ this.loading = true;
283
+ // Clear source
284
+ this.routingLayer.olLayer.getSource().clear();
285
+ // Create point features for the viaPoints
286
+ this.viaPoints.forEach((viaPoint, idx) => this.drawViaPoint(viaPoint, idx, this.abortControllers[STOP_FETCH_ABORT_CONTROLLER_KEY]));
287
+ return Promise.all(this.graphs.map(([graph], index) => {
288
+ const { signal } = this.abortControllers[graph];
289
+ return this.api
290
+ .route(Object.assign({ graph, via: `${formattedViaPoints.join('|')}`, mot: `${this.mot}`, 'resolve-hops': false, elevation: false, 'coord-radius': 100.0, 'coord-punish': 1000.0 }, this.routingApiParams), { signal })
291
+ .then((featureCollection) => {
292
+ this.segments = this.format.readFeatures(featureCollection);
293
+ if (this.mot === 'foot') {
294
+ // Extract unique values from viaPoint target value
295
+ const uniqueVias = this.segments.reduce((resultVias, currentFeat) => {
296
+ const segTrg = currentFeat.get('trg');
297
+ return resultVias.find((via) => via[0] === segTrg[0] && via[1] === segTrg[1])
298
+ ? resultVias
299
+ : [...resultVias, segTrg];
300
+ }, []);
301
+ // Create LineString features from segments with same unique value
302
+ this.segments = uniqueVias.map((via) => {
303
+ const viaSegments = this.segments.filter((seg) => {
304
+ const segTrg = seg.get('trg');
305
+ return segTrg[0] === via[0] && segTrg[1] === via[1];
306
+ });
307
+ const coords = getFlatCoordinatesFromSegments(viaSegments);
308
+ return new Feature({
309
+ geometry: new LineString(coords),
310
+ });
311
+ });
312
+ }
313
+ // Create the new route. This route will be modifiable by the Modifiy interaction.
314
+ const coords = getFlatCoordinatesFromSegments(this.segments);
315
+ const routeFeature = new Feature({
316
+ geometry: new LineString(coords),
317
+ });
318
+ routeFeature.set('graph', graph);
319
+ routeFeature.set('mot', this.mot);
320
+ routeFeature.set('minResolution', this.graphsResolutions[index][0]);
321
+ routeFeature.set('maxResolution', this.graphsResolutions[index][1]);
322
+ this.routingLayer.olLayer.getSource().addFeature(routeFeature);
323
+ this.loading = false;
324
+ })
325
+ .catch((error) => {
326
+ if (error.name === 'AbortError') {
327
+ // Ignore abort error
328
+ return;
329
+ }
330
+ this.segments = [];
331
+ // Dispatch error event and execute error function
332
+ this.dispatchEvent({
333
+ type: 'error',
334
+ target: this,
335
+ });
336
+ this.onRouteError(error, this);
337
+ this.loading = false;
338
+ });
339
+ }));
340
+ }
341
+ /**
342
+ * Draw a via point. This function can parse all the possibilitiies
343
+ *
344
+ * @private
345
+ */
346
+ drawViaPoint(viaPoint, idx, abortController) {
347
+ const pointFeature = new Feature();
348
+ pointFeature.set('viaPointIdx', idx);
349
+ // The via point is a coordinate using the current map's projection
350
+ if (Array.isArray(viaPoint)) {
351
+ pointFeature.setGeometry(new Point(viaPoint));
352
+ this.routingLayer.olLayer.getSource().addFeature(pointFeature);
353
+ return Promise.resolve(pointFeature);
354
+ }
355
+ // Possibility to parse:
356
+ //
357
+ // !8596126 a station with id 8596126
358
+ // !8596126$4 a station with id 8596126
359
+ if (!this.useRawViaPoints || REGEX_VIA_POINT_STATION_ID.test(viaPoint)) {
360
+ let stationId;
361
+ let track;
362
+ if (this.useRawViaPoints) {
363
+ [, stationId, , track] = REGEX_VIA_POINT_STATION_ID.exec(viaPoint);
364
+ }
365
+ else {
366
+ [stationId, track] = viaPoint.split('$');
367
+ }
368
+ return fetch(`${this.stopsApiUrl}lookup/${stationId}?key=${this.stopsApiKey}`, { signal: abortController.signal })
369
+ .then((res) => res.json())
370
+ .then((stationData) => {
371
+ const { coordinates } = stationData.features[0].geometry;
372
+ this.cacheStationData[viaPoint] = fromLonLat(coordinates);
373
+ pointFeature.set('viaPointTrack', track);
374
+ pointFeature.setGeometry(new Point(fromLonLat(coordinates)));
375
+ this.routingLayer.olLayer.getSource().addFeature(pointFeature);
376
+ return pointFeature;
377
+ })
378
+ .catch((error) => {
379
+ if (error.name === 'AbortError') {
380
+ // Ignore abort error
381
+ return;
382
+ }
383
+ // Dispatch error event and execute error function
384
+ this.dispatchEvent({
385
+ type: 'error',
386
+ target: this,
387
+ });
388
+ this.onRouteError(error, this);
389
+ this.loading = false;
390
+ });
391
+ }
392
+ // Only when this.useRawViaPoints is true.
393
+ // Possibility to parse:
394
+ //
395
+ // 47.37811,8.53935 a position 47.37811, 8.53935
396
+ if (this.useRawViaPoints && REGEX_VIA_POINT_COORD.test(viaPoint)) {
397
+ const [lat, lon] = REGEX_VIA_POINT_COORD.exec(viaPoint);
398
+ const coordinates = fromLonLat([parseFloat(lon), parseFloat(lat)], this.map.getView().getProjection());
399
+ pointFeature.setGeometry(new Point(coordinates));
400
+ this.routingLayer.olLayer.getSource().addFeature(pointFeature);
401
+ return Promise.resolve(pointFeature);
402
+ }
403
+ // Only when this.useRawViaPoints is true.
404
+ // It will parse the via point to find some name, id, track coordinates.
405
+ //
406
+ // Possibility to parse:
407
+ //
408
+ // @47.37811,8.53935 a station at position 47.37811, 8.53935
409
+ // @47.37811,8.53935$4 track 4 in a station at position 47.37811, 8.53935
410
+ // zürich hb@47.37811,8.53935$8 track 8 in station "Zürich HB" at position 47.37811, 8.53935
411
+ const [, stationName, , lat, lon, , track] = REGEX_VIA_POINT.exec(viaPoint);
412
+ if (lon && lat) {
413
+ const coordinates = fromLonLat([parseFloat(lon), parseFloat(lat)], this.map.getView().getProjection());
414
+ pointFeature.set('viaPointTrack', track);
415
+ pointFeature.setGeometry(new Point(coordinates));
416
+ this.routingLayer.olLayer.getSource().addFeature(pointFeature);
417
+ return Promise.resolve(pointFeature);
418
+ }
419
+ if (stationName) {
420
+ return fetch(`${this.stopsApiUrl}?key=${this.stopsApiKey}&q=${stationName}&limit=1`, { signal: abortController.signal })
421
+ .then((res) => res.json())
422
+ .then((stationData) => {
423
+ const { coordinates } = stationData.features[0].geometry;
424
+ this.cacheStationData[viaPoint] = fromLonLat(coordinates);
425
+ pointFeature.set('viaPointTrack', track);
426
+ pointFeature.setGeometry(new Point(fromLonLat(coordinates)));
427
+ this.routingLayer.olLayer.getSource().addFeature(pointFeature);
428
+ return pointFeature;
429
+ })
430
+ .catch((error) => {
431
+ // Dispatch error event and execute error function
432
+ this.dispatchEvent({
433
+ type: 'error',
434
+ target: this,
435
+ });
436
+ this.onRouteError(error, this);
437
+ this.loading = false;
438
+ return null;
439
+ });
440
+ }
441
+ return Promise.resolve(null);
442
+ }
443
+ /**
444
+ * Used on click on map while control is active:
445
+ * By default adds a viaPoint to the end of array.
446
+ * If an existing viaPoint is clicked removes the clicked viaPoint.
447
+ * @private
448
+ */
449
+ onMapClick(e) {
450
+ const feats = e.target.getFeaturesAtPixel(e.pixel);
451
+ const viaPoint = feats.find((feat) => {
452
+ var _a;
453
+ return ((_a = feat.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType()) === GeomType.POINT &&
454
+ feat.get('viaPointIdx') !== undefined;
455
+ });
456
+ if (viaPoint) {
457
+ // Remove existing viaPoint on click and abort viaPoint add
458
+ this.removeViaPoint(viaPoint.get('viaPointIdx'));
459
+ return;
460
+ }
461
+ this.addViaPoint(e.coordinate);
462
+ }
463
+ /**
464
+ * Used on start of the modify interaction. Stores relevant data
465
+ * in this.initialRouteDrag object
466
+ * @private
467
+ */
468
+ onModifyStart(evt) {
469
+ // When modify start, we search the index of the segment that is modifying.
470
+ let segmentIndex = -1;
471
+ const route = evt.features
472
+ .getArray()
473
+ .find((feat) => { var _a; return ((_a = feat.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType()) === GeomType.LINE_STRING; });
474
+ // Find the segment index that is being modified
475
+ if (route) {
476
+ // We use a buff extent to fix floating issues , see https://github.com/openlayers/openlayers/issues/7130#issuecomment-535856422
477
+ const closestExtent = buffer(new Point(route.getGeometry().getClosestPoint(evt.mapBrowserEvent.coordinate)).getExtent(), 0.001);
478
+ segmentIndex = this.segments.findIndex((segment) => segment.getGeometry().intersectsExtent(closestExtent));
479
+ }
480
+ // Find the viaPoint that is being modified
481
+ const viaPoint = (evt.features
482
+ .getArray()
483
+ .filter((feat) => { var _a; return ((_a = feat.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType()) === GeomType.POINT; }) ||
484
+ [])[0];
485
+ // Write object with modify info
486
+ /** @ignore */
487
+ this.initialRouteDrag = {
488
+ viaPoint,
489
+ oldRoute: route && route.clone(),
490
+ segmentIndex,
491
+ };
492
+ }
493
+ /**
494
+ * Used on end of the modify interaction. Resolves feature modification:
495
+ * Line drag creates new viaPoint at the final coordinate of drag.
496
+ * Point drag replaces old viaPoint.
497
+ * @private
498
+ */
499
+ onModifyEnd(evt) {
500
+ const coord = evt.mapBrowserEvent.coordinate;
501
+ const { oldRoute, viaPoint, segmentIndex } = this.initialRouteDrag;
502
+ // If viaPoint is being relocated overwrite the old viaPoint
503
+ if (viaPoint) {
504
+ return this.addViaPoint(coord, viaPoint.get('viaPointIdx'), 1);
505
+ }
506
+ // In case there is no route overwrite first coordinate
507
+ if (!oldRoute) {
508
+ return this.addViaPoint(coord, 0, 1);
509
+ }
510
+ // We can't add a via point because we haven't found which segment has been modified.
511
+ if (segmentIndex === -1) {
512
+ return Promise.reject(new Error('No segment found'));
513
+ }
514
+ // Insert new viaPoint at the modified segment index + 1
515
+ return this.addViaPoint(coord, segmentIndex + 1);
516
+ }
517
+ /**
518
+ * Define a default element.
519
+ *
520
+ * @private
521
+ */
522
+ createDefaultElement() {
523
+ /** @ignore */
524
+ this.element = document.createElement('button');
525
+ this.element.id = 'ol-toggle-routing';
526
+ this.element.innerHTML = 'Toggle Route Control';
527
+ this.element.onclick = () => this.active ? this.deactivate() : this.activate();
528
+ Object.assign(this.element.style, {
529
+ position: 'absolute',
530
+ right: '10px',
531
+ top: '10px',
532
+ });
533
+ }
534
+ /**
535
+ * Create the interaction used to modify vertexes of features.
536
+ * @private
537
+ */
538
+ createModifyInteraction() {
539
+ /**
540
+ * @type {ol.interaction.Modify}
541
+ * @private
542
+ */
543
+ // Define and add modify interaction
544
+ this.modifyInteraction = new Modify({
545
+ source: this.routingLayer.olLayer.getSource(),
546
+ pixelTolerance: 4,
547
+ hitDetection: this.routingLayer.olLayer,
548
+ deleteCondition: (e) => {
549
+ const feats = e.target.getFeaturesAtPixel(e.pixel, {
550
+ hitTolerance: 5,
551
+ });
552
+ const viaPoint = feats.find((feat) => {
553
+ var _a;
554
+ return ((_a = feat.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType()) === GeomType.POINT &&
555
+ feat.get('index');
556
+ });
557
+ if (click(e) && viaPoint) {
558
+ // Remove node & viaPoint if an existing viaPoint was clicked
559
+ this.removeViaPoint(viaPoint.get('index'));
560
+ return true;
561
+ }
562
+ return false;
563
+ },
564
+ });
565
+ this.modifyInteraction.on('modifystart', this.onModifyStart);
566
+ this.modifyInteraction.on('modifyend', this.onModifyEnd);
567
+ this.modifyInteraction.setActive(false);
568
+ }
569
+ /**
570
+ * Add click listener to map.
571
+ * @private
572
+ */
573
+ addListeners() {
574
+ if (!this.modify) {
575
+ return;
576
+ }
577
+ this.removeListeners();
578
+ /** @ignore */
579
+ this.onMapClickKey = this.map.on('singleclick', this.onMapClick);
580
+ }
581
+ /**
582
+ * Remove click listener from map.
583
+ * @private
584
+ */
585
+ removeListeners() {
586
+ unByKey(this.onMapClickKey);
587
+ }
588
+ activate() {
589
+ super.activate();
590
+ if (this.map) {
591
+ /** @ignore */
592
+ this.format = new GeoJSON({
593
+ featureProjection: this.map.getView().getProjection(),
594
+ });
595
+ /** @ignore */
596
+ this.graphsResolutions = RoutingControl.getGraphsResolutions(this.graphs, this.map);
597
+ // Clean the modifyInteraction if present
598
+ this.map.removeInteraction(this.modifyInteraction);
599
+ // Add modify interaction, RoutingLayer and listeners
600
+ this.routingLayer.attachToMap(this.map);
601
+ this.map.addInteraction(this.modifyInteraction);
602
+ this.modifyInteraction.setActive(this.modify);
603
+ this.addListeners();
604
+ }
605
+ else {
606
+ // fall back to some default values if map is not available
607
+ this.format = new GeoJSON({ featureProjection: 'EPSG:3857' });
608
+ this.graphsResolutions = this.graphs;
609
+ }
610
+ }
611
+ deactivate() {
612
+ if (this.map) {
613
+ // Remove modify interaction, RoutingLayer, listeners and viaPoints
614
+ this.routingLayer.detachFromMap(this.map);
615
+ this.map.removeInteraction(this.modifyInteraction);
616
+ this.removeListeners();
617
+ this.reset();
618
+ }
619
+ super.deactivate();
620
+ }
621
+ }
622
+ export default RoutingControl;
@@ -0,0 +1,36 @@
1
+ import { fromLonLat } from 'ol/proj';
2
+ import Control from '../../common/controls/Control';
3
+ import mixin from '../../common/mixins/StopFinderMixin';
4
+ /**
5
+ * Search stations.
6
+ *
7
+ * @example
8
+ * import { Map } from 'ol';
9
+ * import { StopFinderControl } from 'mobility-toolbox-js/ol';
10
+ *
11
+ * const map = new Map({
12
+ * target: 'map',
13
+ * });
14
+ *
15
+ * const control = new StopFinderControl({
16
+ * apiKey: [yourApiKey]
17
+ * });
18
+ *
19
+ * control.attachToMap(map);
20
+ *
21
+ *
22
+ * @see <a href="/example/ol-search">Openlayers search example</a>
23
+ *
24
+ * @extends {Control}
25
+ * @implements {StopFinderInterface}
26
+ */
27
+ class StopFinderControl extends mixin(Control) {
28
+ /**
29
+ * @private
30
+ */
31
+ onSuggestionClick({ geometry }) {
32
+ const coord = fromLonLat(geometry.coordinates);
33
+ this.map.getView().setCenter(coord);
34
+ }
35
+ }
36
+ export default StopFinderControl;
@@ -0,0 +1,3 @@
1
+ export { default as CopyrightControl } from './CopyrightControl';
2
+ export { default as RoutingControl } from './RoutingControl';
3
+ export { default as StopFinderControl } from './StopFinderControl';
package/ol/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from '../api';
2
+ export * from '../common';
3
+ export * from './controls';
4
+ export * from './layers';
5
+ export * from './styles';
@@ -44,6 +44,6 @@ declare class Layer {
44
44
  * @param {Object} newOptions Options to override
45
45
  * @return {Layer} A Layer
46
46
  */
47
- clone(newOptions: any): Layer;
47
+ clone(newOptions: Object): Layer;
48
48
  }
49
49
  //# sourceMappingURL=Layer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Layer.d.ts","sourceRoot":"","sources":["../../../src/ol/layers/Layer.js"],"names":[],"mappings":";AAIA;;;;;;;;;;;;;;GAcG;AACH;IAqBE;;;;OAIG;IACH,qCAQC;IAED;;;OAGG;IACH,4BAiDC;IAED;;OAEG;IACH,sBAYC;IAED,iCAyBC;IAjBG,4BAGC;IASD,4BAGC;IAIL,mCAEC;IAED;;;OAGG;IACH,+BAMC;IAED;;;;OAIG;IACH,wBAFY,KAAK,CAIhB;CACF"}
1
+ {"version":3,"file":"Layer.d.ts","sourceRoot":"","sources":["../../../src/ol/layers/Layer.js"],"names":[],"mappings":";AAIA;;;;;;;;;;;;;;GAcG;AACH;IAqBE;;;;OAIG;IACH,qCAQC;IAED;;;OAGG;IACH,4BAiDC;IAED;;OAEG;IACH,sBAYC;IAED,iCAyBC;IAjBG,4BAGC;IASD,4BAGC;IAIL,mCAEC;IAED;;;OAGG;IACH,+BAMC;IAED;;;;OAIG;IACH,kBAHW,MAAM,GACL,KAAK,CAIhB;CACF"}