mobility-toolbox-js 2.0.0 → 2.0.1-beta.13

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