mobility-toolbox-js 1.7.1 → 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/common/mixins/TrackerLayerMixin.js +18 -8
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/ol/controls/RoutingControl.js +150 -27
- package/ol/controls/RoutingControl.test.js +69 -2
- package/ol/layers/TralisLayer.test.js +40 -1
- package/package.json +1 -1
|
@@ -10,6 +10,27 @@ import RoutingAPI from '../../api/routing/RoutingAPI';
|
|
|
10
10
|
import Control from '../../common/controls/Control';
|
|
11
11
|
import RoutingLayer from '../layers/RoutingLayer';
|
|
12
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
|
|
26
|
+
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
|
+
const REGEX_VIA_POINT_STATION_ID = /^!([^$]*)(\$?([a-zA-Z0-9]{0,2}))$/;
|
|
33
|
+
|
|
13
34
|
const getFlatCoordinatesFromSegments = (segmentArray) => {
|
|
14
35
|
const coords = [];
|
|
15
36
|
segmentArray.forEach((seg) => {
|
|
@@ -37,6 +58,8 @@ const getFlatCoordinatesFromSegments = (segmentArray) => {
|
|
|
37
58
|
* @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]]"
|
|
38
59
|
* @classproperty {string} mot - Mean of transport to be used for routing.
|
|
39
60
|
* @classproperty {object} routingApiParams - object of additional parameters to pass to the routing api request.
|
|
61
|
+
* @classproperty {object} snapToClosestStation - If true, the routing will snap the coordinate to the closest station. Default to false.
|
|
62
|
+
* @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.
|
|
40
63
|
* @classproperty {RoutingLayer|Layer} routingLayer - Layer for adding route features.
|
|
41
64
|
* @classproperty {function} onRouteError - Callback on error.
|
|
42
65
|
* @classproperty {boolean} loading - True if the control is requesting the backend.
|
|
@@ -90,6 +113,12 @@ class RoutingControl extends Control {
|
|
|
90
113
|
/** @ignore */
|
|
91
114
|
this.routingApiParams = options.routingApiParams || {};
|
|
92
115
|
|
|
116
|
+
/** @ignore */
|
|
117
|
+
this.useRawViaPoints = options.useRawViaPoints || false;
|
|
118
|
+
|
|
119
|
+
/** @ignore */
|
|
120
|
+
this.snapToClosestStation = options.snapToClosestStation || false;
|
|
121
|
+
|
|
93
122
|
/** @ignore */
|
|
94
123
|
this.cacheStationData = {};
|
|
95
124
|
|
|
@@ -106,8 +135,7 @@ class RoutingControl extends Control {
|
|
|
106
135
|
this.segments = [];
|
|
107
136
|
|
|
108
137
|
/** @ignore */
|
|
109
|
-
this.stopsApiUrl =
|
|
110
|
-
options.stopsApiUrl || 'https://api.geops.io/stops/v1/lookup/';
|
|
138
|
+
this.stopsApiUrl = options.stopsApiUrl || 'https://api.geops.io/stops/v1/';
|
|
111
139
|
|
|
112
140
|
/** @ignore */
|
|
113
141
|
this.api = new RoutingAPI({
|
|
@@ -139,8 +167,14 @@ class RoutingControl extends Control {
|
|
|
139
167
|
|
|
140
168
|
/** @ignore */
|
|
141
169
|
this.viaPoints = [];
|
|
170
|
+
|
|
171
|
+
/** @ignore */
|
|
142
172
|
this.onMapClick = this.onMapClick.bind(this);
|
|
173
|
+
|
|
174
|
+
/** @ignore */
|
|
143
175
|
this.onModifyEnd = this.onModifyEnd.bind(this);
|
|
176
|
+
|
|
177
|
+
/** @ignore */
|
|
144
178
|
this.onModifyStart = this.onModifyStart.bind(this);
|
|
145
179
|
|
|
146
180
|
/** @ignore */
|
|
@@ -173,9 +207,13 @@ class RoutingControl extends Control {
|
|
|
173
207
|
* @param {number} index Integer representing the index of the added viaPoint.
|
|
174
208
|
* @param {number} [overwrite=0] Marks the number of viaPoints that are removed at the specified index on add.
|
|
175
209
|
*/
|
|
176
|
-
addViaPoint(
|
|
210
|
+
addViaPoint(
|
|
211
|
+
coordinatesOrString,
|
|
212
|
+
index = this.viaPoints.length,
|
|
213
|
+
overwrite = 0,
|
|
214
|
+
) {
|
|
177
215
|
/* Add/Insert/Overwrite viapoint and redraw route */
|
|
178
|
-
this.viaPoints.splice(index, overwrite,
|
|
216
|
+
this.viaPoints.splice(index, overwrite, coordinatesOrString);
|
|
179
217
|
this.drawRoute();
|
|
180
218
|
this.dispatchEvent({
|
|
181
219
|
type: 'change:route',
|
|
@@ -247,12 +285,15 @@ class RoutingControl extends Control {
|
|
|
247
285
|
|
|
248
286
|
const formattedViaPoints = this.viaPoints.map((viaPoint) => {
|
|
249
287
|
if (Array.isArray(viaPoint)) {
|
|
288
|
+
const projection = this.map.getView().getProjection();
|
|
250
289
|
// viaPoint is a coordinate
|
|
251
290
|
// Coordinates need to be reversed as required by the backend RoutingAPI
|
|
252
|
-
|
|
291
|
+
const [lon, lat] = toLonLat(viaPoint, projection);
|
|
292
|
+
return this.snapToClosestStation ? [`@${lat}`, lon] : [lat, lon];
|
|
253
293
|
}
|
|
254
|
-
|
|
255
|
-
|
|
294
|
+
|
|
295
|
+
// viaPoint is a string to use as it is
|
|
296
|
+
return this.useRawViaPoints ? viaPoint : `!${viaPoint}`;
|
|
256
297
|
});
|
|
257
298
|
|
|
258
299
|
this.loading = true;
|
|
@@ -344,36 +385,118 @@ class RoutingControl extends Control {
|
|
|
344
385
|
}
|
|
345
386
|
|
|
346
387
|
/**
|
|
347
|
-
* Draw a via point.
|
|
388
|
+
* Draw a via point. This function can parse all the possibilitiies
|
|
348
389
|
*
|
|
349
390
|
* @private
|
|
350
391
|
*/
|
|
351
392
|
drawViaPoint(viaPoint, idx) {
|
|
352
393
|
const pointFeature = new Feature();
|
|
353
394
|
pointFeature.set('viaPointIdx', idx);
|
|
395
|
+
|
|
396
|
+
// The via point is a coordinate using the current map's projection
|
|
354
397
|
if (Array.isArray(viaPoint)) {
|
|
355
398
|
pointFeature.setGeometry(new Point(viaPoint));
|
|
356
|
-
|
|
399
|
+
this.routingLayer.olLayer.getSource().addFeature(pointFeature);
|
|
400
|
+
return Promise.resolve(pointFeature);
|
|
357
401
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
402
|
+
|
|
403
|
+
// Possibility to parse:
|
|
404
|
+
//
|
|
405
|
+
// !8596126 a station with id 8596126
|
|
406
|
+
// !8596126$4 a station with id 8596126
|
|
407
|
+
if (!this.useRawViaPoints || REGEX_VIA_POINT_STATION_ID.test(viaPoint)) {
|
|
408
|
+
let stationId;
|
|
409
|
+
let track;
|
|
410
|
+
if (this.useRawViaPoints) {
|
|
411
|
+
[, stationId, , track] = REGEX_VIA_POINT_STATION_ID.exec(viaPoint);
|
|
412
|
+
} else {
|
|
413
|
+
[stationId, track] = viaPoint.split('$');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return fetch(
|
|
417
|
+
`${this.stopsApiUrl}lookup/${stationId}?key=${this.stopsApiKey}`,
|
|
418
|
+
)
|
|
419
|
+
.then((res) => res.json())
|
|
420
|
+
.then((stationData) => {
|
|
421
|
+
const { coordinates } = stationData.features[0].geometry;
|
|
422
|
+
this.cacheStationData[viaPoint] = fromLonLat(coordinates);
|
|
423
|
+
pointFeature.set('viaPointTrack', track);
|
|
424
|
+
pointFeature.setGeometry(new Point(fromLonLat(coordinates)));
|
|
425
|
+
this.routingLayer.olLayer.getSource().addFeature(pointFeature);
|
|
426
|
+
return pointFeature;
|
|
427
|
+
})
|
|
428
|
+
.catch((error) => {
|
|
429
|
+
// Dispatch error event and execute error function
|
|
430
|
+
this.dispatchEvent({
|
|
431
|
+
type: 'error',
|
|
432
|
+
target: this,
|
|
433
|
+
});
|
|
434
|
+
this.onRouteError(error, this);
|
|
435
|
+
this.loading = false;
|
|
373
436
|
});
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Only when this.useRawViaPoints is true.
|
|
440
|
+
// Possibility to parse:
|
|
441
|
+
//
|
|
442
|
+
// 47.37811,8.53935 a position 47.37811, 8.53935
|
|
443
|
+
if (this.useRawViaPoints && REGEX_VIA_POINT_COORD.test(viaPoint)) {
|
|
444
|
+
const [lat, lon] = REGEX_VIA_POINT_COORD.exec(viaPoint);
|
|
445
|
+
const coordinates = fromLonLat(
|
|
446
|
+
[parseFloat(lon), parseFloat(lat)],
|
|
447
|
+
this.map.getView().getProjection(),
|
|
448
|
+
);
|
|
449
|
+
pointFeature.setGeometry(new Point(coordinates));
|
|
450
|
+
this.routingLayer.olLayer.getSource().addFeature(pointFeature);
|
|
451
|
+
return Promise.resolve(pointFeature);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Only when this.useRawViaPoints is true.
|
|
455
|
+
// It will parse the via point to find some name, id, track coordinates.
|
|
456
|
+
//
|
|
457
|
+
// Possibility to parse:
|
|
458
|
+
//
|
|
459
|
+
// @47.37811,8.53935 a station at position 47.37811, 8.53935
|
|
460
|
+
// @47.37811,8.53935$4 track 4 in a station at position 47.37811, 8.53935
|
|
461
|
+
// zürich hb@47.37811,8.53935$8 track 8 in station "Zürich HB" at position 47.37811, 8.53935
|
|
462
|
+
const [, stationName, , lat, lon, , track] = REGEX_VIA_POINT.exec(viaPoint);
|
|
463
|
+
|
|
464
|
+
if (lon && lat) {
|
|
465
|
+
const coordinates = fromLonLat(
|
|
466
|
+
[parseFloat(lon), parseFloat(lat)],
|
|
467
|
+
this.map.getView().getProjection(),
|
|
468
|
+
);
|
|
469
|
+
pointFeature.set('viaPointTrack', track);
|
|
470
|
+
pointFeature.setGeometry(new Point(coordinates));
|
|
471
|
+
this.routingLayer.olLayer.getSource().addFeature(pointFeature);
|
|
472
|
+
return Promise.resolve(pointFeature);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (stationName) {
|
|
476
|
+
return fetch(
|
|
477
|
+
`${this.stopsApiUrl}?key=${this.stopsApiKey}&q=${stationName}&limit=1`,
|
|
478
|
+
)
|
|
479
|
+
.then((res) => res.json())
|
|
480
|
+
.then((stationData) => {
|
|
481
|
+
const { coordinates } = stationData.features[0].geometry;
|
|
482
|
+
this.cacheStationData[viaPoint] = fromLonLat(coordinates);
|
|
483
|
+
pointFeature.set('viaPointTrack', track);
|
|
484
|
+
pointFeature.setGeometry(new Point(fromLonLat(coordinates)));
|
|
485
|
+
this.routingLayer.olLayer.getSource().addFeature(pointFeature);
|
|
486
|
+
return pointFeature;
|
|
487
|
+
})
|
|
488
|
+
.catch((error) => {
|
|
489
|
+
// Dispatch error event and execute error function
|
|
490
|
+
this.dispatchEvent({
|
|
491
|
+
type: 'error',
|
|
492
|
+
target: this,
|
|
493
|
+
});
|
|
494
|
+
this.onRouteError(error, this);
|
|
495
|
+
this.loading = false;
|
|
496
|
+
return null;
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return Promise.resolve(null);
|
|
377
500
|
}
|
|
378
501
|
|
|
379
502
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fetch from 'jest-fetch-mock';
|
|
2
2
|
import View from 'ol/View';
|
|
3
|
+
import qs from 'query-string';
|
|
3
4
|
import Map from '../Map';
|
|
4
5
|
import RoutingControl from './RoutingControl';
|
|
5
6
|
|
|
@@ -38,6 +39,8 @@ describe('RoutingControl', () => {
|
|
|
38
39
|
test('should be activate by default', () => {
|
|
39
40
|
const control = new RoutingControl();
|
|
40
41
|
expect(control.active).toBe(true);
|
|
42
|
+
expect(control.snapToClosestStation).toBe(false);
|
|
43
|
+
expect(control.useRawViaPoints).toBe(false);
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
test('launch routing and add features', (done) => {
|
|
@@ -99,10 +102,10 @@ describe('RoutingControl', () => {
|
|
|
99
102
|
.then(() => {
|
|
100
103
|
// Should use correct URL
|
|
101
104
|
expect(fetch.mock.calls[0][0]).toEqual(
|
|
102
|
-
'https://foo.ch/a4dca961d199ff76?key=foo',
|
|
105
|
+
'https://foo.ch/lookup/a4dca961d199ff76?key=foo',
|
|
103
106
|
);
|
|
104
107
|
expect(fetch.mock.calls[1][0]).toEqual(
|
|
105
|
-
'https://foo.ch/e3666f03cba06b2b?key=foo',
|
|
108
|
+
'https://foo.ch/lookup/e3666f03cba06b2b?key=foo',
|
|
106
109
|
);
|
|
107
110
|
expect(fetch.mock.calls[2][0]).toEqual(
|
|
108
111
|
'https://foo.ch/?coord-punish=1000&coord-radius=100&elevation=false&graph=gen5&key=foo&mot=bus&resolve-hops=false&via=%21a4dca961d199ff76%7C%21e3666f03cba06b2b',
|
|
@@ -146,4 +149,68 @@ describe('RoutingControl', () => {
|
|
|
146
149
|
done();
|
|
147
150
|
});
|
|
148
151
|
});
|
|
152
|
+
|
|
153
|
+
test('calls routing api with @ before the coordinates when snapToClosestStation is true', (done) => {
|
|
154
|
+
fetch.mockResponses(
|
|
155
|
+
[JSON.stringify(RoutingControlStation1), { status: 200 }],
|
|
156
|
+
[JSON.stringify(global.fetchRouteResponse), { status: 200 }],
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const control = new RoutingControl({
|
|
160
|
+
apiKey: 'foo',
|
|
161
|
+
snapToClosestStation: true,
|
|
162
|
+
});
|
|
163
|
+
control.map = map;
|
|
164
|
+
expect(map.getTarget().querySelector('#ol-toggle-routing')).toBeDefined();
|
|
165
|
+
control.viaPoints = [
|
|
166
|
+
[950476.4055933182, 6003322.253698345],
|
|
167
|
+
[950389.0813034325, 6003656.659274571],
|
|
168
|
+
'e3666f03cba06b2b',
|
|
169
|
+
];
|
|
170
|
+
control
|
|
171
|
+
.drawRoute(control.viaPoints)
|
|
172
|
+
.then(() => {
|
|
173
|
+
const params = qs.parseUrl(fetch.mock.calls[1][0]).query;
|
|
174
|
+
expect(params.via).toBe(
|
|
175
|
+
'@47.3739194713294,8.538274823394632|@47.37595378493421,8.537490375951839|!e3666f03cba06b2b',
|
|
176
|
+
);
|
|
177
|
+
done();
|
|
178
|
+
})
|
|
179
|
+
.catch(() => {});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('calls routing api with raw via points', (done) => {
|
|
183
|
+
fetch.mockResponses(
|
|
184
|
+
[JSON.stringify(RoutingControlStation1), { status: 200 }],
|
|
185
|
+
[JSON.stringify(RoutingControlStation2), { status: 200 }],
|
|
186
|
+
[JSON.stringify(global.fetchRouteResponse), { status: 200 }],
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const control = new RoutingControl({
|
|
190
|
+
apiKey: 'foo',
|
|
191
|
+
useRawViaPoints: true,
|
|
192
|
+
});
|
|
193
|
+
control.map = map;
|
|
194
|
+
expect(map.getTarget().querySelector('#ol-toggle-routing')).toBeDefined();
|
|
195
|
+
control.viaPoints = [
|
|
196
|
+
'46.2,7.1',
|
|
197
|
+
'@46.2,7.1',
|
|
198
|
+
'@46.2,7$1',
|
|
199
|
+
'station name$2', // will send a stops request fo the station name
|
|
200
|
+
'station name@46.2,7', // will use the coordinate
|
|
201
|
+
'stationname@46.2,7.7$3', // will use the coordinate
|
|
202
|
+
'!stationid', // will send a stops lookup request fo the station id
|
|
203
|
+
[950389, 6003656],
|
|
204
|
+
];
|
|
205
|
+
control
|
|
206
|
+
.drawRoute(control.viaPoints)
|
|
207
|
+
.then(() => {
|
|
208
|
+
const params = qs.parseUrl(fetch.mock.calls[2][0]).query;
|
|
209
|
+
expect(params.via).toBe(
|
|
210
|
+
'46.2,7.1|@46.2,7.1|@46.2,7$1|station name$2|station name@46.2,7|stationname@46.2,7.7$3|!stationid|47.375949774398805,8.537489645590679',
|
|
211
|
+
);
|
|
212
|
+
done();
|
|
213
|
+
})
|
|
214
|
+
.catch(() => {});
|
|
215
|
+
});
|
|
149
216
|
});
|
|
@@ -61,6 +61,18 @@ describe('TrajservLayer', () => {
|
|
|
61
61
|
expect(clone).toBeInstanceOf(TralisLayer);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
test('should use the sort function.', () => {
|
|
65
|
+
const fn = () => true;
|
|
66
|
+
const laye = new TralisLayer({
|
|
67
|
+
url: 'ws://localhost:1234',
|
|
68
|
+
apiKey: 'apiKey',
|
|
69
|
+
sort: fn,
|
|
70
|
+
});
|
|
71
|
+
expect(laye).toBeInstanceOf(TralisLayer);
|
|
72
|
+
expect(laye.useDelayStyle).toBe(false);
|
|
73
|
+
expect(laye.sort).toBe(fn);
|
|
74
|
+
});
|
|
75
|
+
|
|
64
76
|
test('should set a default sort function if useDelayStyle is used.', () => {
|
|
65
77
|
const laye = new TralisLayer({
|
|
66
78
|
url: 'ws://localhost:1234',
|
|
@@ -82,7 +94,7 @@ describe('TrajservLayer', () => {
|
|
|
82
94
|
expect(trajectories).toEqual([red, yellow, cancelled, green2, green, gray]);
|
|
83
95
|
});
|
|
84
96
|
|
|
85
|
-
test('should override the
|
|
97
|
+
test('should override the default sort function when useDelayStyle is used.', () => {
|
|
86
98
|
const laye = new TralisLayer({
|
|
87
99
|
url: 'ws://localhost:1234',
|
|
88
100
|
apiKey: 'apiKey',
|
|
@@ -103,4 +115,31 @@ describe('TrajservLayer', () => {
|
|
|
103
115
|
trajectories.sort(laye.sort);
|
|
104
116
|
expect(trajectories).toEqual([cancelled, green2, red, yellow, green, gray]);
|
|
105
117
|
});
|
|
118
|
+
|
|
119
|
+
test('should use filter function.', () => {
|
|
120
|
+
const fn = () => true;
|
|
121
|
+
const laye = new TralisLayer({
|
|
122
|
+
url: 'ws://localhost:1234',
|
|
123
|
+
apiKey: 'apiKey',
|
|
124
|
+
useDelayStyle: true,
|
|
125
|
+
filter: fn, // reverse the array
|
|
126
|
+
});
|
|
127
|
+
expect(laye).toBeInstanceOf(TralisLayer);
|
|
128
|
+
expect(laye.useDelayStyle).toBe(true);
|
|
129
|
+
expect(laye.filter).toBe(fn);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('should override filter function if operator, tripNumber, regexPublishedLineName is set.', () => {
|
|
133
|
+
const fn = () => true;
|
|
134
|
+
const laye = new TralisLayer({
|
|
135
|
+
url: 'ws://localhost:1234',
|
|
136
|
+
apiKey: 'apiKey',
|
|
137
|
+
useDelayStyle: true,
|
|
138
|
+
filter: fn, // reverse the array
|
|
139
|
+
publishedLineName: '.*',
|
|
140
|
+
});
|
|
141
|
+
expect(laye).toBeInstanceOf(TralisLayer);
|
|
142
|
+
expect(laye.useDelayStyle).toBe(true);
|
|
143
|
+
expect(laye.filter).not.toBe(fn);
|
|
144
|
+
});
|
|
106
145
|
});
|
package/package.json
CHANGED