@wemap/routers 6.2.3 → 7.0.0

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 (57) hide show
  1. package/assets/biocbon-bergere-rdc-network.osm +163 -0
  2. package/assets/gare-de-lest-network-pp-bounds.osm +1615 -0
  3. package/dist/wemap-routers.es.js +3011 -0
  4. package/dist/wemap-routers.es.js.map +1 -0
  5. package/index.js +13 -5
  6. package/package.json +9 -6
  7. package/src/Constants.js +1 -0
  8. package/src/ItineraryInfoManager.spec.js +2 -2
  9. package/src/Utils.js +0 -77
  10. package/src/model/Itinerary.js +41 -5
  11. package/src/model/Itinerary.spec.js +91 -0
  12. package/src/model/Itinerary.type.spec.js +3 -78
  13. package/src/model/Leg.js +89 -19
  14. package/src/model/Leg.spec.js +110 -0
  15. package/src/model/Leg.type.spec.js +48 -0
  16. package/src/model/LevelChange.js +14 -24
  17. package/src/model/LevelChange.spec.js +78 -0
  18. package/src/model/LevelChange.type.spec.js +26 -0
  19. package/src/model/RouterResponse.js +70 -1
  20. package/src/model/RouterResponse.spec.js +85 -0
  21. package/src/model/RouterResponse.type.spec.js +7 -4
  22. package/src/model/Step.js +45 -6
  23. package/src/model/Step.spec.js +100 -0
  24. package/src/model/Step.type.spec.js +52 -0
  25. package/src/remote/RemoteRouter.js +31 -0
  26. package/src/remote/RemoteRouterManager.js +84 -0
  27. package/src/remote/RemoteRouterOptions.js +25 -0
  28. package/src/remote/RemoteRouterServerUnreachable.js +10 -0
  29. package/src/remote/RemoteRouterUtils.js +78 -0
  30. package/src/remote/RoutingModeCorrespondanceNotFound.js +18 -0
  31. package/src/remote/cityway/CitywayRemoteRouter.js +386 -0
  32. package/src/{cityway/CitywayUtils.spec.js → remote/cityway/CitywayRemoteRouter.spec.js} +19 -18
  33. package/src/remote/deutsche-bahn/DeutscheBahnRemoteRouter.js +143 -0
  34. package/src/{deutsche-bahn/DeutscheBahnRouterUtils.spec.js → remote/deutsche-bahn/DeutscheBahnRemoteRouter.spec.js} +7 -6
  35. package/src/remote/idfm/IdfmRemoteRouter.js +432 -0
  36. package/src/{idfm/IdfmUtils.spec.js → remote/idfm/IdfmRemoteRouter.spec.js} +7 -6
  37. package/src/remote/idfm/IdfmRemoteRouterTokenError.js +6 -0
  38. package/src/remote/osrm/OsrmRemoteRouter.js +331 -0
  39. package/src/{osrm/OsrmUtils.spec.js → remote/osrm/OsrmRemoteRouter.spec.js} +9 -15
  40. package/src/remote/otp/OtpRemoteRouter.js +222 -0
  41. package/src/{otp/OtpUtils.spec.js → remote/otp/OtpRemoteRouter.spec.js} +10 -9
  42. package/src/remote/wemap-meta/WemapMetaRemoteRouter.js +57 -0
  43. package/src/remote/wemap-meta/WemapMetaRemoteRouter.spec.js +22 -0
  44. package/src/remote/wemap-meta/WemapMetaRemoteRouterOptions.js +36 -0
  45. package/src/remote/wemap-meta/WemapMetaRemoteRouterPayload.js +44 -0
  46. package/src/wemap/WemapRouter.js +6 -0
  47. package/src/wemap/WemapRouterUtils.js +10 -4
  48. package/src/wemap/WemapStepsGeneration.js +36 -9
  49. package/src/wemap-meta/IOMap.js +191 -0
  50. package/src/wemap-meta/WemapMetaRouter.js +314 -0
  51. package/src/wemap-meta/WemapMetaRouter.spec.js +119 -0
  52. package/src/wemap-meta/WemapMetaRouterOptions.js +20 -0
  53. package/src/cityway/CitywayUtils.js +0 -309
  54. package/src/deutsche-bahn/DeutscheBahnRouterUtils.js +0 -91
  55. package/src/idfm/IdfmUtils.js +0 -256
  56. package/src/osrm/OsrmUtils.js +0 -269
  57. package/src/otp/OtpUtils.js +0 -150
@@ -0,0 +1,3011 @@
1
+ import { Coordinates, Network, GraphRouterOptions, GraphRouter, GraphUtils, Level, GraphEdge, GraphNode, GraphItinerary, Utils, MapMatching } from '@wemap/geo';
2
+ import { OsmWay, OsmNode } from '@wemap/osm';
3
+ import { deg2rad, diffAngle, positiveMod, rad2deg } from '@wemap/maths';
4
+ import Logger from '@wemap/logger';
5
+ import Polyline from '@mapbox/polyline';
6
+
7
+ class LevelChange {
8
+
9
+ /** @type {!string} [up|down] */
10
+ direction;
11
+
12
+ /** @type {!number} [-2, -1, 1, ...] */
13
+ difference;
14
+
15
+ /** @type {?string} [elevator|conveyor|stairs] */
16
+ type = null;
17
+
18
+ /**
19
+ * @param {LevelChange} obj1
20
+ * @param {LevelChange} obj2
21
+ * @returns {Boolean}
22
+ */
23
+ static equalsTo(obj1, obj2) {
24
+ return obj1.difference === obj2.difference
25
+ && obj1.direction === obj2.direction
26
+ && obj1.type === obj2.type;
27
+ }
28
+
29
+ /**
30
+ * @param {LevelChange} obj
31
+ * @returns {Boolean}
32
+ */
33
+ equalsTo(obj) {
34
+ return LevelChange.equalsTo(this, obj);
35
+ }
36
+
37
+ /**
38
+ * @returns {object}
39
+ */
40
+ toJson() {
41
+ return {
42
+ direction: this.direction,
43
+ difference: this.difference,
44
+ type: this.type
45
+ };
46
+ }
47
+
48
+ /**
49
+ * @param {object} json
50
+ * @returns {LevelChange}
51
+ */
52
+ static fromJson(json) {
53
+ const levelChange = new LevelChange();
54
+ levelChange.direction = json.direction;
55
+ levelChange.difference = json.difference;
56
+ levelChange.type = json.type;
57
+ return levelChange;
58
+ }
59
+ }
60
+
61
+ class Step {
62
+
63
+ /** @type {!boolean} */
64
+ firstStep = false;
65
+
66
+ /** @type {!boolean} */
67
+ lastStep = false;
68
+
69
+ /** @type {!number} */
70
+ number;
71
+
72
+ /** @type {!Coordinates} */
73
+ coords = [];
74
+
75
+
76
+ /** @type {!number} */
77
+ angle;
78
+
79
+ /** @type {!number} */
80
+ previousBearing;
81
+
82
+ /** @type {!number} */
83
+ nextBearing;
84
+
85
+
86
+ /** @type {!number} */
87
+ distance;
88
+
89
+ /** @type {?number} */
90
+ duration = null;
91
+
92
+ /** @type {?string} */
93
+ name = null;
94
+
95
+
96
+ /** @type {?LevelChange} */
97
+ levelChange = null;
98
+
99
+ /** @type {?{?subwayEntrance: boolean, ?subwayEntranceRef: string}} */
100
+ extras = {};
101
+
102
+ /** @type {!number} */
103
+ _idCoordsInLeg = null;
104
+
105
+ /**
106
+ * @param {Step} obj1
107
+ * @param {Step} obj2
108
+ * @returns {Boolean}
109
+ */
110
+ static equalsTo(obj1, obj2) {
111
+ return obj1.firstStep === obj2.firstStep
112
+ && obj1.lastStep === obj2.lastStep
113
+ && obj1.number === obj2.number
114
+ && obj1.coords.equalsTo(obj2.coords)
115
+ && obj1.angle === obj2.angle
116
+ && obj1.previousBearing === obj2.previousBearing
117
+ && obj1.nextBearing === obj2.nextBearing
118
+ && obj1.distance === obj2.distance
119
+ && obj1.duration === obj2.duration
120
+ && obj1.name === obj2.name
121
+ && (
122
+ obj1.levelChange === obj2.levelChange
123
+ || obj1.levelChange !== null && obj1.levelChange.equalsTo(obj2.levelChange)
124
+ )
125
+ && (
126
+ obj1.extras === obj2.extras
127
+ || (
128
+ obj1.extras !== null
129
+ && obj1.extras.subwayEntrance === obj2.extras.subwayEntrance
130
+ && obj1.extras.subwayEntranceRef === obj2.extras.subwayEntranceRef
131
+ )
132
+ )
133
+ && obj1._idCoordsInLeg === obj2._idCoordsInLeg;
134
+ }
135
+
136
+ /**
137
+ * @param {Step} obj
138
+ * @returns {Boolean}
139
+ */
140
+ equalsTo(obj) {
141
+ return Step.equalsTo(this, obj);
142
+ }
143
+
144
+ /**
145
+ * @returns {object}
146
+ */
147
+ toJson() {
148
+ const output = {
149
+ number: this.number,
150
+ coords: this.coords.toCompressedJson(),
151
+ angle: this.angle,
152
+ previousBearing: this.previousBearing,
153
+ nextBearing: this.nextBearing,
154
+ distance: this.distance,
155
+ _idCoordsInLeg: this._idCoordsInLeg
156
+ };
157
+ if (this.firstStep) {
158
+ output.firstStep = true;
159
+ }
160
+ if (this.lastStep) {
161
+ output.lastStep = true;
162
+ }
163
+ if (this.duration !== null) {
164
+ output.duration = this.duration;
165
+ }
166
+ if (this.name !== null) {
167
+ output.name = this.name;
168
+ }
169
+ if (this.levelChange !== null) {
170
+ output.levelChange = this.levelChange.toJson();
171
+ }
172
+ if (this.extras && Object.keys(this.extras).length !== 0) {
173
+ output.extras = this.extras;
174
+ }
175
+ return output;
176
+ }
177
+
178
+ /**
179
+ * @param {object} json
180
+ * @returns {Step}
181
+ */
182
+ static fromJson(json) {
183
+ const step = new Step();
184
+ step.number = json.number;
185
+ step.coords = Coordinates.fromCompressedJson(json.coords);
186
+ step.angle = json.angle;
187
+ step.previousBearing = json.previousBearing;
188
+ step.nextBearing = json.nextBearing;
189
+ step.distance = json.distance;
190
+ step._idCoordsInLeg = json._idCoordsInLeg;
191
+ if (typeof json.firstStep === 'boolean') {
192
+ step.firstStep = json.firstStep;
193
+ }
194
+ if (typeof json.lastStep === 'boolean') {
195
+ step.lastStep = json.lastStep;
196
+ }
197
+ if (typeof json.duration === 'number') {
198
+ step.duration = json.duration;
199
+ }
200
+ if (typeof json.name === 'string') {
201
+ step.name = json.name;
202
+ }
203
+ if (typeof json.levelChange === 'object') {
204
+ step.levelChange = LevelChange.fromJson(json.levelChange);
205
+ }
206
+ if (typeof json.extras === 'object') {
207
+ step.extras = json.extras;
208
+ }
209
+ return step;
210
+ }
211
+ }
212
+
213
+ const Constants = {};
214
+
215
+ Constants.ROUTING_MODE = {
216
+ AIRPLANE: 'AIRPLANE',
217
+ BOAT: 'BOAT',
218
+ BIKE: 'BIKE',
219
+ BUS: 'BUS',
220
+ CAR: 'CAR',
221
+ FERRY: 'FERRY',
222
+ FUNICULAR: 'FUNICULAR',
223
+ METRO: 'METRO',
224
+ MOTO: 'MOTO',
225
+ TRAIN: 'TRAIN',
226
+ TAXI: 'TAXI',
227
+ TRAM: 'TRAM',
228
+ WALK: 'WALK',
229
+ MULTI: 'MULTI',
230
+ UNKNOWN: 'UNKNOWN'
231
+ };
232
+
233
+ Constants.PUBLIC_TRANSPORT = [
234
+ Constants.ROUTING_MODE.AIRPLANE,
235
+ Constants.ROUTING_MODE.BOAT,
236
+ Constants.ROUTING_MODE.BUS,
237
+ Constants.ROUTING_MODE.FERRY,
238
+ Constants.ROUTING_MODE.FUNICULAR,
239
+ Constants.ROUTING_MODE.METRO,
240
+ Constants.ROUTING_MODE.TRAIN,
241
+ Constants.ROUTING_MODE.TRAM
242
+ ];
243
+
244
+ class Leg {
245
+
246
+ /** @type {!string} can be values in Constants.ROUTING_MODE */
247
+ mode;
248
+
249
+ /** @type {!number} */
250
+ distance;
251
+
252
+ /** @type {!number} */
253
+ duration;
254
+
255
+ /** @type {?number} */
256
+ startTime = null;
257
+
258
+ /** @type {?number} */
259
+ endTime = null;
260
+
261
+ /** @type {!{name: ?string, coords: !Coordinates}} */
262
+ from;
263
+
264
+ /** @type {!{name: ?string, coords: !Coordinates}} */
265
+ to;
266
+
267
+ /** @type {!Coordinates[]} */
268
+ coords;
269
+
270
+ /** @type {?{name: !string, routeColor: ?string, routeTextColor: ?string, directionName: ?string}} */
271
+ transportInfo = null;
272
+
273
+ /** @type {?(Step[])} */
274
+ steps = null;
275
+
276
+ isPublicTransport() {
277
+ return Constants.PUBLIC_TRANSPORT.includes(this.mode);
278
+ }
279
+
280
+ /**
281
+ * @returns {Network}
282
+ */
283
+ toNetwork() {
284
+ return Network.fromCoordinates([this.coords]);
285
+ }
286
+
287
+
288
+ /**
289
+ * @param {Leg} obj1
290
+ * @param {Leg} obj2
291
+ * @returns {Boolean}
292
+ */
293
+ // eslint-disable-next-line complexity
294
+ static equalsTo(obj1, obj2) {
295
+ const intermediate = obj1.mode === obj2.mode
296
+ && obj1.distance === obj2.distance
297
+ && obj1.duration === obj2.duration
298
+ && obj1.startTime === obj2.startTime
299
+ && obj1.endTime === obj2.endTime
300
+ && obj1.from.name === obj2.from.name
301
+ && obj1.from.coords.equalsTo(obj2.from.coords)
302
+ && obj1.to.name === obj2.to.name
303
+ && obj1.to.coords.equalsTo(obj2.to.coords)
304
+ && obj1.coords.length === obj2.coords.length
305
+ && (
306
+ obj1.steps === obj2.steps
307
+ || obj1.steps.length === obj2.steps.length
308
+ );
309
+
310
+ if (!intermediate) {
311
+ return false;
312
+ }
313
+
314
+ let i;
315
+ for (i = 0; i < obj1.coords.length; i++) {
316
+ if (!obj1.coords[i].equalsTo(obj2.coords[i])) {
317
+ return false;
318
+ }
319
+ }
320
+ if (obj1.steps) {
321
+ for (i = 0; i < obj1.steps.length; i++) {
322
+ if (!obj1.steps[i].equalsTo(obj2.steps[i])) {
323
+ return false;
324
+ }
325
+ }
326
+ }
327
+
328
+ if (obj1.transportInfo !== obj2.transportInfo) {
329
+ if (obj1.transportInfo === null) {
330
+ return false;
331
+ }
332
+ if (
333
+ obj1.transportInfo.name !== obj2.transportInfo.name
334
+ || obj1.transportInfo.routeColor !== obj2.transportInfo.routeColor
335
+ || obj1.transportInfo.routeTextColor !== obj2.transportInfo.routeTextColor
336
+ || obj1.transportInfo.directionName !== obj2.transportInfo.directionName
337
+ ) {
338
+ return false;
339
+ }
340
+ }
341
+
342
+ return true;
343
+ }
344
+
345
+ /**
346
+ * @param {Leg} obj
347
+ * @returns {Boolean}
348
+ */
349
+ equalsTo(obj) {
350
+ return Leg.equalsTo(this, obj);
351
+ }
352
+
353
+ /**
354
+ * @returns {object}
355
+ */
356
+ toJson() {
357
+ const output = {
358
+ mode: this.mode,
359
+ from: { coords: this.from.coords.toCompressedJson() },
360
+ to: { coords: this.to.coords.toCompressedJson() },
361
+ distance: this.distance,
362
+ duration: this.duration,
363
+ coords: this.coords.map(coords => coords.toCompressedJson())
364
+ };
365
+ if (this.startTime !== null) {
366
+ output.startTime = this.startTime;
367
+ }
368
+ if (this.endTime !== null) {
369
+ output.endTime = this.endTime;
370
+ }
371
+ if (this.from.name !== null) {
372
+ output.from.name = this.from.name;
373
+ }
374
+ if (this.to.name !== null) {
375
+ output.to.name = this.to.name;
376
+ }
377
+ if (this.transportInfo !== null) {
378
+ output.transportInfo = this.transportInfo;
379
+ }
380
+ if (this.steps !== null && this.steps.length > 0) {
381
+ output.steps = this.steps.map(step => step.toJson());
382
+ }
383
+ return output;
384
+ }
385
+
386
+
387
+ /**
388
+ * @param {object} json
389
+ * @returns {Leg}
390
+ */
391
+ static fromJson(json) {
392
+ const leg = new Leg();
393
+ leg.mode = json.mode;
394
+ leg.distance = json.distance;
395
+ leg.duration = json.duration;
396
+
397
+ if (typeof json.startTime === 'number') {
398
+ leg.startTime = json.startTime;
399
+ }
400
+ if (typeof json.endTime === 'number') {
401
+ leg.endTime = json.endTime;
402
+ }
403
+
404
+ leg.from = {
405
+ coords: Coordinates.fromCompressedJson(json.from.coords),
406
+ name: typeof json.from.name === 'string' ? json.from.name : null
407
+ };
408
+ leg.to = {
409
+ coords: Coordinates.fromCompressedJson(json.to.coords),
410
+ name: typeof json.to.name === 'string' ? json.to.name : null
411
+ };
412
+
413
+ leg.coords = json.coords.map(Coordinates.fromCompressedJson);
414
+
415
+ if (typeof json.transportInfo === 'object') {
416
+ leg.transportInfo = json.transportInfo;
417
+ }
418
+ if (typeof json.steps === 'object') {
419
+ leg.steps = json.steps.map(Step.fromJson);
420
+ }
421
+ return leg;
422
+ }
423
+
424
+ }
425
+
426
+ /**
427
+ * Get route duration
428
+ * @param {Number} speed in km/h
429
+ * @returns {Number} duration in seconds
430
+ */
431
+ function getDurationFromLength(length, speed = 5) {
432
+ return length / (speed * 1000 / 3600);
433
+ }
434
+
435
+ /* eslint-disable max-statements */
436
+
437
+ /**
438
+ * Main attributes are:
439
+ * nodes: the ordered list of Node
440
+ * edges: the ordered list of Edge
441
+ * start: the start point (Coordinates)
442
+ * end: the end point (Coordinates)
443
+ * length: the route length
444
+ */
445
+ class Itinerary {
446
+
447
+ /** @type {!Coordinates} */
448
+ from;
449
+
450
+ /** @type {!Coordinates} */
451
+ to;
452
+
453
+ /** @type {!number} */
454
+ distance;
455
+
456
+ /** @type {!number} */
457
+ duration;
458
+
459
+ /** @type {!string} can be WALK, BIKE, CAR, PT */
460
+ _mode;
461
+
462
+ /** @type {?number} */
463
+ startTime = null;
464
+
465
+ /** @type {?number} */
466
+ endTime = null;
467
+
468
+ /** @type {!(Leg[])} */
469
+ legs = [];
470
+
471
+ /** @type {?Coordinates[]} */
472
+ _coords = null;
473
+
474
+ set coords(_) {
475
+ throw new Error('Itinerary.coords cannot be set. They are calculated from Itinerary.legs.');
476
+ }
477
+
478
+ /** @type {!(Coordinates[])} */
479
+ get coords() {
480
+ if (!this._coords) {
481
+ // Returns the coordinates contained in all legs and remove duplicates between array
482
+ this._coords = this.legs.reduce((acc, val) => {
483
+ const isDuplicate = acc.length && val.coords.length && acc[acc.length - 1].equalsTo(val.coords[0]);
484
+ acc.push(...val.coords.slice(isDuplicate ? 1 : 0));
485
+ return acc;
486
+ }, []);
487
+ }
488
+ return this._coords;
489
+ }
490
+
491
+ set steps(_) {
492
+ throw new Error('Itinerary.step cannot be set. They are calculated from Itinerary.legs.');
493
+ }
494
+
495
+ /** @type {!(Step[])} */
496
+ get steps() {
497
+ return this.legs.map(leg => leg.steps).flat();
498
+ }
499
+
500
+ set mode(_) {
501
+ throw new Error('Itinerary.mode cannot be set. They are calculated from Itinerary.legs.');
502
+ }
503
+
504
+ get mode() {
505
+ if (!this._mode) {
506
+ let isPublicTransport = false;
507
+ let isBicycle = false;
508
+ let isDriving = false;
509
+
510
+ this.legs.forEach((leg) => {
511
+ isPublicTransport = isPublicTransport || Constants.PUBLIC_TRANSPORT.includes(leg.mode);
512
+ isBicycle = isBicycle || leg.mode === Constants.ROUTING_MODE.BIKE;
513
+ isDriving = isDriving || leg.mode === Constants.ROUTING_MODE.CAR;
514
+ });
515
+
516
+ if (isPublicTransport) {
517
+ this._mode = 'PT';
518
+ } else if (isDriving) {
519
+ this._mode = 'CAR';
520
+ } else if (isBicycle) {
521
+ this._mode = 'BIKE';
522
+ } else {
523
+ this._mode = 'WALK';
524
+ }
525
+ }
526
+
527
+ return this._mode;
528
+
529
+ }
530
+
531
+ /**
532
+ * @returns {Network}
533
+ */
534
+ toNetwork() {
535
+ return Network.fromCoordinates([this.coords]);
536
+ }
537
+
538
+ /**
539
+ * @param {Itinerary[]} itineraries
540
+ * @returns {Itinerary}
541
+ */
542
+ static fromItineraries(...itineraries) {
543
+ const itinerary = new Itinerary();
544
+ itinerary.from = itineraries[0].from;
545
+ itinerary.to = itineraries[itineraries.length - 1].to;
546
+ itinerary.distance = 0;
547
+ itinerary.duration = 0;
548
+ itinerary.legs = [];
549
+
550
+ itineraries.forEach(_itinerary => {
551
+ itinerary.distance += _itinerary.distance;
552
+ itinerary.duration += _itinerary.duration;
553
+ itinerary.legs.push(..._itinerary.legs);
554
+ itinerary.legs.forEach(leg => {
555
+ leg.steps[0].firstStep = false;
556
+ leg.steps[leg.steps.length - 1].lastStep = false;
557
+ });
558
+ });
559
+
560
+ itinerary.legs[0].steps[0].firstStep = true;
561
+ const lastLeg = itinerary.legs[itinerary.legs.length - 1];
562
+ lastLeg.steps[lastLeg.steps.length - 1].lastStep = true;
563
+
564
+ return itinerary;
565
+ }
566
+
567
+ /**
568
+ * Convert lat/lng/level points to Itinerary
569
+ * @param {number[][]} points 2D points array of lat/lng/level (level is optional)
570
+ * @param {Coordinates} from
571
+ * @param {Coordinates} to
572
+ * @param {string} mode
573
+ * @returns {Itinerary}
574
+ */
575
+ static fromOrderedPointsArray(points, start, end) {
576
+
577
+ const pointToCoordinates = point => new Coordinates(point[0], point[1], null, point[2]);
578
+
579
+ return this.fromOrderedCoordinates(
580
+ points.map(pointToCoordinates),
581
+ pointToCoordinates(start),
582
+ pointToCoordinates(end)
583
+ );
584
+ }
585
+
586
+ /**
587
+ * Convert ordered Coordinates to Itinerary
588
+ * @param {Coordinates[]} points
589
+ * @param {Coordinates} from
590
+ * @param {Coordinates} to
591
+ * @param {string} mode
592
+ * @returns {Itinerary}
593
+ */
594
+ static fromOrderedCoordinates(points, from, to, mode = 'WALK') {
595
+
596
+ const itinerary = new Itinerary();
597
+ itinerary.from = from;
598
+ itinerary.to = to;
599
+
600
+ const leg = new Leg();
601
+ leg.mode = mode;
602
+ leg.from = { name: null, coords: from };
603
+ leg.to = { name: null, coords: to };
604
+
605
+ leg.coords = points;
606
+ leg.distance = points.reduce((acc, coords, idx, arr) => {
607
+ if (idx !== 0) {
608
+ return acc + arr[idx - 1].distanceTo(coords);
609
+ }
610
+ return acc;
611
+ }, 0);
612
+ leg.duration = getDurationFromLength(leg.distance);
613
+ itinerary.legs.push(leg);
614
+
615
+ itinerary.distance = leg.distance;
616
+ itinerary.duration = leg.duration;
617
+
618
+ return itinerary;
619
+ }
620
+
621
+
622
+ /**
623
+ * @param {Itinerary} obj1
624
+ * @param {Itinerary} obj2
625
+ * @returns {Boolean}
626
+ */
627
+ static equalsTo(obj1, obj2) {
628
+ const intermediate = obj1.from.equalsTo(obj2.from)
629
+ && obj1.to.equalsTo(obj2.to)
630
+ && obj1.distance === obj2.distance
631
+ && obj1.duration === obj2.duration
632
+ && obj1.startTime === obj2.startTime
633
+ && obj1.endTime === obj2.endTime
634
+ && obj1.legs.length === obj2.legs.length;
635
+
636
+ if (!intermediate) {
637
+ return false;
638
+ }
639
+
640
+ for (let i = 0; i < obj1.legs.length; i++) {
641
+ if (!obj1.legs[i].equalsTo(obj2.legs[i])) {
642
+ return false;
643
+ }
644
+ }
645
+
646
+ return true;
647
+ }
648
+
649
+ /**
650
+ * @param {Itinerary} obj
651
+ * @returns {Boolean}
652
+ */
653
+ equalsTo(obj) {
654
+ return Itinerary.equalsTo(this, obj);
655
+ }
656
+
657
+ /**
658
+ * @returns {object}
659
+ */
660
+ toJson() {
661
+ const output = {
662
+ from: this.from.toCompressedJson(),
663
+ to: this.to.toCompressedJson(),
664
+ distance: this.distance,
665
+ duration: this.duration,
666
+ mode: this.mode,
667
+ legs: this.legs.map(leg => leg.toJson())
668
+ };
669
+ if (this.startTime !== null) {
670
+ output.startTime = this.startTime;
671
+ }
672
+ if (this.endTime !== null) {
673
+ output.endTime = this.endTime;
674
+ }
675
+ return output;
676
+ }
677
+
678
+ /**
679
+ * @param {object} json
680
+ * @returns {Itinerary}
681
+ */
682
+ static fromJson(json) {
683
+ const itinerary = new Itinerary();
684
+ itinerary.from = Coordinates.fromCompressedJson(json.from);
685
+ itinerary.to = Coordinates.fromCompressedJson(json.to);
686
+ itinerary.distance = json.distance;
687
+ itinerary.duration = json.duration;
688
+ itinerary.legs = json.legs.map(Leg.fromJson);
689
+ if (typeof json.startTime === 'number') {
690
+ itinerary.startTime = json.startTime;
691
+ }
692
+ if (typeof json.endTime === 'number') {
693
+ itinerary.endTime = json.endTime;
694
+ }
695
+ return itinerary;
696
+ }
697
+ }
698
+
699
+ class RouterResponse {
700
+
701
+ /** @type {!string|Array<string>} */
702
+ routerName;
703
+
704
+ /** @type {!Coordinates} */
705
+ from;
706
+
707
+ /** @type {!Coordinates} */
708
+ to;
709
+
710
+ /** @type {!(Itinerary[])} */
711
+ itineraries = [];
712
+
713
+ /**
714
+ * @param {RouterResponse} obj1
715
+ * @param {RouterResponse} obj2
716
+ * @returns {Boolean}
717
+ */
718
+ static equalsTo(obj1, obj2) {
719
+ const intermediate = obj1.routerName === obj2.routerName
720
+ && obj1.from.equalsTo(obj2.from)
721
+ && obj1.to.equalsTo(obj2.to)
722
+ && obj1.itineraries.length === obj2.itineraries.length;
723
+
724
+ if (!intermediate) {
725
+ return false;
726
+ }
727
+
728
+ for (let i = 0; i < obj1.itineraries.length; i++) {
729
+ if (!obj1.itineraries[i].equalsTo(obj2.itineraries[i])) {
730
+ return false;
731
+ }
732
+ }
733
+
734
+ return true;
735
+ }
736
+
737
+ /**
738
+ * @param {RouterResponse} obj
739
+ * @returns {Boolean}
740
+ */
741
+ equalsTo(obj) {
742
+ return RouterResponse.equalsTo(this, obj);
743
+ }
744
+
745
+
746
+ /**
747
+ * @returns {object}
748
+ */
749
+ toJson() {
750
+ return {
751
+ routerName: this.routerName,
752
+ from: this.from.toCompressedJson(),
753
+ to: this.to.toCompressedJson(),
754
+ itineraries: this.itineraries.map(itinerary => itinerary.toJson())
755
+ };
756
+ }
757
+
758
+ /**
759
+ * @param {object} json
760
+ * @returns {RouterResponse}
761
+ */
762
+ static fromJson(json) {
763
+ const routerResponse = new RouterResponse();
764
+ routerResponse.routerName = json.routerName;
765
+ routerResponse.from = Coordinates.fromCompressedJson(json.from);
766
+ routerResponse.to = Coordinates.fromCompressedJson(json.to);
767
+ routerResponse.itineraries = json.itineraries.map(Itinerary.fromJson);
768
+ return routerResponse;
769
+ }
770
+ }
771
+
772
+ class ItineraryInfo {
773
+
774
+ /** @type {Step} */
775
+ nextStep;
776
+
777
+ /** @type {Step} */
778
+ previousStep;
779
+
780
+ /** @type {GraphProjection} */
781
+ projection;
782
+
783
+ /** @type {Leg} */
784
+ leg;
785
+
786
+ /** @type {number} */
787
+ traveledDistance;
788
+
789
+ /** @type {number} */
790
+ traveledPercentage;
791
+
792
+ /** @type {number} */
793
+ remainingDistance;
794
+
795
+ /** @type {number} */
796
+ remainingPercentage;
797
+
798
+ }
799
+
800
+ class WemapRouterOptions extends GraphRouterOptions {
801
+
802
+ /** @type {WemapRouterOptions} */
803
+ static DEFAULT = new WemapRouterOptions();
804
+
805
+ /**
806
+ * @returns {WemapRouterOptions}
807
+ */
808
+ static get WITHOUT_STAIRS() {
809
+ const options = new WemapRouterOptions();
810
+ options.acceptEdgeFn = edge => edge.builtFrom.tags.highway !== 'steps';
811
+ return options;
812
+ }
813
+
814
+ /**
815
+ * Get route duration
816
+ * @param {Number} speed in km/h
817
+ */
818
+ static getDurationFromLength(length, speed = 5) {
819
+ return length / (speed * 1000 / 3600);
820
+ }
821
+
822
+ /** @type {function(GraphEdge<OsmElement>):boolean} */
823
+ weightEdgeFn = edge => edge.builtFrom.isElevator ? 30 : WemapRouterOptions.getDurationFromLength(edge.length);
824
+
825
+
826
+ }
827
+
828
+ class WemapRouter extends GraphRouter {
829
+
830
+ /**
831
+ * @param {!Network<OsmElement>} network
832
+ */
833
+ constructor(network) {
834
+ super(network);
835
+ }
836
+
837
+
838
+ /** @type {string} */
839
+ static get rname() {
840
+ return 'wemap';
841
+ }
842
+
843
+ /**
844
+ * @param {Coordinates} start
845
+ * @param {Coordinates} end
846
+ * @param {WemapRouterOptions} options
847
+ * @returns {GraphItinerary<OsmElement>}
848
+ */
849
+ getShortestPath(start, end, options = new WemapRouterOptions()) {
850
+ return super.getShortestPath(start, end, options);
851
+ }
852
+
853
+ }
854
+
855
+ /* eslint-disable complexity */
856
+
857
+ const SKIP_STEP_ANGLE_MAX = deg2rad(20);
858
+
859
+ class WemapStepsGeneration {
860
+
861
+ /**
862
+ * @param {GraphItinerary<OsmElement>} itinerary
863
+ * @returns {Step[]}
864
+ */
865
+ static fromGraphItinerary(itinerary) {
866
+
867
+ const steps = [];
868
+
869
+ const { start, end, nodes, edges } = itinerary;
870
+
871
+ let currentStep, previousStep;
872
+ let previousBearing = start.bearingTo(nodes[0].coords);
873
+
874
+ for (let i = 0; i < nodes.length - 1; i++) {
875
+
876
+ const isFirstStep = !currentStep;
877
+
878
+ const node = nodes[i];
879
+ const nextNode = nodes[i + 1];
880
+ const edge = edges[i];
881
+
882
+ const bearing = edge.bearing;
883
+ const angle = diffAngle(previousBearing, bearing + Math.PI);
884
+
885
+ let splitByAngle = Math.abs(diffAngle(Math.PI, angle)) >= SKIP_STEP_ANGLE_MAX;
886
+
887
+ const splitByLevel = edge.level && edge.level.isRange
888
+ && node.coords.level && !node.coords.level.isRange;
889
+ splitByAngle = splitByAngle && !(node.coords.level && node.coords.level.isRange);
890
+
891
+ const splitStepCondition = splitByAngle || splitByLevel;
892
+
893
+ const isSubwayEntrance = node ? node.builtFrom.tags.railway === 'subway_entrance' : false;
894
+
895
+ // New step creation
896
+ if (isFirstStep || splitStepCondition || isSubwayEntrance) {
897
+
898
+ previousStep = currentStep;
899
+
900
+ currentStep = new Step();
901
+ currentStep.coords = node.coords;
902
+ currentStep.number = steps.length + 1;
903
+ currentStep.angle = angle;
904
+ currentStep.previousBearing = previousBearing;
905
+ currentStep.nextBearing = bearing;
906
+ currentStep.name = edge.builtFrom.tags.name || null;
907
+ currentStep.distance = 0;
908
+ currentStep.duration = 0;
909
+
910
+ if (isSubwayEntrance) {
911
+ currentStep.extras.subwayEntrance = true;
912
+ currentStep.name = node.builtFrom.tags.name;
913
+ if (node.builtFrom.tags.ref) {
914
+ currentStep.extras.subwayEntranceRef = node.builtFrom.tags.ref;
915
+ }
916
+ }
917
+
918
+ if (splitByLevel) {
919
+ currentStep.levelChange = WemapStepsGeneration.levelChangefromTwoNodes(node, nextNode);
920
+ }
921
+
922
+ steps.push(currentStep);
923
+
924
+ if (!previousStep) {
925
+ currentStep.firstStep = true;
926
+ }
927
+ }
928
+
929
+ currentStep.distance += edge.length;
930
+ currentStep.duration += itinerary.edgesWeights[i];
931
+ previousBearing = bearing;
932
+ }
933
+
934
+ const lastNode = nodes[nodes.length - 1];
935
+
936
+ // Create a last step if end is not on the network
937
+ if (!Coordinates.equalsTo(lastNode.coords, end)) {
938
+ const lastStep = new Step();
939
+ lastStep.coords = lastNode.coords;
940
+ lastStep.number = steps.length + 1;
941
+ lastStep.previousBearing = previousBearing;
942
+ lastStep.distance = lastNode.coords.distanceTo(end);
943
+ lastStep.nextBearing = lastNode.coords.bearingTo(end);
944
+ lastStep.angle = diffAngle(lastStep.previousBearing, lastStep.nextBearing + Math.PI);
945
+ steps.push(lastStep);
946
+ }
947
+
948
+ steps[steps.length - 1].lastStep = true;
949
+
950
+ return steps;
951
+ }
952
+
953
+ /**
954
+ * @param {GraphNode<OsmElement>} firstNode
955
+ * @param {GraphNode<OsmElement>} secondNode
956
+ * @returns {LevelChange}
957
+ */
958
+ static levelChangefromTwoNodes(firstNode, secondNode) {
959
+
960
+ const levelChange = new LevelChange();
961
+
962
+ const edge = GraphUtils.getEdgeByNodes(firstNode.edges, firstNode, secondNode);
963
+
964
+ if (edge.builtFrom.isElevator) {
965
+ levelChange.type = 'elevator';
966
+ } else if (edge.builtFrom.isConveying) {
967
+ levelChange.type = 'conveyor';
968
+ } else if (edge.builtFrom.areStairs) {
969
+ levelChange.type = 'stairs';
970
+ }
971
+
972
+ levelChange.difference = Level.diff(firstNode.coords.level, secondNode.coords.level);
973
+ levelChange.direction = levelChange.difference > 0 ? 'up' : 'down';
974
+
975
+ return levelChange;
976
+ }
977
+ }
978
+
979
+ /**
980
+ * @param {GraphItinerary<OsmElement>} graphItinerary
981
+ * @param {string} mode
982
+ * @returns {Leg}
983
+ */
984
+ function createLegFromGraphItinerary(graphItinerary,
985
+ mode = Constants.ROUTING_MODE.WALK) {
986
+
987
+ const leg = new Leg();
988
+
989
+ leg.from = { coords: graphItinerary.start, name: null };
990
+ leg.to = { coords: graphItinerary.end, name: null };
991
+ leg.coords = graphItinerary.nodes.map(node => node.coords);
992
+ leg.distance = graphItinerary.edges.reduce((acc, edge) => acc + edge.length, 0);
993
+ leg.duration = graphItinerary.edgesWeights.reduce((acc, weight) => acc + weight, 0);
994
+ leg.mode = mode;
995
+ leg.steps = WemapStepsGeneration.fromGraphItinerary(graphItinerary);
996
+ leg.steps.forEach(step => {
997
+ step._idCoordsInLeg = leg.coords.findIndex(coords => coords === step.coords);
998
+ });
999
+
1000
+ return leg;
1001
+ }
1002
+
1003
+ /**
1004
+ * @param {GraphItinerary<OsmElement>} graphItinerary
1005
+ * @param {string} mode
1006
+ * @returns {Itinerary}
1007
+ */
1008
+ function createItineraryFromGraphItinerary(graphItinerary,
1009
+ mode = Constants.ROUTING_MODE.WALK) {
1010
+
1011
+ const leg = createLegFromGraphItinerary(graphItinerary, mode);
1012
+
1013
+ const itinerary = new Itinerary();
1014
+
1015
+ itinerary.from = graphItinerary.start;
1016
+ itinerary.to = graphItinerary.end;
1017
+ itinerary.distance = leg.distance;
1018
+ itinerary.duration = leg.duration;
1019
+ itinerary.legs.push(leg);
1020
+
1021
+ return itinerary;
1022
+ }
1023
+
1024
+ var WemapRouterUtils = /*#__PURE__*/Object.freeze({
1025
+ __proto__: null,
1026
+ createLegFromGraphItinerary: createLegFromGraphItinerary,
1027
+ createItineraryFromGraphItinerary: createItineraryFromGraphItinerary
1028
+ });
1029
+
1030
+ const HIGHWAYS_PEDESTRIANS = ['footway', 'steps', 'pedestrian', 'living_street', 'path', 'track', 'sidewalk'];
1031
+
1032
+ const DEFAULT_WAY_SELECTOR = way => {
1033
+ return HIGHWAYS_PEDESTRIANS.includes(way.tags.highway)
1034
+ || way.tags.footway === 'sidewalk'
1035
+ || way.tags.public_transport === 'platform'
1036
+ || way.tags.railway === 'platform';
1037
+ };
1038
+
1039
+ /**
1040
+ * @param {Network<OsmElement>} network
1041
+ * @param {string} name
1042
+ */
1043
+ function getNodeByName(network, name) {
1044
+ return network.nodes.find(({ builtFrom }) => builtFrom.tags.name === name);
1045
+ }
1046
+
1047
+ /**
1048
+ * @param {Network<OsmElement>} network
1049
+ * @param {string} name
1050
+ */
1051
+ function getEdgeByName(network, name) {
1052
+ return network.edges.find(({ builtFrom }) => builtFrom.tags.name === name);
1053
+ }
1054
+
1055
+ /**
1056
+ * @param {GraphEdge<OsmElement>} edge
1057
+ * @param {OsmWay} way
1058
+ * @returns {boolean}
1059
+ */
1060
+ function manageOneWay(edge, way) {
1061
+
1062
+ const { highway, oneway, conveying } = way.tags;
1063
+
1064
+ edge.isOneway = Boolean((oneway === 'yes' || oneway === 'true' || oneway === '1')
1065
+ || (conveying && highway && ['yes', 'forward', 'backward'].includes(conveying)));
1066
+
1067
+ if (edge.isOneway && conveying === 'backward') {
1068
+ const tmpNode = edge.node1;
1069
+ edge.node1 = edge.node2;
1070
+ edge.node2 = tmpNode;
1071
+ }
1072
+ }
1073
+
1074
+ /**
1075
+ * @param {Network} networkModel
1076
+ * @param {GraphNode} node
1077
+ */
1078
+ function createNodesAndEdgesFromElevator(networkModel, node) {
1079
+
1080
+ /** @type {GraphNode[]} */
1081
+ const createdNodes = [];
1082
+ const getOrCreateLevelNode = (level, builtFrom) => {
1083
+ let levelNode = createdNodes.find(({ coords }) => Level.equalsTo(level, coords.level));
1084
+ if (!levelNode) {
1085
+ levelNode = new GraphNode(node.coords.clone(), builtFrom);
1086
+ levelNode.coords.level = level;
1087
+ createdNodes.push(levelNode);
1088
+ networkModel.nodes.push(levelNode);
1089
+ }
1090
+ return levelNode;
1091
+ };
1092
+
1093
+ // Create nodes from node.edges
1094
+ node.edges.forEach(edge => {
1095
+ if (edge.level.isRange) {
1096
+ throw new Error('Cannot handle this elevator edge due to ambiguity');
1097
+ }
1098
+
1099
+ const levelNode = getOrCreateLevelNode(edge.level, node.builtFrom);
1100
+ if (edge.node1 === node) {
1101
+ edge.node1 = levelNode;
1102
+ } else {
1103
+ edge.node2 = levelNode;
1104
+ }
1105
+ levelNode.edges.push(edge);
1106
+ });
1107
+
1108
+ // Create edges from createdNodes
1109
+ for (let i = 0; i < createdNodes.length; i++) {
1110
+ for (let j = i + 1; j < createdNodes.length; j++) {
1111
+
1112
+ const createdNode1 = createdNodes[i];
1113
+ const createdNode2 = createdNodes[j];
1114
+
1115
+ const newEdge = new GraphEdge(
1116
+ createdNode1,
1117
+ createdNode2,
1118
+ new Level(createdNode1.coords.level.val, createdNode2.coords.level.val),
1119
+ node.builtFrom
1120
+ );
1121
+ networkModel.edges.push(newEdge);
1122
+ }
1123
+ }
1124
+
1125
+ // Remove the historical elevator node from the network
1126
+ networkModel.nodes = networkModel.nodes.filter(_node => _node !== node);
1127
+ }
1128
+
1129
+
1130
+ /**
1131
+ * @param {OsmModel} osmModel
1132
+ * @param {function} _waySelectionFilter
1133
+ * @returns {Network<OsmElement>}
1134
+ */
1135
+ function createNetworkFromOsmModel(
1136
+ osmModel,
1137
+ waySelectionFilter = DEFAULT_WAY_SELECTOR
1138
+ ) {
1139
+
1140
+ const networkModel = new Network();
1141
+
1142
+ const nodesCreated = {};
1143
+ const elevatorNodes = [];
1144
+
1145
+ /**
1146
+ * @param {OsmNode} osmNode
1147
+ */
1148
+ const getOrCreateNode = osmNode => {
1149
+ let node = nodesCreated[osmNode.id];
1150
+ if (!node) {
1151
+ node = new GraphNode(osmNode.coords, osmNode);
1152
+ nodesCreated[osmNode.id] = node;
1153
+ networkModel.nodes.push(node);
1154
+
1155
+ if (osmNode.tags.highway === 'elevator') {
1156
+ elevatorNodes.push(node);
1157
+ }
1158
+ }
1159
+ return node;
1160
+ };
1161
+
1162
+ osmModel.ways.forEach(way => {
1163
+ if (!waySelectionFilter(way)) {
1164
+ return;
1165
+ }
1166
+
1167
+ let firstNode = getOrCreateNode(way.nodes[0]);
1168
+ for (let i = 1; i < way.nodes.length; i++) {
1169
+ const secondNode = getOrCreateNode(way.nodes[i]);
1170
+
1171
+ const edge = new GraphEdge(firstNode, secondNode, way.level, way);
1172
+ manageOneWay(edge, way);
1173
+ networkModel.edges.push(edge);
1174
+ firstNode = secondNode;
1175
+ }
1176
+
1177
+ });
1178
+
1179
+ elevatorNodes.forEach(node => {
1180
+ // We have to clone this node for each connected edge
1181
+ createNodesAndEdgesFromElevator(networkModel, node);
1182
+ });
1183
+
1184
+ GraphNode.generateNodesLevels(networkModel.nodes);
1185
+
1186
+ return networkModel;
1187
+ }
1188
+
1189
+
1190
+ // /**
1191
+ // * @param {GraphNode} node
1192
+ // * @param {object} tags
1193
+ // */
1194
+ // static _applyNodePropertiesFromTags(node, tags) {
1195
+ // node.name = tags.name || null;
1196
+ // node.subwayEntrance = tags.railway === 'subway_entrance';
1197
+ // if (node.subwayEntrance && tags.ref) {
1198
+ // node.subwayEntranceRef = tags.ref;
1199
+ // }
1200
+ // }
1201
+
1202
+ // /**
1203
+ // * @param {GraphEdge} edge
1204
+ // * @param {object} tags
1205
+ // */
1206
+ // static _applyEdgePropertiesFromTags(edge, tags) {
1207
+ // const { highway, oneway, conveying, name } = tags;
1208
+ // edge.name = name || null;
1209
+ // edge.isStairs = highway === 'steps';
1210
+ // edge.isConveying = 'conveying' in tags;
1211
+ // edge.isOneway = Boolean((oneway === 'yes' || oneway === 'true' || oneway === '1')
1212
+ // || (conveying && highway && ['yes', 'forward', 'backward'].includes(conveying)));
1213
+
1214
+ // if (conveying === 'backward') {
1215
+ // const tmpNode = edge.node1;
1216
+ // edge.node1 = edge.node2;
1217
+ // edge.node2 = tmpNode;
1218
+ // }
1219
+
1220
+ // }
1221
+
1222
+ var WemapNetworkUtils = /*#__PURE__*/Object.freeze({
1223
+ __proto__: null,
1224
+ HIGHWAYS_PEDESTRIANS: HIGHWAYS_PEDESTRIANS,
1225
+ DEFAULT_WAY_SELECTOR: DEFAULT_WAY_SELECTOR,
1226
+ getNodeByName: getNodeByName,
1227
+ getEdgeByName: getEdgeByName,
1228
+ createNetworkFromOsmModel: createNetworkFromOsmModel
1229
+ });
1230
+
1231
+ /**
1232
+ * @param {Itinerary} itinerary
1233
+ */
1234
+ function generateStepsMetadata(itinerary) {
1235
+
1236
+ let counter = 1;
1237
+
1238
+ itinerary.legs.forEach((leg, legId) => {
1239
+ leg.steps.forEach((step, stepId) => {
1240
+
1241
+ if (counter === 1) {
1242
+ step.firstStep = true;
1243
+ }
1244
+ if (legId === itinerary.legs.length - 1
1245
+ && stepId === leg.steps.length - 1) {
1246
+ step.lastStep = true;
1247
+ }
1248
+
1249
+ step.number = counter++;
1250
+
1251
+
1252
+ /*
1253
+ * Generate previousBearing, nextBearing and angle
1254
+ */
1255
+
1256
+ let coordsBeforeStep;
1257
+ if (step._idCoordsInLeg > 0) {
1258
+ coordsBeforeStep = leg.coords[step._idCoordsInLeg - 1];
1259
+ } else if (legId === 0) {
1260
+ coordsBeforeStep = itinerary.from;
1261
+ } else {
1262
+ coordsBeforeStep = itinerary.legs[legId - 1].to.coords;
1263
+ }
1264
+
1265
+ let coordsAfterStep;
1266
+ if (step._idCoordsInLeg !== leg.coords.length - 1) {
1267
+ coordsAfterStep = leg.coords[step._idCoordsInLeg + 1];
1268
+ } else if (legId === itinerary.legs.length - 1) {
1269
+ coordsAfterStep = itinerary.to;
1270
+ } else {
1271
+ coordsAfterStep = itinerary.legs[legId + 1].from.coords;
1272
+ }
1273
+
1274
+ step.previousBearing = coordsBeforeStep.bearingTo(step.coords);
1275
+ step.nextBearing = step.coords.bearingTo(coordsAfterStep);
1276
+ step.angle = diffAngle(step.previousBearing, step.nextBearing + Math.PI);
1277
+
1278
+ });
1279
+ });
1280
+ }
1281
+
1282
+
1283
+ /**
1284
+ * Create and return a date with a given timezone
1285
+ * @param {number} year
1286
+ * @param {number} month
1287
+ * @param {number} day
1288
+ * @param {number} hours
1289
+ * @param {number} minutes
1290
+ * @param {number} seconds
1291
+ * @param {string} timeZone - timezone name (e.g. 'Europe/Paris')
1292
+ */
1293
+ function dateWithTimeZone(year, month, day, hour, minute, second, timeZone = 'Europe/Paris') {
1294
+ const date = new Date(Date.UTC(year, month, day, hour, minute, second));
1295
+
1296
+ const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
1297
+ const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timeZone }));
1298
+ const offset = utcDate.getTime() - tzDate.getTime();
1299
+
1300
+ date.setTime(date.getTime() + offset);
1301
+
1302
+ return date;
1303
+ }
1304
+
1305
+ const stringify = (data) => {
1306
+ return Object.keys(data).map(key => {
1307
+ return `${key}=${data[key]}`;
1308
+ });
1309
+ };
1310
+
1311
+
1312
+ const request = async (url, options = {}) => {
1313
+ const responseType = options.responseType || 'json';
1314
+
1315
+ const fetchOptions = {
1316
+ method: options.method || 'GET',
1317
+ headers: {}
1318
+ // headers: new Headers(options.headers || {})
1319
+ };
1320
+
1321
+ if (options.hasOwnProperty('json')) {
1322
+ fetchOptions.headers.append('Content-Type', 'application/json');
1323
+ fetchOptions.body = JSON.stringify(options.json);
1324
+ } else if (options.hasOwnProperty('data')) {
1325
+ fetchOptions.headers.append('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
1326
+ fetchOptions.body = options.data instanceof URLSearchParams ? options.data : stringify(options.data);
1327
+ }
1328
+
1329
+ return fetch(url, fetchOptions).then(response => {
1330
+ if (responseType === 'arraybuffer') {
1331
+ Logger.ok('Request succeeded for: ' + url);
1332
+ return response.arrayBuffer();
1333
+ } else if (responseType === 'json') {
1334
+ return response.json().then((json) => {
1335
+ Logger.ok('Request succeeded for: ' + url);
1336
+ return json;
1337
+ }).catch(() => {
1338
+ Logger.error('Request failed for: ' + url);
1339
+ throw Error('Request failed for: ' + url);
1340
+ });
1341
+ }
1342
+
1343
+ return null;
1344
+ });
1345
+ };
1346
+
1347
+ class RemoteRouterOptions {
1348
+
1349
+ /** @type {boolean} */
1350
+ useStairs = true;
1351
+
1352
+ }
1353
+
1354
+ class RemoteRouter {
1355
+
1356
+ /**
1357
+ * Get the router name
1358
+ * @type {string} the router name
1359
+ */
1360
+ get rname() {
1361
+ return 'Unknown';
1362
+ }
1363
+
1364
+ /**
1365
+ * @abstract
1366
+ * @param {string} endpointUrl
1367
+ * @param {string} mode see Constants.ITINERARY_MODE
1368
+ * @param {Array<Coordinates>} waypoints
1369
+ * @param {RemoteRouterOptions} options
1370
+ * @returns {!RouterResponse}
1371
+ */
1372
+ // eslint-disable-next-line no-unused-vars
1373
+ async getItineraries(endpointUrl, mode, waypoints, options = new RemoteRouterOptions()) {
1374
+ throw new Error('RemoteRouter "' + this.rname + '" does not @override getItineraries()');
1375
+ }
1376
+
1377
+ }
1378
+
1379
+ class OsrmRoutingModeCorrespondanceNotFound extends Error {
1380
+
1381
+ /**
1382
+ * @param {!string} routingMode
1383
+ */
1384
+ constructor(routingMode) {
1385
+ super(`OSRM routing mode correspondance not found: ${routingMode}`);
1386
+ }
1387
+ }
1388
+
1389
+ /* eslint-disable max-statements */
1390
+
1391
+ /**
1392
+ * Input mode correspondance
1393
+ */
1394
+ const inputModeCorrespondance$2 = new Map();
1395
+ inputModeCorrespondance$2.set(Constants.ROUTING_MODE.CAR, 'driving');
1396
+ inputModeCorrespondance$2.set(Constants.ROUTING_MODE.WALK, 'walking');
1397
+ inputModeCorrespondance$2.set(Constants.ROUTING_MODE.BIKE, 'bike');
1398
+ inputModeCorrespondance$2.set(Constants.ROUTING_MODE.BUS, 'bus');
1399
+ inputModeCorrespondance$2.set(Constants.ROUTING_MODE.MULTI, 'walking');
1400
+
1401
+
1402
+ /**
1403
+ * Singleton.
1404
+ */
1405
+ class OsrmRemoteRouter extends RemoteRouter {
1406
+
1407
+ /**
1408
+ * @override
1409
+ */
1410
+ get rname() {
1411
+ return 'osrm';
1412
+ }
1413
+
1414
+ /**
1415
+ * @override
1416
+ */
1417
+ async getItineraries(endpointUrl, mode, waypoints) {
1418
+ const url = this.getURL(endpointUrl, mode, waypoints);
1419
+ const response = await request(url);
1420
+ return this.createRouterResponseFromJson(response, waypoints[0], waypoints[1]);
1421
+ }
1422
+
1423
+ /**
1424
+ * @param {string} endpointUrl
1425
+ * @param {string} mode
1426
+ * @param {Array<Coordinates>} waypoints
1427
+ */
1428
+ getURL(endpointUrl, mode, waypoints) {
1429
+
1430
+ const osrmMode = inputModeCorrespondance$2.get(mode);
1431
+ if (!osrmMode) {
1432
+ throw new OsrmRoutingModeCorrespondanceNotFound(mode);
1433
+ }
1434
+
1435
+ let url = endpointUrl + '/route/v1/' + osrmMode + '/';
1436
+ url += waypoints.map(waypoint => [waypoint.longitude + ',' + waypoint.latitude]).join(';');
1437
+ url += '?geometries=geojson&overview=full&steps=true';
1438
+
1439
+ return url;
1440
+ }
1441
+
1442
+ /**
1443
+ * @param {Coordinates} coordinates
1444
+ * @returns {object}
1445
+ */
1446
+ coordinatesToJson(coordinates) {
1447
+ const output = [coordinates.lng, coordinates.lat];
1448
+ if (coordinates.level) {
1449
+ output.push(coordinates.level.toString());
1450
+ }
1451
+ return output;
1452
+ }
1453
+
1454
+ /**
1455
+ * @param {object} json
1456
+ * @returns {Coordinates}
1457
+ */
1458
+ jsonToCoordinates(json) {
1459
+ const output = new Coordinates(json[1], json[0]);
1460
+ if (json.length > 2) {
1461
+ output.level = Level.fromString(json[2]);
1462
+ }
1463
+ return output;
1464
+ }
1465
+
1466
+ nodesToJsonCoords(nodes) {
1467
+ return nodes.map(node => this.coordinatesToJson(node.coords));
1468
+ }
1469
+
1470
+
1471
+ getModifierFromAngle(_angle) {
1472
+
1473
+ const angle = positiveMod(rad2deg(_angle), 360);
1474
+
1475
+ if (angle > 0 && angle < 60) {
1476
+ return 'sharp right';
1477
+ }
1478
+ if (angle >= 60 && angle < 140) {
1479
+ return 'right';
1480
+ }
1481
+ if (angle >= 140 && angle < 160) {
1482
+ return 'slight right';
1483
+ }
1484
+ if (angle >= 160 && angle <= 200) {
1485
+ return 'straight';
1486
+ }
1487
+ if (angle > 200 && angle <= 220) {
1488
+ return 'slight left';
1489
+ }
1490
+ if (angle > 220 && angle <= 300) {
1491
+ return 'left';
1492
+ }
1493
+ if (angle > 300 && angle < 360) {
1494
+ return 'sharp left';
1495
+ }
1496
+ return 'u turn';
1497
+ }
1498
+
1499
+
1500
+ noRouteFoundJson(message) {
1501
+ return {
1502
+ 'code': 'NoRoute',
1503
+ message
1504
+ };
1505
+ }
1506
+
1507
+ /**
1508
+ * @param {Itinerary} itinerary
1509
+ * @returns {object}
1510
+ */
1511
+ itineraryToOsrmJson(itinerary) {
1512
+
1513
+ const lastLegId = itinerary.legs.length - 1;
1514
+
1515
+ const jsonLegs = itinerary.legs.map(({ distance, duration, coords, steps }, idLeg) => {
1516
+
1517
+ const lastStepId = steps.length - 1;
1518
+
1519
+ return {
1520
+ distance,
1521
+ duration,
1522
+ steps: steps.map((step, idStep, arr) => {
1523
+
1524
+ let type = idStep === 0 && idLeg === 0 ? 'depart' : 'turn';
1525
+ type = idStep === lastStepId && idLeg === lastLegId ? 'arrive' : type;
1526
+
1527
+ const stepCoordsIdx = coords.findIndex(p => p.equalsTo(step.coords));
1528
+ const nextStepCoordsIdx = idStep === lastStepId
1529
+ ? stepCoordsIdx
1530
+ : coords.findIndex(p => p.equalsTo(arr[idStep + 1].coords));
1531
+
1532
+ const jsonStep = {
1533
+ geometry: {
1534
+ type: 'LineString',
1535
+ coordinates: coords.slice(stepCoordsIdx, nextStepCoordsIdx + 1).map(this.coordinatesToJson)
1536
+ },
1537
+ distance: step.distance,
1538
+ duration: step.duration,
1539
+ name: step.name,
1540
+ maneuver: {
1541
+ bearing_before: rad2deg(step.previousBearing),
1542
+ bearing_after: rad2deg(step.nextBearing),
1543
+ location: this.coordinatesToJson(step.coords),
1544
+ modifier: this.getModifierFromAngle(step.angle),
1545
+ type
1546
+ }
1547
+ };
1548
+ if (step.levelChange !== null) {
1549
+ jsonStep.levelChange = step.levelChange.toJson();
1550
+ }
1551
+ if (typeof step.extras === 'object' && Object.keys(step.extras).length !== 0) {
1552
+ jsonStep.extras = step.extras;
1553
+ }
1554
+
1555
+ return jsonStep;
1556
+ })
1557
+ };
1558
+ });
1559
+
1560
+ return {
1561
+ 'code': 'Ok',
1562
+ 'routes': [
1563
+ {
1564
+ 'geometry': {
1565
+ 'type': 'LineString',
1566
+ 'coordinates': itinerary.coords.map(this.coordinatesToJson)
1567
+ },
1568
+ 'legs': jsonLegs,
1569
+ 'distance': itinerary.distance,
1570
+ 'duration': itinerary.duration,
1571
+ 'weight_name': 'routability',
1572
+ 'weight': 0
1573
+ }
1574
+ ],
1575
+ 'waypoints': []
1576
+ };
1577
+ }
1578
+
1579
+ /**
1580
+ * @param {object} jsonSteps
1581
+ * @param {Coordinates[]} legCoords
1582
+ * @returns {Step[]}
1583
+ */
1584
+ parseJsonSteps(jsonSteps, legCoords) {
1585
+
1586
+ if (!jsonSteps) {
1587
+ return [];
1588
+ }
1589
+
1590
+ return jsonSteps.map(jsonStep => {
1591
+
1592
+ const step = new Step();
1593
+ step.coords = this.jsonToCoordinates(jsonStep.maneuver.location);
1594
+
1595
+ // Sometimes, OSRM step does not have the same coordinates than a point in legCoords.
1596
+ // ex: first step of https://routing.getwemap.com/route/v1/walking/2.33222164147,48.87084765712;2.3320734,48.8730212?geometries=geojson&overview=full&steps=true
1597
+ // That is why we look for the closest point.
1598
+ const distances = legCoords.map(coords => coords.distanceTo(step.coords));
1599
+ const idStepCoordsInLeg = distances.indexOf(Math.min(...distances));
1600
+ if (idStepCoordsInLeg < 0) {
1601
+ throw new Error('Osrm Parser: Cannot find step coords in leg coordinates');
1602
+ }
1603
+ step._idCoordsInLeg = idStepCoordsInLeg;
1604
+
1605
+ step.name = jsonStep.name;
1606
+ step.levelChange = jsonStep.levelChange ? LevelChange.fromJson(jsonStep.levelChange) : null;
1607
+
1608
+ step.distance = jsonStep.distance;
1609
+ step.duration = jsonStep.duration;
1610
+
1611
+ if (jsonStep.extras && jsonStep.extras.subwayEntrance) {
1612
+ step.extras.subwayEntrance = true;
1613
+ if (jsonStep.extras.subwayEntranceRef) {
1614
+ step.extras.subwayEntranceRef = jsonStep.extras.subwayEntranceRef;
1615
+ }
1616
+ }
1617
+
1618
+ return step;
1619
+ });
1620
+ }
1621
+
1622
+ /**
1623
+ * Generate multi itineraries from OSRM JSON
1624
+ * @param {object} json JSON file provided by OSRM.
1625
+ * @param {Coordinates} from itinerary start
1626
+ * @param {Coordinates} to itinerary end
1627
+ * @param {?string} routingMode [walking|driving|bicycle]
1628
+ * @returns {?RouterResponse}
1629
+ */
1630
+ createRouterResponseFromJson(json, from, to, routingMode = 'walking') {
1631
+ const { routes: jsonRoutes } = json;
1632
+
1633
+ if (!jsonRoutes) {
1634
+ return null;
1635
+ }
1636
+
1637
+ const routingModeCorrespondance = new Map();
1638
+ routingModeCorrespondance.set('walking', 'WALK');
1639
+ routingModeCorrespondance.set('driving', 'CAR');
1640
+ routingModeCorrespondance.set('bicycle', 'BIKE');
1641
+ const mode = routingModeCorrespondance.get(routingMode) || null;
1642
+
1643
+ const routerResponse = new RouterResponse();
1644
+ routerResponse.routerName = this.rname;
1645
+
1646
+ routerResponse.from = from;
1647
+ routerResponse.to = to;
1648
+
1649
+ for (const jsonItinerary of jsonRoutes) {
1650
+
1651
+ const itinerary = new Itinerary();
1652
+
1653
+ // itinerary.coords = jsonItinerary.geometry.coordinates.map(jsonToCoordinates);
1654
+ itinerary.distance = jsonItinerary.distance;
1655
+ itinerary.duration = jsonItinerary.duration;
1656
+ itinerary.from = from;
1657
+ itinerary.to = to;
1658
+
1659
+ routerResponse.itineraries.push(itinerary);
1660
+
1661
+ for (const jsonLeg of jsonItinerary.legs) {
1662
+
1663
+ const leg = new Leg();
1664
+
1665
+ leg.mode = mode;
1666
+ leg.distance = jsonLeg.distance;
1667
+ leg.duration = jsonLeg.duration;
1668
+
1669
+ leg.coords = jsonLeg.steps
1670
+ .map(step => step.geometry.coordinates.map(this.jsonToCoordinates))
1671
+ .flat()
1672
+ // Remove duplicates
1673
+ .filter((coords, idx, arr) => idx === 0 || !arr[idx - 1].equalsTo(coords));
1674
+
1675
+ leg.from = {
1676
+ name: null,
1677
+ coords: leg.coords[0]
1678
+ };
1679
+ leg.to = {
1680
+ name: null,
1681
+ coords: leg.coords[leg.coords.length - 1]
1682
+ };
1683
+
1684
+ leg.steps = this.parseJsonSteps(jsonLeg.steps, leg.coords);
1685
+
1686
+ itinerary.legs.push(leg);
1687
+ }
1688
+
1689
+ // All legs have to be parsed before computing steps metadata
1690
+ generateStepsMetadata(itinerary);
1691
+
1692
+ }
1693
+
1694
+ return routerResponse;
1695
+ }
1696
+ }
1697
+
1698
+ var OsrmRemoteRouter$1 = new OsrmRemoteRouter();
1699
+
1700
+ class OtpRoutingModeCorrespondanceNotFound extends Error {
1701
+
1702
+ /**
1703
+ * @param {!string} routingMode
1704
+ */
1705
+ constructor(routingMode) {
1706
+ super(`OTP routing mode correspondance not found: ${routingMode}`);
1707
+ }
1708
+ }
1709
+
1710
+ /* eslint-disable max-statements */
1711
+
1712
+ /**
1713
+ * Input mode correspondance
1714
+ */
1715
+ const inputModeCorrespondance$1 = new Map();
1716
+ inputModeCorrespondance$1.set(Constants.ROUTING_MODE.CAR, 'CAR');
1717
+ inputModeCorrespondance$1.set(Constants.ROUTING_MODE.WALK, 'WALK');
1718
+ inputModeCorrespondance$1.set(Constants.ROUTING_MODE.BIKE, 'BICYCLE');
1719
+ inputModeCorrespondance$1.set(Constants.ROUTING_MODE.BUS, 'WALK,TRANSIT');
1720
+ inputModeCorrespondance$1.set(Constants.ROUTING_MODE.MULTI, 'WALK,TRANSIT');
1721
+
1722
+ /**
1723
+ * Singleton.
1724
+ */
1725
+ class OtpRemoteRouter extends RemoteRouter {
1726
+
1727
+ /**
1728
+ * @override
1729
+ */
1730
+ get rname() {
1731
+ return 'otp';
1732
+ }
1733
+
1734
+ /**
1735
+ * @override
1736
+ */
1737
+ async getItineraries(endpointUrl, mode, waypoints) {
1738
+ const url = this.getURL(endpointUrl, mode, waypoints);
1739
+ const response = await request(url);
1740
+ return this.createRouterResponseFromJson(response);
1741
+ }
1742
+
1743
+ /**
1744
+ * @param {string} endpointUrl
1745
+ * @param {string} mode
1746
+ * @param {Array<Coordinates>} waypoints
1747
+ */
1748
+ getURL(endpointUrl, mode, waypoints) {
1749
+
1750
+ const otpMode = inputModeCorrespondance$1.get(mode);
1751
+ if (!otpMode) {
1752
+ throw new OtpRoutingModeCorrespondanceNotFound(mode);
1753
+ }
1754
+
1755
+ if (waypoints.length > 2) {
1756
+ Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
1757
+ }
1758
+
1759
+ const fromPlace = `fromPlace=${waypoints[0].latitude},${waypoints[0].longitude}`;
1760
+ const toPlace = `toPlace=${waypoints[1].latitude},${waypoints[1].longitude}`;
1761
+ const queryMode = `mode=${otpMode}`;
1762
+
1763
+ const url = new URL(endpointUrl);
1764
+ let { search } = url;
1765
+ search = (search ? `${search}&` : '?') + `${fromPlace}&${toPlace}&${queryMode}`;
1766
+
1767
+ return `${url.origin}${url.pathname}${search}`;
1768
+ }
1769
+
1770
+ /**
1771
+ * @param {object} json
1772
+ * @returns {Coordinates}
1773
+ */
1774
+ jsonToCoordinates(json) {
1775
+ return new Coordinates(json.lat, json.lon);
1776
+ }
1777
+
1778
+ /**
1779
+ * @param {object} jsonSteps
1780
+ * @param {Coordinates[]} legCoords
1781
+ * @returns {Step[]}
1782
+ */
1783
+ parseJsonSteps(jsonSteps, legCoords) {
1784
+
1785
+ if (!jsonSteps) {
1786
+ return [];
1787
+ }
1788
+
1789
+ return jsonSteps.map(jsonStep => {
1790
+
1791
+ const step = new Step();
1792
+ const stepCoords = this.jsonToCoordinates(jsonStep);
1793
+
1794
+ // OTP step does not have the same coordinates than a point in legCoords.
1795
+ // That is why we look for the closest point.
1796
+ const distances = legCoords.map(coords => coords.distanceTo(stepCoords));
1797
+ const idStepCoordsInLeg = distances.indexOf(Math.min(...distances));
1798
+ if (idStepCoordsInLeg < 0) {
1799
+ throw new Error('OTP Parser: Cannot find closest step');
1800
+ }
1801
+ step.coords = legCoords[idStepCoordsInLeg];
1802
+ step._idCoordsInLeg = idStepCoordsInLeg;
1803
+
1804
+ step.name = jsonStep.streetName;
1805
+ step.levelChange = null;
1806
+
1807
+ step.distance = jsonStep.distance;
1808
+
1809
+ return step;
1810
+ });
1811
+ }
1812
+
1813
+ /**
1814
+ * Generate multi itineraries from OTP JSON
1815
+ * @param {object} json JSON file provided by OTP.
1816
+ * @returns {?RouterResponse}
1817
+ */
1818
+ createRouterResponseFromJson(json) {
1819
+
1820
+ const { plan: jsonPlan } = json;
1821
+
1822
+ if (!jsonPlan) {
1823
+ return null;
1824
+ }
1825
+
1826
+ const routerResponse = new RouterResponse();
1827
+ routerResponse.routerName = this.rname;
1828
+
1829
+ routerResponse.from = this.jsonToCoordinates(jsonPlan.from);
1830
+ routerResponse.to = this.jsonToCoordinates(jsonPlan.to);
1831
+
1832
+ for (const jsonItinerary of jsonPlan.itineraries) {
1833
+
1834
+ const itinerary = new Itinerary();
1835
+
1836
+ itinerary.duration = jsonItinerary.duration;
1837
+ itinerary.startTime = jsonItinerary.startTime;
1838
+ itinerary.endTime = jsonItinerary.endTime;
1839
+ itinerary.from = routerResponse.from;
1840
+ itinerary.to = routerResponse.to;
1841
+
1842
+ routerResponse.itineraries.push(itinerary);
1843
+
1844
+ for (const jsonLeg of jsonItinerary.legs) {
1845
+
1846
+ const leg = new Leg();
1847
+
1848
+ leg.mode = jsonLeg.mode;
1849
+ leg.duration = jsonLeg.duration;
1850
+ leg.startTime = jsonLeg.startTime;
1851
+ leg.endTime = jsonLeg.endTime;
1852
+ leg.from = {
1853
+ name: jsonLeg.from.name,
1854
+ coords: this.jsonToCoordinates(jsonLeg.from)
1855
+ };
1856
+ leg.to = {
1857
+ name: jsonLeg.to.name,
1858
+ coords: this.jsonToCoordinates(jsonLeg.to)
1859
+ };
1860
+ leg.coords = Polyline.decode(jsonLeg.legGeometry.points).map(([lat, lon]) => new Coordinates(lat, lon));
1861
+
1862
+ leg.steps = this.parseJsonSteps(jsonLeg.steps, leg.coords);
1863
+
1864
+ if (leg.mode === 'BUS' || leg.mode === 'TRAM') {
1865
+ leg.transportInfo = {
1866
+ name: jsonLeg.route,
1867
+ routeColor: jsonLeg.routeColor,
1868
+ routeTextColor: jsonLeg.routeTextColor,
1869
+ directionName: jsonLeg.headsign
1870
+ };
1871
+
1872
+ const legStep = new Step();
1873
+ legStep.coords = leg.coords[0];
1874
+ legStep._idCoordsInLeg = 0;
1875
+ legStep.name = jsonLeg.headsign;
1876
+ legStep.levelChange = null;
1877
+ legStep.distance = jsonLeg.distance;
1878
+ leg.steps = [legStep];
1879
+ }
1880
+
1881
+ // jsonLeg.distance is not reliable when compared to the array of leg coords.
1882
+ // leg.distance = jsonLeg.distance;
1883
+ leg.distance = leg.coords.reduce((acc, coords, idx, arr) => {
1884
+ if (idx === 0) {
1885
+ return acc;
1886
+ }
1887
+ return acc + arr[idx - 1].distanceTo(coords);
1888
+ }, 0);
1889
+
1890
+ itinerary.legs.push(leg);
1891
+
1892
+ }
1893
+
1894
+ itinerary.distance = itinerary.coords.reduce((acc, coords, idx, arr) => {
1895
+ if (idx === 0) {
1896
+ return acc;
1897
+ }
1898
+ return acc + arr[idx - 1].distanceTo(coords);
1899
+ }, 0);
1900
+
1901
+ // All legs have to be parsed before computing steps metadata
1902
+ generateStepsMetadata(itinerary);
1903
+ }
1904
+
1905
+ return routerResponse;
1906
+ }
1907
+ }
1908
+
1909
+ var OtpRemoteRouter$1 = new OtpRemoteRouter();
1910
+
1911
+ class CitywayRoutingModeCorrespondanceNotFound extends Error {
1912
+
1913
+ /**
1914
+ * @param {!string} routingMode
1915
+ */
1916
+ constructor(routingMode) {
1917
+ super(`Cityway routing mode correspondance not found: ${routingMode}`);
1918
+ }
1919
+ }
1920
+
1921
+ /* eslint-disable max-depth */
1922
+
1923
+ /**
1924
+ * Input mode correspondance
1925
+ */
1926
+ const inputModeCorrespondance = new Map();
1927
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.CAR, 'Car');
1928
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.WALK, 'Walk');
1929
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.BIKE, 'Bike');
1930
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.BUS, 'PT');
1931
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.MULTI, 'PT');
1932
+
1933
+
1934
+ /**
1935
+ * List of all routing modes supported by the API
1936
+ */
1937
+ const routingModeCorrespondance$1 = new Map();
1938
+ routingModeCorrespondance$1.set('WALK', Constants.ROUTING_MODE.WALK);
1939
+ routingModeCorrespondance$1.set('BICYCLE', Constants.ROUTING_MODE.BIKE);
1940
+ routingModeCorrespondance$1.set('TRAMWAY', Constants.ROUTING_MODE.TRAM);
1941
+ routingModeCorrespondance$1.set('METRO', Constants.ROUTING_MODE.METRO);
1942
+ routingModeCorrespondance$1.set('FUNICULAR', Constants.ROUTING_MODE.FUNICULAR);
1943
+ routingModeCorrespondance$1.set('BUS', Constants.ROUTING_MODE.BUS);
1944
+ routingModeCorrespondance$1.set('COACH', Constants.ROUTING_MODE.BUS);
1945
+ routingModeCorrespondance$1.set('SCHOOL', Constants.ROUTING_MODE.BUS);
1946
+ routingModeCorrespondance$1.set('BUS_PMR', Constants.ROUTING_MODE.BUS);
1947
+ routingModeCorrespondance$1.set('MINIBUS', Constants.ROUTING_MODE.BUS);
1948
+ routingModeCorrespondance$1.set('TROLLEY_BUS', Constants.ROUTING_MODE.BUS);
1949
+ routingModeCorrespondance$1.set('TAXIBUS', Constants.ROUTING_MODE.BUS);
1950
+ routingModeCorrespondance$1.set('SHUTTLE', Constants.ROUTING_MODE.BUS);
1951
+ routingModeCorrespondance$1.set('TRAIN', Constants.ROUTING_MODE.TRAIN);
1952
+ routingModeCorrespondance$1.set('HST', Constants.ROUTING_MODE.TRAIN);
1953
+ routingModeCorrespondance$1.set('LOCAL_TRAIN', Constants.ROUTING_MODE.TRAIN);
1954
+ routingModeCorrespondance$1.set('AIR', Constants.ROUTING_MODE.AIRPLANE);
1955
+ routingModeCorrespondance$1.set('FERRY', Constants.ROUTING_MODE.BOAT);
1956
+ routingModeCorrespondance$1.set('TAXI', Constants.ROUTING_MODE.UNKNOWN);
1957
+ routingModeCorrespondance$1.set('CAR_POOL', Constants.ROUTING_MODE.UNKNOWN);
1958
+ routingModeCorrespondance$1.set('PRIVATE_VEHICLE', Constants.ROUTING_MODE.CAR);
1959
+ routingModeCorrespondance$1.set('SCOOTER', Constants.ROUTING_MODE.MOTO);
1960
+
1961
+ /**
1962
+ * List of all plan trip supported by the API
1963
+ * Routing mode UNKNOWN means that the itinerary will not be parsed by the router
1964
+ */
1965
+ const planTripType = new Map();
1966
+ planTripType.set(0, Constants.ROUTING_MODE.BUS);
1967
+ planTripType.set(1, Constants.ROUTING_MODE.WALK);
1968
+ planTripType.set(2, Constants.ROUTING_MODE.BIKE);
1969
+ planTripType.set(3, Constants.ROUTING_MODE.CAR);
1970
+ planTripType.set(4, Constants.ROUTING_MODE.UNKNOWN);
1971
+ planTripType.set(5, Constants.ROUTING_MODE.UNKNOWN);
1972
+ planTripType.set(6, Constants.ROUTING_MODE.UNKNOWN);
1973
+ planTripType.set(7, Constants.ROUTING_MODE.UNKNOWN);
1974
+ planTripType.set(8, Constants.ROUTING_MODE.UNKNOWN);
1975
+ planTripType.set(9, Constants.ROUTING_MODE.UNKNOWN);
1976
+ planTripType.set(10, Constants.ROUTING_MODE.UNKNOWN);
1977
+ planTripType.set(11, Constants.ROUTING_MODE.UNKNOWN);
1978
+ planTripType.set(12, Constants.ROUTING_MODE.UNKNOWN);
1979
+ planTripType.set(13, Constants.ROUTING_MODE.UNKNOWN);
1980
+ planTripType.set(14, Constants.ROUTING_MODE.UNKNOWN);
1981
+
1982
+
1983
+ /**
1984
+ * Singleton.
1985
+ */
1986
+ class CitywayRemoteRouter extends RemoteRouter {
1987
+
1988
+ /**
1989
+ * @override
1990
+ */
1991
+ get rname() {
1992
+ return 'cityway';
1993
+ }
1994
+
1995
+
1996
+ /**
1997
+ * @override
1998
+ */
1999
+ async getItineraries(endpointUrl, mode, waypoints) {
2000
+ const url = this.getURL(endpointUrl, mode, waypoints);
2001
+ const response = await request(url);
2002
+ return this.createRouterResponseFromJson(response);
2003
+ }
2004
+
2005
+
2006
+ /**
2007
+ * @param {string} endpointUrl
2008
+ * @param {string} mode
2009
+ * @param {Array<Coordinates>} waypoints
2010
+ */
2011
+ getURL(endpointUrl, mode, waypoints) {
2012
+ const citywayMode = inputModeCorrespondance.get(mode);
2013
+ if (!citywayMode) {
2014
+ throw new CitywayRoutingModeCorrespondanceNotFound(mode);
2015
+ }
2016
+ if (waypoints.length > 2) {
2017
+ Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
2018
+ }
2019
+ const fromPlace = `DepartureLatitude=${waypoints[0].latitude}&DepartureLongitude=${waypoints[0].longitude}`;
2020
+ const toPlace = `ArrivalLatitude=${waypoints[1].latitude}&ArrivalLongitude=${waypoints[1].longitude}`;
2021
+ const queryMode = `TripModes=${citywayMode}`;
2022
+
2023
+ const url = new URL(endpointUrl);
2024
+ let { search } = url;
2025
+ search = (search ? `${search}&` : '?') + `${fromPlace}&${toPlace}&${queryMode}`;
2026
+
2027
+ return `${url.origin}${url.pathname}${search}`;
2028
+ }
2029
+
2030
+ /**
2031
+ * Generate multi itineraries from Cityway JSON
2032
+ * @param {object} json JSON file provided by Cityway.
2033
+ * @returns {?RouterResponse}
2034
+ * @example https://preprod.api.lia2.cityway.fr/journeyplanner/api/opt/PlanTrips/json?DepartureLatitude=49.51509388236216&DepartureLongitude=0.09341749619366316&ArrivalLatitude=49.5067090188444&ArrivalLongitude=0.1694842115417831&DepartureType=COORDINATES&ArrivalType=COORDINATES
2035
+ */
2036
+ createRouterResponseFromJson(json) {
2037
+
2038
+ if (json.StatusCode !== 200 || !json.Data || !json.Data.length) {
2039
+ return null;
2040
+ }
2041
+
2042
+ const routerResponse = new RouterResponse();
2043
+ routerResponse.routerName = this.rname;
2044
+
2045
+
2046
+ // Do not know if it the best approach, but it works...
2047
+ const allJsonTrips = json.Data.reduce((acc, dataObj) => {
2048
+ acc.push(...dataObj.response.trips.Trip.map(trip => ({
2049
+ ...trip,
2050
+ ...(dataObj.hasOwnProperty('PlanTripType') ? { PlanTripType: dataObj.PlanTripType } : {})
2051
+ })));
2052
+ return acc;
2053
+ }, []);
2054
+
2055
+ // eslint-disable-next-line no-labels
2056
+ itineraryLoop:
2057
+ for (const trip of allJsonTrips) {
2058
+
2059
+ if (trip.hasOwnProperty('PlanTripType') && planTripType.get(trip.PlanTripType) === Constants.ROUTING_MODE.UNKNOWN) {
2060
+ continue;
2061
+ }
2062
+
2063
+ const itinerary = new Itinerary();
2064
+
2065
+ itinerary.duration = this.parseDuration(trip.Duration);
2066
+ itinerary.startTime = this.jsonDateToTimestamp(trip.Departure.Time);
2067
+ itinerary.from = this.jsonToCoordinates(trip.Departure.Site.Position);
2068
+ itinerary.endTime = this.jsonDateToTimestamp(trip.Arrival.Time);
2069
+ itinerary.to = this.jsonToCoordinates(trip.Arrival.Site.Position);
2070
+
2071
+ for (const jsonSection of trip.sections.Section) {
2072
+
2073
+ const jsonLeg = jsonSection.Leg ? jsonSection.Leg : jsonSection.PTRide;
2074
+
2075
+ const leg = new Leg();
2076
+
2077
+ leg.mode = routingModeCorrespondance$1.get(jsonLeg.TransportMode);
2078
+ leg.duration = this.parseDuration(jsonLeg.Duration);
2079
+ leg.startTime = this.jsonDateToTimestamp(jsonLeg.Departure.Time);
2080
+ leg.endTime = this.jsonDateToTimestamp(jsonLeg.Arrival.Time);
2081
+ leg.coords = [];
2082
+
2083
+ if (leg.mode === Constants.ROUTING_MODE.UNKNOWN) {
2084
+ // eslint-disable-next-line
2085
+ continue itineraryLoop;
2086
+ }
2087
+
2088
+ if (leg.mode === Constants.ROUTING_MODE.WALK
2089
+ || leg.mode === Constants.ROUTING_MODE.BIKE
2090
+ || leg.mode === Constants.ROUTING_MODE.CAR) {
2091
+
2092
+ leg.from = {
2093
+ name: jsonLeg.Departure.Site.Name,
2094
+ coords: this.jsonToCoordinates(jsonLeg.Departure.Site.Position)
2095
+ };
2096
+ leg.to = {
2097
+ name: jsonLeg.Arrival.Site.Name,
2098
+ coords: this.jsonToCoordinates(jsonLeg.Arrival.Site.Position)
2099
+ };
2100
+
2101
+ leg.steps = [];
2102
+ for (const jsonPathLink of jsonLeg.pathLinks.PathLink) {
2103
+ const step = new Step();
2104
+ let stepCoords;
2105
+ if (jsonPathLink.Geometry) {
2106
+ stepCoords = this.parseWKTGeometry(jsonPathLink.Geometry);
2107
+ } else {
2108
+ stepCoords = [leg.from.coords, leg.to.coords];
2109
+ }
2110
+ step.coords = stepCoords[0];
2111
+ step._idCoordsInLeg = leg.coords.length;
2112
+ stepCoords.forEach((coords, idx) => {
2113
+ if (
2114
+ idx !== 0
2115
+ || leg.coords.length === 0
2116
+ || !leg.coords[leg.coords.length - 1].equalsTo(coords)
2117
+ ) {
2118
+ leg.coords.push(coords);
2119
+ }
2120
+ });
2121
+
2122
+
2123
+ step.name = jsonPathLink.Departure.Site.Name;
2124
+ step.levelChange = null;
2125
+
2126
+ step.distance = jsonPathLink.Distance;
2127
+
2128
+ leg.steps.push(step);
2129
+ }
2130
+
2131
+ } else if (Constants.PUBLIC_TRANSPORT.includes(leg.mode)) {
2132
+
2133
+ leg.from = {
2134
+ name: jsonLeg.Departure.StopPlace.Name,
2135
+ coords: this.jsonToCoordinates(jsonLeg.Departure.StopPlace.Position)
2136
+ };
2137
+ leg.to = {
2138
+ name: jsonLeg.Arrival.StopPlace.Name,
2139
+ coords: this.jsonToCoordinates(jsonLeg.Arrival.StopPlace.Position)
2140
+ };
2141
+
2142
+ let transportName = jsonLeg.Line.Number;
2143
+ if (leg.mode === Constants.ROUTING_MODE.TRAM && transportName.toLowerCase().includes('tram')) {
2144
+ // In order to remove the "TRAM " prefix.
2145
+ transportName = transportName.substr(5);
2146
+ }
2147
+
2148
+ leg.transportInfo = {
2149
+ name: transportName,
2150
+ routeColor: jsonLeg.Line.Color,
2151
+ routeTextColor: jsonLeg.Line.TextColor,
2152
+ directionName: jsonLeg.Destination
2153
+ };
2154
+
2155
+ for (const jsonStep of jsonLeg.steps.Step) {
2156
+ const stepCoords = this.parseWKTGeometry(jsonStep.Geometry);
2157
+ stepCoords.forEach((coords, idx) => {
2158
+ if (
2159
+ idx !== 0
2160
+ || leg.coords.length === 0
2161
+ || !leg.coords[leg.coords.length - 1].equalsTo(coords)
2162
+ ) {
2163
+ leg.coords.push(coords);
2164
+ }
2165
+ });
2166
+ }
2167
+
2168
+ const legStep = new Step();
2169
+ legStep.coords = leg.coords[0];
2170
+ legStep._idCoordsInLeg = 0;
2171
+ legStep.name = jsonLeg.Line.Name;
2172
+ legStep.levelChange = null;
2173
+ legStep.distance = jsonLeg.Distance;
2174
+ leg.steps = [legStep];
2175
+ } else {
2176
+ Logger.warn(`[CitywayParser] Unknown leg mode: ${jsonLeg.TransportMode}`);
2177
+ }
2178
+
2179
+ leg.distance = leg.coords.reduce((acc, coords, idx, arr) => {
2180
+ if (idx === 0) {
2181
+ return acc;
2182
+ }
2183
+ return acc + arr[idx - 1].distanceTo(coords);
2184
+ }, 0);
2185
+
2186
+ itinerary.legs.push(leg);
2187
+
2188
+ }
2189
+
2190
+ routerResponse.itineraries.push(itinerary);
2191
+
2192
+ itinerary.distance = itinerary.coords.reduce((acc, coords, idx, arr) => {
2193
+ if (idx === 0) {
2194
+ return acc;
2195
+ }
2196
+ return acc + arr[idx - 1].distanceTo(coords);
2197
+ }, 0);
2198
+
2199
+ // All legs have to be parsed before computing steps metadata
2200
+ generateStepsMetadata(itinerary);
2201
+ }
2202
+
2203
+ routerResponse.from = routerResponse.itineraries[0].from;
2204
+ routerResponse.to = routerResponse.itineraries[routerResponse.itineraries.length - 1].to;
2205
+
2206
+ return routerResponse;
2207
+ }
2208
+
2209
+ /**
2210
+ * @param {object} json
2211
+ * @returns {Coordinates}
2212
+ */
2213
+ jsonToCoordinates(json) {
2214
+ return new Coordinates(json.Lat, json.Long);
2215
+ }
2216
+
2217
+ /**
2218
+ * @param {string} jsonDate
2219
+ * @returns {number}
2220
+ */
2221
+ jsonDateToTimestamp(jsonDate) {
2222
+ const [dateStr, timeStr] = jsonDate.split(' ');
2223
+ const [dayStr, monthStr, yearStr] = dateStr.split('/');
2224
+ const [hoursStr, minutesStr, secondsStr] = timeStr.split(':');
2225
+
2226
+ return dateWithTimeZone(
2227
+ Number(yearStr),
2228
+ Number(monthStr) - 1,
2229
+ Number(dayStr),
2230
+ Number(hoursStr),
2231
+ Number(minutesStr),
2232
+ Number(secondsStr)
2233
+ ).getTime();
2234
+ }
2235
+
2236
+ /**
2237
+ * @param {string} wktGeometry
2238
+ * @returns {Coordinates[]}
2239
+ */
2240
+ parseWKTGeometry(wktGeometry) {
2241
+ const tmpCoordsStr = wktGeometry.match(/LINESTRING \((.*)\)/i);
2242
+ const tmpCoordsPt = wktGeometry.match(/POINT \((.*)\)/i);
2243
+ if (!tmpCoordsStr && !tmpCoordsPt) {
2244
+ return null;
2245
+ }
2246
+
2247
+ if (tmpCoordsPt) {
2248
+ const [lng, lat] = tmpCoordsPt[1].split(' ');
2249
+ return [new Coordinates(Number(lat), Number(lng))];
2250
+ }
2251
+
2252
+ return tmpCoordsStr[1].split(',').map(str => {
2253
+ const sp = str.trim().split(' ');
2254
+ return new Coordinates(Number(sp[1]), Number(sp[0]));
2255
+ });
2256
+ }
2257
+
2258
+ /**
2259
+ * @param {string} iso8601Duration
2260
+ * @see https://stackoverflow.com/a/29153059/2239938
2261
+ */
2262
+ parseDuration(iso8601Duration) {
2263
+ const iso8601DurationRegex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
2264
+
2265
+ var matches = iso8601Duration.match(iso8601DurationRegex);
2266
+
2267
+ // const sign = typeof matches[1] === 'undefined' ? '+' : '-',
2268
+ const years = typeof matches[2] === 'undefined' ? 0 : Number(matches[2]);
2269
+ const months = typeof matches[3] === 'undefined' ? 0 : Number(matches[3]);
2270
+ const weeks = typeof matches[4] === 'undefined' ? 0 : Number(matches[4]);
2271
+ const days = typeof matches[5] === 'undefined' ? 0 : Number(matches[5]);
2272
+ const hours = typeof matches[6] === 'undefined' ? 0 : Number(matches[6]);
2273
+ const minutes = typeof matches[7] === 'undefined' ? 0 : Number(matches[7]);
2274
+ const seconds = typeof matches[8] === 'undefined' ? 0 : Number(matches[8]);
2275
+
2276
+ return seconds
2277
+ + minutes * 60
2278
+ + hours * 3600
2279
+ + days * 86400
2280
+ + weeks * (86400 * 7)
2281
+ + months * (86400 * 30)
2282
+ + years * (86400 * 365.25);
2283
+ }
2284
+ }
2285
+
2286
+ var CitywayRemoteRouter$1 = new CitywayRemoteRouter();
2287
+
2288
+ /* eslint-disable max-statements */
2289
+
2290
+ /**
2291
+ * Singleton.
2292
+ */
2293
+ class DeutscheBahnRemoteRouter extends RemoteRouter {
2294
+
2295
+ /**
2296
+ * @override
2297
+ */
2298
+ get rname() {
2299
+ return 'deutsche-bahn';
2300
+ }
2301
+
2302
+ /**
2303
+ * @override
2304
+ */
2305
+ async getItineraries(endpointUrl, mode, waypoints) {
2306
+ const url = this.getURL(endpointUrl, mode, waypoints);
2307
+ const response = await request(url);
2308
+ return this.createRouterResponseFromJson(response, waypoints[0], waypoints[1]);
2309
+ }
2310
+
2311
+ /**
2312
+ * @param {string} endpointUrl
2313
+ * @param {string} mode
2314
+ * @param {Array<Coordinates>} waypoints
2315
+ */
2316
+ getURL(endpointUrl, mode, waypoints) {
2317
+ let url = endpointUrl + '/route/v1/' + mode + '/';
2318
+
2319
+ url += waypoints.map(waypoint => {
2320
+ if (waypoint.level) {
2321
+ const altitude = waypoint.level.isRange ? waypoint.level.low : waypoint.level.val;
2322
+
2323
+ return waypoint.longitude + ',' + waypoint.latitude + ',' + altitude;
2324
+ }
2325
+
2326
+ return waypoint.longitude + ',' + waypoint.latitude;
2327
+ }).join(';');
2328
+
2329
+ url += '?geometries=geojson&overview=full&steps=true';
2330
+
2331
+ return url;
2332
+ }
2333
+
2334
+ /**
2335
+ * Generate multi itineraries from the DB JSON
2336
+ * @param {object} json JSON file provided by the DB.
2337
+ * @param {Coordinates} from itinerary start
2338
+ * @param {Coordinates} to itinerary end
2339
+ * @returns {?RouterResponse}
2340
+ */
2341
+ createRouterResponseFromJson(json, from, to) {
2342
+
2343
+ const { segments: jsonSegments } = json;
2344
+
2345
+ if (!jsonSegments) {
2346
+ return null;
2347
+ }
2348
+
2349
+ const routerResponse = new RouterResponse();
2350
+ routerResponse.routerName = this.rname;
2351
+
2352
+ routerResponse.from = from;
2353
+ routerResponse.to = to;
2354
+
2355
+ /** @type {GraphEdge<OsmElement>[]} */
2356
+ const edges = [];
2357
+
2358
+ /** @type {GraphNode<OsmElement>[]} */
2359
+ const nodes = [];
2360
+
2361
+ /** @type {number[]} */
2362
+ const edgesWeights = [];
2363
+
2364
+ let id = 1;
2365
+ for (const jsonSegment of jsonSegments) {
2366
+
2367
+ const level = new Level(jsonSegment.fromLevel, jsonSegment.toLevel);
2368
+ const osmWay = new OsmWay(id++, null, level);
2369
+
2370
+ for (const jsonPoint of jsonSegment.polyline) {
2371
+ const coord = new Coordinates(jsonPoint.lat, jsonPoint.lon, null, level);
2372
+
2373
+ if (nodes.length !== 0
2374
+ && nodes[nodes.length - 1].coords.equalsTo(coord)) {
2375
+ continue;
2376
+ }
2377
+
2378
+ const osmNode = new OsmNode(id++, coord);
2379
+ const node = new GraphNode(osmNode.coords, osmNode);
2380
+
2381
+ if (nodes.length !== 0) {
2382
+ const prevNode = nodes[nodes.length - 1];
2383
+ const edge = new GraphEdge(prevNode, node, level, osmWay);
2384
+ edges.push(edge);
2385
+ edgesWeights.push(prevNode.coords.distanceTo(osmNode));
2386
+ }
2387
+
2388
+ nodes.push(node);
2389
+
2390
+ }
2391
+ }
2392
+
2393
+ /** @type {GraphItinerary<OsmElement>} */
2394
+ const graphItinerary = new GraphItinerary();
2395
+ graphItinerary.nodes = nodes;
2396
+ graphItinerary.edges = edges;
2397
+ graphItinerary.edgesWeights = edgesWeights;
2398
+ graphItinerary.start = nodes[0].coords;
2399
+ graphItinerary.end = nodes[nodes.length - 1].coords;
2400
+
2401
+ const points = nodes.map(node => node.coords);
2402
+ const itinerary = Itinerary.fromOrderedCoordinates(points, from, to);
2403
+ itinerary.legs[0].steps = WemapStepsGeneration.fromGraphItinerary(graphItinerary);
2404
+ itinerary.legs[0].steps.map((step, idx) => (step._idCoordsInLeg = idx));
2405
+
2406
+ // All legs have to be parsed before computing steps metadata
2407
+ generateStepsMetadata(itinerary);
2408
+
2409
+ routerResponse.itineraries.push(itinerary);
2410
+
2411
+ return routerResponse;
2412
+ }
2413
+ }
2414
+
2415
+ var DeutscheBahnRemoteRouter$1 = new DeutscheBahnRemoteRouter();
2416
+
2417
+ /* eslint-disable max-statements */
2418
+
2419
+ /**
2420
+ * List of all modes supported by the API
2421
+ * http://doc.navitia.io/#physical-mode
2422
+ */
2423
+
2424
+ const routingModeCorrespondance = new Map();
2425
+ routingModeCorrespondance.set('Air', Constants.ROUTING_MODE.AIRPLANE);
2426
+ routingModeCorrespondance.set('Boat', Constants.ROUTING_MODE.BOAT);
2427
+ routingModeCorrespondance.set('Bus', Constants.ROUTING_MODE.BUS);
2428
+ routingModeCorrespondance.set('BusRapidTransit', Constants.ROUTING_MODE.BUS);
2429
+ routingModeCorrespondance.set('Coach', Constants.ROUTING_MODE.BUS);
2430
+ routingModeCorrespondance.set('Ferry', Constants.ROUTING_MODE.FERRY);
2431
+ routingModeCorrespondance.set('Funicular', Constants.ROUTING_MODE.FUNICULAR);
2432
+ routingModeCorrespondance.set('LocalTrain', Constants.ROUTING_MODE.TRAIN);
2433
+ routingModeCorrespondance.set('LongDistanceTrain', Constants.ROUTING_MODE.TRAIN);
2434
+ routingModeCorrespondance.set('Metro', Constants.ROUTING_MODE.METRO);
2435
+ routingModeCorrespondance.set('Métro', Constants.ROUTING_MODE.METRO);
2436
+ routingModeCorrespondance.set('RailShuttle', Constants.ROUTING_MODE.TRAIN);
2437
+ routingModeCorrespondance.set('RapidTransit', Constants.ROUTING_MODE.BUS);
2438
+ routingModeCorrespondance.set('Shuttle', Constants.ROUTING_MODE.BUS);
2439
+ routingModeCorrespondance.set('SuspendedCableCar', Constants.ROUTING_MODE.FUNICULAR);
2440
+ routingModeCorrespondance.set('Taxi', Constants.ROUTING_MODE.TAXI);
2441
+ routingModeCorrespondance.set('Train', Constants.ROUTING_MODE.TRAIN);
2442
+ routingModeCorrespondance.set('RER', Constants.ROUTING_MODE.TRAIN);
2443
+ routingModeCorrespondance.set('Tramway', Constants.ROUTING_MODE.TRAM);
2444
+ routingModeCorrespondance.set('walking', Constants.ROUTING_MODE.WALK);
2445
+ routingModeCorrespondance.set('bike', Constants.ROUTING_MODE.BIKE);
2446
+
2447
+ /**
2448
+ * List of transports modes
2449
+ */
2450
+ const TRANSPORT_IDS = [
2451
+ 'physical_mode:Air',
2452
+ 'physical_mode:Boat',
2453
+ 'physical_mode:Bus',
2454
+ 'physical_mode:BusRapidTransit',
2455
+ 'physical_mode:Coach',
2456
+ 'physical_mode:Ferry',
2457
+ 'physical_mode:Funicular',
2458
+ 'physical_mode:LocalTrain',
2459
+ 'physical_mode:LongDistanceTrain',
2460
+ 'physical_mode:Metro',
2461
+ 'physical_mode:RailShuttle',
2462
+ 'physical_mode:RapidTransit',
2463
+ 'physical_mode:Shuttle',
2464
+ 'physical_mode:SuspendedCableCar',
2465
+ 'physical_mode:Taxi',
2466
+ 'physical_mode:Train',
2467
+ 'physical_mode:Tramway'
2468
+ ];
2469
+
2470
+ const clientId = '539eec73-3bb5-4327-bb5e-a52672658592';
2471
+ const clientSecret = '899f4bb9-f1d5-45d3-9f67-530827bb6734';
2472
+
2473
+ /**
2474
+ * Get last item of a given array
2475
+ * @param {Array} array
2476
+ * @returns {any}
2477
+ */
2478
+ function last(array) {
2479
+ return array[array.length - 1];
2480
+ }
2481
+
2482
+ /**
2483
+ * Singleton.
2484
+ */
2485
+ class IdfmRemoteRouter extends RemoteRouter {
2486
+
2487
+ isLogged = false;
2488
+ token = null;
2489
+ expiresAt = null;
2490
+
2491
+ /**
2492
+ * @override
2493
+ */
2494
+ get rname() {
2495
+ return 'idfm';
2496
+ }
2497
+
2498
+ /**
2499
+ * @override
2500
+ */
2501
+ async getItineraries(endpointUrl, mode, waypoints) {
2502
+ if (!this.canRequestService()) {
2503
+ await this._connect();
2504
+ }
2505
+
2506
+ const url = this.getURL(endpointUrl, mode, waypoints);
2507
+
2508
+ const response = await request(url, {
2509
+ headers: { Authorization: 'Bearer ' + this.token }
2510
+ });
2511
+
2512
+ return this.createRouterResponseFromJson(response);
2513
+ }
2514
+
2515
+ _connect() {
2516
+
2517
+ const details = {
2518
+ 'grant_type': 'client_credentials',
2519
+ 'scope': 'read-data',
2520
+ 'client_id': clientId,
2521
+ 'client_secret': clientSecret
2522
+ };
2523
+
2524
+ const data = new URLSearchParams();
2525
+ for (const property in details) {
2526
+ if (details.hasOwnProperty(property)) {
2527
+ const encodedKey = encodeURIComponent(property);
2528
+ const encodedValue = encodeURIComponent(details[property]);
2529
+ data.append(encodedKey, encodedValue);
2530
+ }
2531
+ }
2532
+
2533
+ return request('https://idfm.getwemap.com/api/oauth/token', {
2534
+ method: 'POST',
2535
+ data
2536
+ }).then((response) => {
2537
+ if (response.access_token) {
2538
+ this.token = response.access_token;
2539
+ this.isLogged = true;
2540
+ this.expiresAt = new Date(new Date().getTime() + response.expires_in * 1000);
2541
+ }
2542
+ });
2543
+ }
2544
+
2545
+ canRequestService() {
2546
+ return this.token && this.expiresAt && this.expiresAt - new Date() > 0;
2547
+ }
2548
+
2549
+ /**
2550
+ * @param {string} endpointUrl
2551
+ * @param {string} mode
2552
+ * @param {Array<Coordinates>} waypoints
2553
+ */
2554
+ getURL(endpointUrl, mode, waypoints) {
2555
+
2556
+ if (waypoints.length > 2) {
2557
+ Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
2558
+ }
2559
+
2560
+ const fromPlace = `from=${waypoints[0].longitude};${waypoints[0].latitude}`;
2561
+ const toPlace = `to=${waypoints[1].longitude};${waypoints[1].latitude}`;
2562
+
2563
+ let url = new URL(endpointUrl);
2564
+ let { search } = url;
2565
+ search = (search ? `${search}&` : '?') + `${fromPlace}&${toPlace}`;
2566
+
2567
+ let query = '';
2568
+ switch (mode) {
2569
+ case Constants.ITINERARY_MODE.FOOT:
2570
+ query = this.getWalkingQuery();
2571
+ break;
2572
+ case Constants.ITINERARY_MODE.BIKE:
2573
+ query = this.getBikeQuery();
2574
+ break;
2575
+ case Constants.ITINERARY_MODE.DRIVING:
2576
+ query = this.getCarQuery();
2577
+ break;
2578
+ }
2579
+
2580
+ url = `${url.origin}${url.pathname}${search}${query}`;
2581
+
2582
+ return url;
2583
+ }
2584
+
2585
+ getCarQuery() {
2586
+ const forbiddenTransport = TRANSPORT_IDS.map((id) => `forbidden_uris[]=${id}`).join('&');
2587
+ const allowCar = 'first_section_mode[]=walking&first_section_mode[]=car&last_section_mode[]=walking&last_section_mode[]=car';
2588
+
2589
+ return `&${forbiddenTransport}&${allowCar}`;
2590
+ }
2591
+
2592
+ getWalkingQuery() {
2593
+ const forbiddenTransport = TRANSPORT_IDS.map((id) => `forbidden_uris[]=${id}`).join('&');
2594
+ const allowWalking = 'first_section_mode[]=walking&last_section_mode[]=walking';
2595
+
2596
+ return `&${forbiddenTransport}&${allowWalking}`;
2597
+ }
2598
+
2599
+ getBikeQuery() {
2600
+ const forbiddenTransport = TRANSPORT_IDS.map((id) => `forbidden_uris[]=${id}`).join('&');
2601
+ const allowBike = 'first_section_mode[]=bike&last_section_mode[]=bike';
2602
+
2603
+ return `&${forbiddenTransport}&${allowBike}`;
2604
+ }
2605
+
2606
+ /**
2607
+ * @param {object} json
2608
+ * @returns {Coordinates}
2609
+ */
2610
+ jsonToCoordinates(json) {
2611
+ return new Coordinates(Number(json.lat), Number(json.lon));
2612
+ }
2613
+
2614
+ getSectionCoords(section) {
2615
+ const from = section.from.stop_point ? this.jsonToCoordinates(section.from.stop_point.coord) : this.jsonToCoordinates(section.from.address.coord);
2616
+ const to = section.to.stop_point ? this.jsonToCoordinates(section.to.stop_point.coord) : this.jsonToCoordinates(section.to.address.coord);
2617
+
2618
+ return {
2619
+ from,
2620
+ to
2621
+ };
2622
+ }
2623
+
2624
+ /**
2625
+ * Since the IDFM API does not provide coords for each step, we need to compute them
2626
+ * We trim the coordinates of the leg with the distance of each step and keep the last result as the coords of the step
2627
+ * @param {Leg} leg
2628
+ */
2629
+ findStepsCoord(leg) {
2630
+ const { steps, coords } = leg;
2631
+
2632
+ const duplicatedCoords = [...coords];
2633
+ let previousStep = steps[0];
2634
+ let accumulatedIndex = 0;
2635
+
2636
+ for (const [idx, step] of steps.entries()) {
2637
+ let newCoords;
2638
+
2639
+ if (idx === 0) {
2640
+ step._idCoordsInLeg = 0;
2641
+ newCoords = coords[0];
2642
+ } else if (idx === steps.length - 1) {
2643
+ step._idCoordsInLeg = coords.length - 1;
2644
+ newCoords = last(coords);
2645
+ } else if (duplicatedCoords.length === 1) {
2646
+ accumulatedIndex++;
2647
+
2648
+ step._idCoordsInLeg = accumulatedIndex;
2649
+
2650
+ newCoords = duplicatedCoords[0];
2651
+
2652
+ coords[step._idCoordsInLeg] = newCoords;
2653
+ } else {
2654
+ const result = Utils.trimRoute(duplicatedCoords, duplicatedCoords[0], previousStep.distance);
2655
+ accumulatedIndex += result.length - 1;
2656
+
2657
+ duplicatedCoords.splice(0, result.length - 1);
2658
+
2659
+ step._idCoordsInLeg = accumulatedIndex;
2660
+
2661
+ newCoords = last(result);
2662
+
2663
+ coords[step._idCoordsInLeg] = newCoords;
2664
+ }
2665
+
2666
+ step.coords = newCoords;
2667
+
2668
+ previousStep = step;
2669
+ }
2670
+ }
2671
+
2672
+ /**
2673
+ * @param {string} stringDate (e.g. 20211117T104516)
2674
+ * @returns {number}
2675
+ */
2676
+ dateStringToTimestamp(stringDate, timeZone) {
2677
+ const yearStr = stringDate.substr(0, 4);
2678
+ const monthStr = stringDate.substr(4, 2);
2679
+ const dayStr = stringDate.substr(6, 2);
2680
+ const hoursStr = stringDate.substr(9, 2);
2681
+ const minutesStr = stringDate.substr(11, 2);
2682
+ const secondsStr = stringDate.substr(13, 2);
2683
+
2684
+ return dateWithTimeZone(
2685
+ Number(yearStr),
2686
+ Number(monthStr) - 1,
2687
+ Number(dayStr),
2688
+ Number(hoursStr),
2689
+ Number(minutesStr),
2690
+ Number(secondsStr),
2691
+ timeZone
2692
+ ).getTime();
2693
+ }
2694
+
2695
+ /**
2696
+ * Generate multi itineraries from OTP JSON
2697
+ * @param {object} json JSON file provided by OTP.
2698
+ * @returns {?RouterResponse}
2699
+ */
2700
+ createRouterResponseFromJson(json) {
2701
+
2702
+ if (!json || !json.journeys) {
2703
+ return null;
2704
+ }
2705
+
2706
+ const routerResponse = new RouterResponse();
2707
+ routerResponse.routerName = this.rname;
2708
+
2709
+ routerResponse.from = this.getSectionCoords(json.journeys[0].sections[0]).from;
2710
+ routerResponse.to = this.getSectionCoords(last(json.journeys[0].sections)).to;
2711
+
2712
+ const timeZone = json.context.timezone;
2713
+
2714
+ for (const jsonItinerary of json.journeys) {
2715
+
2716
+ const itinerary = new Itinerary();
2717
+
2718
+ itinerary.duration = jsonItinerary.duration;
2719
+ itinerary.startTime = this.dateStringToTimestamp(jsonItinerary.departure_date_time, timeZone);
2720
+ itinerary.endTime = this.dateStringToTimestamp(jsonItinerary.arrival_date_time, timeZone);
2721
+ itinerary.from = routerResponse.from;
2722
+ itinerary.to = routerResponse.to;
2723
+ itinerary.distance = 0;
2724
+
2725
+ routerResponse.itineraries.push(itinerary);
2726
+
2727
+ for (const jsonSection of jsonItinerary.sections) {
2728
+
2729
+ if (jsonSection.type === 'waiting' || jsonSection.type === 'transfer') {
2730
+ continue;
2731
+ }
2732
+
2733
+ const leg = new Leg();
2734
+ let existingCoords = [];
2735
+ const { from, to } = this.getSectionCoords(jsonSection);
2736
+
2737
+ leg.distance = 0;
2738
+ leg.mode = routingModeCorrespondance.get(jsonSection.mode);
2739
+ leg.duration = jsonSection.duration;
2740
+ leg.startTime = this.dateStringToTimestamp(jsonSection.departure_date_time, timeZone);
2741
+ leg.endTime = this.dateStringToTimestamp(jsonSection.arrival_date_time, timeZone);
2742
+
2743
+ leg.from = {
2744
+ name: jsonSection.from.name,
2745
+ coords: from
2746
+ };
2747
+
2748
+ leg.to = {
2749
+ name: jsonSection.to.name,
2750
+ coords: to
2751
+ };
2752
+
2753
+ // A section can have multiple same coordinates, we need to remove them
2754
+ leg.coords = jsonSection.geojson.coordinates.reduce((acc, [lon, lat]) => {
2755
+ if (!existingCoords.includes(`${lon}-${lat}`)) {
2756
+ existingCoords = existingCoords.concat(`${lon}-${lat}`);
2757
+ acc.push(new Coordinates(lat, lon));
2758
+ }
2759
+
2760
+ return acc;
2761
+ }, []);
2762
+
2763
+ leg.steps = [];
2764
+
2765
+ if (jsonSection.path) {
2766
+ for (const jsonPathLink of jsonSection.path) {
2767
+ const step = new Step();
2768
+
2769
+ step.levelChange = null;
2770
+
2771
+ step.name = jsonPathLink.name;
2772
+ step.distance = jsonPathLink.length;
2773
+
2774
+ leg.distance += step.distance;
2775
+ leg.steps.push(step);
2776
+ }
2777
+
2778
+ this.findStepsCoord(leg);
2779
+ }
2780
+
2781
+ if (jsonSection.type === 'public_transport') {
2782
+ leg.transportInfo = {
2783
+ name: jsonSection.display_informations.code,
2784
+ routeColor: jsonSection.display_informations.color,
2785
+ routeTextColor: jsonSection.display_informations.text_color,
2786
+ directionName: jsonSection.display_informations.direction
2787
+ };
2788
+
2789
+ leg.mode = routingModeCorrespondance.get(jsonSection.display_informations.physical_mode);
2790
+
2791
+ const legStep = new Step();
2792
+ legStep.coords = leg.coords[0];
2793
+ legStep._idCoordsInLeg = 0;
2794
+ legStep.name = leg.transportInfo.directionName;
2795
+ legStep.levelChange = null;
2796
+ legStep.distance = jsonSection.geojson.properties[0].length;
2797
+
2798
+ leg.steps = [legStep];
2799
+ }
2800
+
2801
+ itinerary.distance += leg.distance;
2802
+
2803
+ itinerary.legs.push(leg);
2804
+
2805
+ }
2806
+
2807
+ // All legs have to be parsed before computing steps metadata
2808
+ generateStepsMetadata(itinerary);
2809
+ }
2810
+
2811
+ return routerResponse;
2812
+ }
2813
+ }
2814
+
2815
+ var IdfmRemoteRouter$1 = new IdfmRemoteRouter();
2816
+
2817
+ /* eslint-disable max-statements */
2818
+
2819
+
2820
+ /**
2821
+ * Singleton.
2822
+ */
2823
+ class WemapMetaRemoteRouter extends RemoteRouter {
2824
+
2825
+ /**
2826
+ * @override
2827
+ */
2828
+ get rname() {
2829
+ return 'wemap-meta';
2830
+ }
2831
+
2832
+ /**
2833
+ * @override
2834
+ */
2835
+ async getItineraries(endpointUrl, mode, waypoints, options) {
2836
+ const url = this.getURL(endpointUrl, mode, waypoints, options);
2837
+ const response = await request(url);
2838
+ return RouterResponse.fromJson(response);
2839
+ }
2840
+
2841
+ /**
2842
+ * @param {string} endpointUrl
2843
+ * @param {string} mode
2844
+ * @param {Array<Coordinates>} waypoints
2845
+ * @param {WemapMetaRemoteRouterOptions} options
2846
+ */
2847
+ getURL(endpointUrl, mode, waypoints, options) {
2848
+
2849
+ const url = new URL(endpointUrl);
2850
+
2851
+ url.pathname = url.pathname + waypoints.map(waypoint => {
2852
+ if (waypoint.level) {
2853
+ const altitude = waypoint.level.isRange ? waypoint.level.low : waypoint.level.val;
2854
+ return waypoint.longitude + ',' + waypoint.latitude + ',' + altitude;
2855
+ }
2856
+ return waypoint.longitude + ',' + waypoint.latitude;
2857
+ }).join(';');
2858
+
2859
+ url.searchParams.set('externalRouterName', options.externalRouterName);
2860
+ url.searchParams.set('externalRouterEndpoint', encodeURIComponent(options.externalRouterEndpointUrl));
2861
+ url.searchParams.set('externalRouterMode', mode);
2862
+
2863
+ return url;
2864
+ }
2865
+ }
2866
+
2867
+ var WemapMetaRemoteRouter$1 = new WemapMetaRemoteRouter();
2868
+
2869
+ /* eslint-disable max-statements */
2870
+
2871
+ class ItineraryInfoManager {
2872
+
2873
+ /** @type {Itinerary} */
2874
+ _itinerary;
2875
+
2876
+ /** @type {Network} */
2877
+ _network;
2878
+
2879
+ /** @type {Network} */
2880
+ _mapMatching;
2881
+
2882
+ /** @type {Step[]} */
2883
+ _steps;
2884
+
2885
+ /** @type {Step[]} */
2886
+ _coordsNextStep;
2887
+
2888
+ /** @type {Step[]} */
2889
+ _coordsPreviousStep;
2890
+
2891
+ /** @type {number[]} */
2892
+ _coordsDistanceTraveled;
2893
+
2894
+ /** @type {Leg[]} */
2895
+ _coordsLeg;
2896
+
2897
+ /** @type {Itinerary} */
2898
+ set itinerary(itinerary) {
2899
+
2900
+ if (itinerary === null) {
2901
+ this._itinerary = null;
2902
+ return;
2903
+ }
2904
+
2905
+ this._itinerary = itinerary;
2906
+ this._steps = itinerary.steps;
2907
+ this._network = itinerary.toNetwork();
2908
+ this._mapMatching = new MapMatching(this._network);
2909
+
2910
+ this._coordsNextStep = new Array(itinerary.coords.length);
2911
+ this._coordsPreviousStep = new Array(itinerary.coords.length);
2912
+ this._coordsDistanceTraveled = new Array(itinerary.coords.length);
2913
+ this._coordsLeg = new Array(itinerary.coords.length);
2914
+
2915
+ let stepId = 0;
2916
+ let previousStep = null;
2917
+ let nextStep = this._steps[0];
2918
+ let distanceTraveled = 0;
2919
+
2920
+ itinerary.coords.forEach((coords, idx, arr) => {
2921
+ if (stepId < this._steps.length && this._steps[stepId].coords.equalsTo(coords)) {
2922
+ previousStep = this._steps[stepId];
2923
+ nextStep = stepId === this._steps.length - 1 ? null : this._steps[stepId + 1];
2924
+ stepId++;
2925
+ }
2926
+ if (idx !== 0) {
2927
+ distanceTraveled += arr[idx - 1].distanceTo(coords);
2928
+ }
2929
+
2930
+ this._coordsNextStep[idx] = nextStep;
2931
+ this._coordsPreviousStep[idx] = previousStep;
2932
+ this._coordsDistanceTraveled[idx] = distanceTraveled;
2933
+ this._coordsLeg[idx] = itinerary.legs.find(leg => leg.coords.includes(coords));
2934
+ });
2935
+ }
2936
+
2937
+ /**
2938
+ * @param {Coordinates} position
2939
+ * @returns {ItineraryInfo}
2940
+ */
2941
+ getInfo(position) {
2942
+
2943
+ if (!this._itinerary) {
2944
+ return null;
2945
+ }
2946
+
2947
+ if (!(position instanceof Coordinates)) {
2948
+ return null;
2949
+ }
2950
+
2951
+ const projection = this._mapMatching.getProjection(position);
2952
+ if (!projection) {
2953
+ return null;
2954
+ }
2955
+
2956
+ let itineraryInfo = null;
2957
+
2958
+ if (projection.nearestElement instanceof GraphNode) {
2959
+ const idx = this._itinerary.coords.findIndex(
2960
+ coords => projection.nearestElement.coords === coords
2961
+ );
2962
+ if (idx === -1) {
2963
+ throw new Error('ItineraryInfoManager: could not find projection in itinerary (Node)');
2964
+ }
2965
+
2966
+ itineraryInfo = new ItineraryInfo();
2967
+ itineraryInfo.nextStep = this._coordsNextStep[idx];
2968
+ itineraryInfo.previousStep = this._coordsPreviousStep[idx];
2969
+ itineraryInfo.projection = projection;
2970
+ itineraryInfo.leg = this._coordsLeg[idx];
2971
+ itineraryInfo.traveledDistance = this._coordsDistanceTraveled[idx];
2972
+ itineraryInfo.remainingDistance = this._itinerary.distance - itineraryInfo.traveledDistance;
2973
+ itineraryInfo.traveledPercentage = itineraryInfo.traveledDistance / this._itinerary.distance;
2974
+ itineraryInfo.remainingPercentage = itineraryInfo.remainingDistance / this._itinerary.distance;
2975
+
2976
+ } else if (projection.nearestElement instanceof GraphEdge) {
2977
+
2978
+ let firstNode = projection.nearestElement.node1.coords;
2979
+ let idx = this._itinerary.coords.findIndex(coords => firstNode === coords);
2980
+ if (idx === -1) {
2981
+ throw new Error('ItineraryInfoManager: could not find projection in itinerary (Edge)');
2982
+ }
2983
+
2984
+ // graphEdge is not necessarly ordered. We have to look for the first point
2985
+ if (idx === this._itinerary.coords.length - 1
2986
+ || this._itinerary.coords[idx + 1] !== projection.nearestElement.node2.coords
2987
+ ) {
2988
+ firstNode = projection.nearestElement.node2.coords;
2989
+ idx--;
2990
+ }
2991
+
2992
+ itineraryInfo = new ItineraryInfo();
2993
+ itineraryInfo.nextStep = this._coordsNextStep[idx];
2994
+ itineraryInfo.previousStep = this._coordsPreviousStep[idx];
2995
+ itineraryInfo.projection = projection;
2996
+ itineraryInfo.leg = this._coordsLeg[idx];
2997
+ itineraryInfo.traveledDistance = this._coordsDistanceTraveled[idx]
2998
+ + projection.projection.distanceTo(firstNode);
2999
+ itineraryInfo.remainingDistance = this._itinerary.distance - itineraryInfo.traveledDistance;
3000
+ itineraryInfo.traveledPercentage = itineraryInfo.traveledDistance / this._itinerary.distance;
3001
+ itineraryInfo.remainingPercentage = itineraryInfo.remainingDistance / this._itinerary.distance;
3002
+
3003
+ }
3004
+
3005
+ return itineraryInfo;
3006
+ }
3007
+
3008
+ }
3009
+
3010
+ export { CitywayRemoteRouter$1 as CitywayRemoteRouter, DeutscheBahnRemoteRouter$1 as DeutscheBahnRemoteRouter, IdfmRemoteRouter$1 as IdfmRemoteRouter, Itinerary, ItineraryInfo, ItineraryInfoManager, Leg, LevelChange, OsrmRemoteRouter$1 as OsrmRemoteRouter, OtpRemoteRouter$1 as OtpRemoteRouter, RouterResponse, Step, WemapMetaRemoteRouter$1 as WemapMetaRemoteRouter, WemapNetworkUtils, WemapRouter, WemapRouterOptions, WemapRouterUtils, getDurationFromLength };
3011
+ //# sourceMappingURL=wemap-routers.es.js.map