@wemap/routers 8.0.1 → 8.1.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.
@@ -0,0 +1,3833 @@
1
+ import { Coordinates, Network, GraphRouterOptions, GraphRouter, GraphUtils, Level, GraphEdge, GraphNode, NoRouteFoundError, GraphItinerary, Utils, MapMatching } from '@wemap/geo';
2
+ import { OsmParser, OsmWay, OsmNode } from '@wemap/osm';
3
+ import { deg2rad, diffAngle, positiveMod, rad2deg } from '@wemap/maths';
4
+ import Logger from '@wemap/logger';
5
+ import pointInPolygon from '@turf/boolean-point-in-polygon';
6
+ import convexHullFn from '@turf/convex';
7
+ import { polygon } from '@turf/helpers';
8
+ import Polyline from '@mapbox/polyline';
9
+
10
+ class LevelChange {
11
+
12
+ /** @type {!string} [up|down] */
13
+ direction;
14
+
15
+ /** @type {!number} [-2, -1, 1, ...] */
16
+ difference;
17
+
18
+ /** @type {?string} [elevator|conveyor|stairs] */
19
+ type = null;
20
+
21
+ /**
22
+ * @param {LevelChange} obj1
23
+ * @param {LevelChange} obj2
24
+ * @returns {Boolean}
25
+ */
26
+ static equalsTo(obj1, obj2) {
27
+ return obj1.difference === obj2.difference
28
+ && obj1.direction === obj2.direction
29
+ && obj1.type === obj2.type;
30
+ }
31
+
32
+ /**
33
+ * @param {LevelChange} obj
34
+ * @returns {Boolean}
35
+ */
36
+ equalsTo(obj) {
37
+ return LevelChange.equalsTo(this, obj);
38
+ }
39
+
40
+ /**
41
+ * @returns {object}
42
+ */
43
+ toJson() {
44
+ return {
45
+ direction: this.direction,
46
+ difference: this.difference,
47
+ type: this.type
48
+ };
49
+ }
50
+
51
+ /**
52
+ * @param {object} json
53
+ * @returns {LevelChange}
54
+ */
55
+ static fromJson(json) {
56
+ const levelChange = new LevelChange();
57
+ levelChange.direction = json.direction;
58
+ levelChange.difference = json.difference;
59
+ levelChange.type = json.type;
60
+ return levelChange;
61
+ }
62
+ }
63
+
64
+ class Step {
65
+
66
+ /** @type {!boolean} */
67
+ firstStep = false;
68
+
69
+ /** @type {!boolean} */
70
+ lastStep = false;
71
+
72
+ /** @type {!number} */
73
+ number;
74
+
75
+ /** @type {!Coordinates} */
76
+ coords = [];
77
+
78
+
79
+ /** @type {!number} */
80
+ angle;
81
+
82
+ /** @type {!number} */
83
+ previousBearing;
84
+
85
+ /** @type {!number} */
86
+ nextBearing;
87
+
88
+
89
+ /** @type {!number} */
90
+ distance;
91
+
92
+ /** @type {?number} */
93
+ duration = null;
94
+
95
+ /** @type {?string} */
96
+ name = null;
97
+
98
+
99
+ /** @type {?LevelChange} */
100
+ levelChange = null;
101
+
102
+ /** @type {?{?subwayEntrance: boolean, ?subwayEntranceRef: string}} */
103
+ extras = {};
104
+
105
+ /** @type {!number} */
106
+ _idCoordsInLeg = null;
107
+
108
+ /**
109
+ * @param {Step} obj1
110
+ * @param {Step} obj2
111
+ * @returns {Boolean}
112
+ */
113
+ static equalsTo(obj1, obj2) {
114
+ return obj1.firstStep === obj2.firstStep
115
+ && obj1.lastStep === obj2.lastStep
116
+ && obj1.number === obj2.number
117
+ && obj1.coords.equalsTo(obj2.coords)
118
+ && obj1.angle === obj2.angle
119
+ && obj1.previousBearing === obj2.previousBearing
120
+ && obj1.nextBearing === obj2.nextBearing
121
+ && obj1.distance === obj2.distance
122
+ && obj1.duration === obj2.duration
123
+ && obj1.name === obj2.name
124
+ && (
125
+ obj1.levelChange === obj2.levelChange
126
+ || obj1.levelChange !== null && obj1.levelChange.equalsTo(obj2.levelChange)
127
+ )
128
+ && (
129
+ obj1.extras === obj2.extras
130
+ || (
131
+ obj1.extras !== null
132
+ && obj1.extras.subwayEntrance === obj2.extras.subwayEntrance
133
+ && obj1.extras.subwayEntranceRef === obj2.extras.subwayEntranceRef
134
+ )
135
+ )
136
+ && obj1._idCoordsInLeg === obj2._idCoordsInLeg;
137
+ }
138
+
139
+ /**
140
+ * @param {Step} obj
141
+ * @returns {Boolean}
142
+ */
143
+ equalsTo(obj) {
144
+ return Step.equalsTo(this, obj);
145
+ }
146
+
147
+ /**
148
+ * @returns {object}
149
+ */
150
+ toJson() {
151
+ const output = {
152
+ number: this.number,
153
+ coords: this.coords.toCompressedJson(),
154
+ angle: this.angle,
155
+ previousBearing: this.previousBearing,
156
+ nextBearing: this.nextBearing,
157
+ distance: this.distance,
158
+ _idCoordsInLeg: this._idCoordsInLeg
159
+ };
160
+ if (this.firstStep) {
161
+ output.firstStep = true;
162
+ }
163
+ if (this.lastStep) {
164
+ output.lastStep = true;
165
+ }
166
+ if (this.duration !== null) {
167
+ output.duration = this.duration;
168
+ }
169
+ if (this.name !== null) {
170
+ output.name = this.name;
171
+ }
172
+ if (this.levelChange !== null) {
173
+ output.levelChange = this.levelChange.toJson();
174
+ }
175
+ if (this.extras && Object.keys(this.extras).length !== 0) {
176
+ output.extras = this.extras;
177
+ }
178
+ return output;
179
+ }
180
+
181
+ /**
182
+ * @param {object} json
183
+ * @returns {Step}
184
+ */
185
+ static fromJson(json) {
186
+ const step = new Step();
187
+ step.number = json.number;
188
+ step.coords = Coordinates.fromCompressedJson(json.coords);
189
+ step.angle = json.angle;
190
+ step.previousBearing = json.previousBearing;
191
+ step.nextBearing = json.nextBearing;
192
+ step.distance = json.distance;
193
+ step._idCoordsInLeg = json._idCoordsInLeg;
194
+ if (typeof json.firstStep === 'boolean') {
195
+ step.firstStep = json.firstStep;
196
+ }
197
+ if (typeof json.lastStep === 'boolean') {
198
+ step.lastStep = json.lastStep;
199
+ }
200
+ if (typeof json.duration === 'number') {
201
+ step.duration = json.duration;
202
+ }
203
+ if (typeof json.name === 'string') {
204
+ step.name = json.name;
205
+ }
206
+ if (typeof json.levelChange === 'object') {
207
+ step.levelChange = LevelChange.fromJson(json.levelChange);
208
+ }
209
+ if (typeof json.extras === 'object') {
210
+ step.extras = json.extras;
211
+ }
212
+ return step;
213
+ }
214
+ }
215
+
216
+ const Constants = {};
217
+
218
+ Constants.ROUTING_MODE = {
219
+ AIRPLANE: 'AIRPLANE',
220
+ BOAT: 'BOAT',
221
+ BIKE: 'BIKE',
222
+ BUS: 'BUS',
223
+ CAR: 'CAR',
224
+ FERRY: 'FERRY',
225
+ FUNICULAR: 'FUNICULAR',
226
+ METRO: 'METRO',
227
+ MOTO: 'MOTO',
228
+ TRAIN: 'TRAIN',
229
+ TAXI: 'TAXI',
230
+ TRAM: 'TRAM',
231
+ WALK: 'WALK',
232
+ MULTI: 'MULTI',
233
+ UNKNOWN: 'UNKNOWN'
234
+ };
235
+
236
+ Constants.PUBLIC_TRANSPORT = [
237
+ Constants.ROUTING_MODE.AIRPLANE,
238
+ Constants.ROUTING_MODE.BOAT,
239
+ Constants.ROUTING_MODE.BUS,
240
+ Constants.ROUTING_MODE.FERRY,
241
+ Constants.ROUTING_MODE.FUNICULAR,
242
+ Constants.ROUTING_MODE.METRO,
243
+ Constants.ROUTING_MODE.TRAIN,
244
+ Constants.ROUTING_MODE.TRAM
245
+ ];
246
+
247
+ class Leg {
248
+
249
+ /** @type {!string} can be values in Constants.ROUTING_MODE */
250
+ mode;
251
+
252
+ /** @type {!number} */
253
+ distance;
254
+
255
+ /** @type {!number} */
256
+ duration;
257
+
258
+ /** @type {?number} */
259
+ startTime = null;
260
+
261
+ /** @type {?number} */
262
+ endTime = null;
263
+
264
+ /** @type {!{name: ?string, coords: !Coordinates}} */
265
+ from;
266
+
267
+ /** @type {!{name: ?string, coords: !Coordinates}} */
268
+ to;
269
+
270
+ /** @type {!Coordinates[]} */
271
+ coords;
272
+
273
+ /** @type {?{name: !string, routeColor: ?string, routeTextColor: ?string, directionName: ?string}} */
274
+ transportInfo = null;
275
+
276
+ /** @type {?(Step[])} */
277
+ steps = null;
278
+
279
+ isPublicTransport() {
280
+ return Constants.PUBLIC_TRANSPORT.includes(this.mode);
281
+ }
282
+
283
+ /**
284
+ * @returns {Network}
285
+ */
286
+ toNetwork() {
287
+ return Network.fromCoordinates([this.coords]);
288
+ }
289
+
290
+
291
+ /**
292
+ * @param {Leg} obj1
293
+ * @param {Leg} obj2
294
+ * @returns {Boolean}
295
+ */
296
+ // eslint-disable-next-line complexity
297
+ static equalsTo(obj1, obj2) {
298
+ const intermediate = obj1.mode === obj2.mode
299
+ && obj1.distance === obj2.distance
300
+ && obj1.duration === obj2.duration
301
+ && obj1.startTime === obj2.startTime
302
+ && obj1.endTime === obj2.endTime
303
+ && obj1.from.name === obj2.from.name
304
+ && obj1.from.coords.equalsTo(obj2.from.coords)
305
+ && obj1.to.name === obj2.to.name
306
+ && obj1.to.coords.equalsTo(obj2.to.coords)
307
+ && obj1.coords.length === obj2.coords.length
308
+ && (
309
+ obj1.steps === obj2.steps
310
+ || obj1.steps.length === obj2.steps.length
311
+ );
312
+
313
+ if (!intermediate) {
314
+ return false;
315
+ }
316
+
317
+ let i;
318
+ for (i = 0; i < obj1.coords.length; i++) {
319
+ if (!obj1.coords[i].equalsTo(obj2.coords[i])) {
320
+ return false;
321
+ }
322
+ }
323
+ if (obj1.steps) {
324
+ for (i = 0; i < obj1.steps.length; i++) {
325
+ if (!obj1.steps[i].equalsTo(obj2.steps[i])) {
326
+ return false;
327
+ }
328
+ }
329
+ }
330
+
331
+ if (obj1.transportInfo !== obj2.transportInfo) {
332
+ if (obj1.transportInfo === null) {
333
+ return false;
334
+ }
335
+ if (
336
+ obj1.transportInfo.name !== obj2.transportInfo.name
337
+ || obj1.transportInfo.routeColor !== obj2.transportInfo.routeColor
338
+ || obj1.transportInfo.routeTextColor !== obj2.transportInfo.routeTextColor
339
+ || obj1.transportInfo.directionName !== obj2.transportInfo.directionName
340
+ ) {
341
+ return false;
342
+ }
343
+ }
344
+
345
+ return true;
346
+ }
347
+
348
+ /**
349
+ * @param {Leg} obj
350
+ * @returns {Boolean}
351
+ */
352
+ equalsTo(obj) {
353
+ return Leg.equalsTo(this, obj);
354
+ }
355
+
356
+ /**
357
+ * @returns {object}
358
+ */
359
+ toJson() {
360
+ const output = {
361
+ mode: this.mode,
362
+ from: { coords: this.from.coords.toCompressedJson() },
363
+ to: { coords: this.to.coords.toCompressedJson() },
364
+ distance: this.distance,
365
+ duration: this.duration,
366
+ coords: this.coords.map(coords => coords.toCompressedJson())
367
+ };
368
+ if (this.startTime !== null) {
369
+ output.startTime = this.startTime;
370
+ }
371
+ if (this.endTime !== null) {
372
+ output.endTime = this.endTime;
373
+ }
374
+ if (this.from.name !== null) {
375
+ output.from.name = this.from.name;
376
+ }
377
+ if (this.to.name !== null) {
378
+ output.to.name = this.to.name;
379
+ }
380
+ if (this.transportInfo !== null) {
381
+ output.transportInfo = this.transportInfo;
382
+ }
383
+ if (this.steps !== null && this.steps.length > 0) {
384
+ output.steps = this.steps.map(step => step.toJson());
385
+ }
386
+ return output;
387
+ }
388
+
389
+
390
+ /**
391
+ * @param {object} json
392
+ * @returns {Leg}
393
+ */
394
+ static fromJson(json) {
395
+ const leg = new Leg();
396
+ leg.mode = json.mode;
397
+ leg.distance = json.distance;
398
+ leg.duration = json.duration;
399
+
400
+ if (typeof json.startTime === 'number') {
401
+ leg.startTime = json.startTime;
402
+ }
403
+ if (typeof json.endTime === 'number') {
404
+ leg.endTime = json.endTime;
405
+ }
406
+
407
+ leg.from = {
408
+ coords: Coordinates.fromCompressedJson(json.from.coords),
409
+ name: typeof json.from.name === 'string' ? json.from.name : null
410
+ };
411
+ leg.to = {
412
+ coords: Coordinates.fromCompressedJson(json.to.coords),
413
+ name: typeof json.to.name === 'string' ? json.to.name : null
414
+ };
415
+
416
+ leg.coords = json.coords.map(Coordinates.fromCompressedJson);
417
+
418
+ if (typeof json.transportInfo === 'object') {
419
+ leg.transportInfo = json.transportInfo;
420
+ }
421
+ if (typeof json.steps === 'object') {
422
+ leg.steps = json.steps.map(Step.fromJson);
423
+ }
424
+ return leg;
425
+ }
426
+
427
+ }
428
+
429
+ /**
430
+ * Get route duration
431
+ * @param {Number} speed in km/h
432
+ * @returns {Number} duration in seconds
433
+ */
434
+ function getDurationFromLength(length, speed = 5) {
435
+ return length / (speed * 1000 / 3600);
436
+ }
437
+
438
+ /* eslint-disable max-statements */
439
+ /* eslint-disable max-depth */
440
+ /**
441
+ * @param {RouterResponse} routerResponse
442
+ * @param {Number} levelFactor
443
+ */
444
+ function multiplyRouterResponseLevel(routerResponse, levelFactor) {
445
+ if (routerResponse.from.level) {
446
+ routerResponse.from.level.multiplyBy(levelFactor);
447
+ }
448
+ if (routerResponse.to.level) {
449
+ routerResponse.to.level.multiplyBy(levelFactor);
450
+ }
451
+ for (const itinerary of routerResponse.itineraries) {
452
+ if (itinerary.from.level) {
453
+ itinerary.from.level.multiplyBy(levelFactor);
454
+ }
455
+ if (itinerary.to.level) {
456
+ itinerary.to.level.multiplyBy(levelFactor);
457
+ }
458
+
459
+ for (const leg of itinerary.legs) {
460
+ if (leg.from.coords.level) {
461
+ leg.from.coords.level.multiplyBy(levelFactor);
462
+ }
463
+ if (leg.to.coords.level) {
464
+ leg.to.coords.level.multiplyBy(levelFactor);
465
+ }
466
+ for (const coords of leg.coords) {
467
+ if (coords.level) {
468
+ coords.level.multiplyBy(levelFactor);
469
+ }
470
+ }
471
+ if (leg.steps) {
472
+ for (const step of leg.steps) {
473
+ if (step.coords.level) {
474
+ step.coords.level.multiplyBy(levelFactor);
475
+ }
476
+ }
477
+ }
478
+ }
479
+
480
+ if (itinerary._coords) {
481
+ for (const coords of itinerary._coords) {
482
+ if (coords.level) {
483
+ coords.level.multiplyBy(levelFactor);
484
+ }
485
+ }
486
+ }
487
+ }
488
+ }
489
+
490
+ /* eslint-disable max-statements */
491
+
492
+ /**
493
+ * Main attributes are:
494
+ * nodes: the ordered list of Node
495
+ * edges: the ordered list of Edge
496
+ * start: the start point (Coordinates)
497
+ * end: the end point (Coordinates)
498
+ * length: the route length
499
+ */
500
+ class Itinerary {
501
+
502
+ /** @type {!Coordinates} */
503
+ from;
504
+
505
+ /** @type {!Coordinates} */
506
+ to;
507
+
508
+ /** @type {!number} */
509
+ distance;
510
+
511
+ /** @type {!number} */
512
+ duration;
513
+
514
+ /** @type {!string} can be WALK, BIKE, CAR, PT */
515
+ _mode;
516
+
517
+ /** @type {?number} */
518
+ startTime = null;
519
+
520
+ /** @type {?number} */
521
+ endTime = null;
522
+
523
+ /** @type {!(Leg[])} */
524
+ legs = [];
525
+
526
+ /** @type {?Coordinates[]} */
527
+ _coords = null;
528
+
529
+ set coords(_) {
530
+ throw new Error('Itinerary.coords cannot be set. They are calculated from Itinerary.legs.');
531
+ }
532
+
533
+ /** @type {!(Coordinates[])} */
534
+ get coords() {
535
+ if (!this._coords) {
536
+ // Returns the coordinates contained in all legs and remove duplicates between array
537
+ this._coords = this.legs.reduce((acc, val) => {
538
+ const isDuplicate = acc.length && val.coords.length && acc[acc.length - 1].equalsTo(val.coords[0]);
539
+ acc.push(...val.coords.slice(isDuplicate ? 1 : 0));
540
+ return acc;
541
+ }, []);
542
+ }
543
+ return this._coords;
544
+ }
545
+
546
+ set steps(_) {
547
+ throw new Error('Itinerary.step cannot be set. They are calculated from Itinerary.legs.');
548
+ }
549
+
550
+ /** @type {!(Step[])} */
551
+ get steps() {
552
+ return this.legs.map(leg => leg.steps).flat();
553
+ }
554
+
555
+ set mode(_) {
556
+ throw new Error('Itinerary.mode cannot be set. They are calculated from Itinerary.legs.');
557
+ }
558
+
559
+ get mode() {
560
+ if (!this._mode) {
561
+ let isPublicTransport = false;
562
+ let isBicycle = false;
563
+ let isDriving = false;
564
+
565
+ this.legs.forEach((leg) => {
566
+ isPublicTransport = isPublicTransport || Constants.PUBLIC_TRANSPORT.includes(leg.mode);
567
+ isBicycle = isBicycle || leg.mode === Constants.ROUTING_MODE.BIKE;
568
+ isDriving = isDriving || leg.mode === Constants.ROUTING_MODE.CAR;
569
+ });
570
+
571
+ if (isPublicTransport) {
572
+ this._mode = 'PT';
573
+ } else if (isDriving) {
574
+ this._mode = 'CAR';
575
+ } else if (isBicycle) {
576
+ this._mode = 'BIKE';
577
+ } else {
578
+ this._mode = 'WALK';
579
+ }
580
+ }
581
+
582
+ return this._mode;
583
+
584
+ }
585
+
586
+ /**
587
+ * @returns {Network}
588
+ */
589
+ toNetwork() {
590
+ return Network.fromCoordinates([this.coords]);
591
+ }
592
+
593
+ /**
594
+ * @param {Itinerary[]} itineraries
595
+ * @returns {Itinerary}
596
+ */
597
+ static fromItineraries(...itineraries) {
598
+ const itinerary = new Itinerary();
599
+ itinerary.from = itineraries[0].from;
600
+ itinerary.to = itineraries[itineraries.length - 1].to;
601
+ itinerary.distance = 0;
602
+ itinerary.duration = 0;
603
+ itinerary.legs = [];
604
+
605
+ itineraries.forEach(_itinerary => {
606
+ itinerary.distance += _itinerary.distance;
607
+ itinerary.duration += _itinerary.duration;
608
+ itinerary.legs.push(..._itinerary.legs);
609
+ itinerary.legs.forEach(leg => {
610
+ leg.steps[0].firstStep = false;
611
+ leg.steps[leg.steps.length - 1].lastStep = false;
612
+ });
613
+ });
614
+
615
+ itinerary.legs[0].steps[0].firstStep = true;
616
+ const lastLeg = itinerary.legs[itinerary.legs.length - 1];
617
+ lastLeg.steps[lastLeg.steps.length - 1].lastStep = true;
618
+
619
+ return itinerary;
620
+ }
621
+
622
+ /**
623
+ * Convert lat/lng/level points to Itinerary
624
+ * @param {number[][]} points 2D points array of lat/lng/level (level is optional)
625
+ * @param {Coordinates} from
626
+ * @param {Coordinates} to
627
+ * @param {string} mode
628
+ * @returns {Itinerary}
629
+ */
630
+ static fromOrderedPointsArray(points, start, end) {
631
+
632
+ const pointToCoordinates = point => new Coordinates(point[0], point[1], null, point[2]);
633
+
634
+ return this.fromOrderedCoordinates(
635
+ points.map(pointToCoordinates),
636
+ pointToCoordinates(start),
637
+ pointToCoordinates(end)
638
+ );
639
+ }
640
+
641
+ /**
642
+ * Convert ordered Coordinates to Itinerary
643
+ * @param {Coordinates[]} points
644
+ * @param {Coordinates} from
645
+ * @param {Coordinates} to
646
+ * @param {string} mode
647
+ * @returns {Itinerary}
648
+ */
649
+ static fromOrderedCoordinates(points, from, to, mode = 'WALK') {
650
+
651
+ const itinerary = new Itinerary();
652
+ itinerary.from = from;
653
+ itinerary.to = to;
654
+
655
+ const leg = new Leg();
656
+ leg.mode = mode;
657
+ leg.from = { name: null, coords: from };
658
+ leg.to = { name: null, coords: to };
659
+
660
+ leg.coords = points;
661
+ leg.distance = points.reduce((acc, coords, idx, arr) => {
662
+ if (idx !== 0) {
663
+ return acc + arr[idx - 1].distanceTo(coords);
664
+ }
665
+ return acc;
666
+ }, 0);
667
+ leg.duration = getDurationFromLength(leg.distance);
668
+ itinerary.legs.push(leg);
669
+
670
+ itinerary.distance = leg.distance;
671
+ itinerary.duration = leg.duration;
672
+
673
+ return itinerary;
674
+ }
675
+
676
+
677
+ /**
678
+ * @param {Itinerary} obj1
679
+ * @param {Itinerary} obj2
680
+ * @returns {Boolean}
681
+ */
682
+ static equalsTo(obj1, obj2) {
683
+ const intermediate = obj1.from.equalsTo(obj2.from)
684
+ && obj1.to.equalsTo(obj2.to)
685
+ && obj1.distance === obj2.distance
686
+ && obj1.duration === obj2.duration
687
+ && obj1.startTime === obj2.startTime
688
+ && obj1.endTime === obj2.endTime
689
+ && obj1.legs.length === obj2.legs.length;
690
+
691
+ if (!intermediate) {
692
+ return false;
693
+ }
694
+
695
+ for (let i = 0; i < obj1.legs.length; i++) {
696
+ if (!obj1.legs[i].equalsTo(obj2.legs[i])) {
697
+ return false;
698
+ }
699
+ }
700
+
701
+ return true;
702
+ }
703
+
704
+ /**
705
+ * @param {Itinerary} obj
706
+ * @returns {Boolean}
707
+ */
708
+ equalsTo(obj) {
709
+ return Itinerary.equalsTo(this, obj);
710
+ }
711
+
712
+ /**
713
+ * @returns {object}
714
+ */
715
+ toJson() {
716
+ const output = {
717
+ from: this.from.toCompressedJson(),
718
+ to: this.to.toCompressedJson(),
719
+ distance: this.distance,
720
+ duration: this.duration,
721
+ mode: this.mode,
722
+ legs: this.legs.map(leg => leg.toJson())
723
+ };
724
+ if (this.startTime !== null) {
725
+ output.startTime = this.startTime;
726
+ }
727
+ if (this.endTime !== null) {
728
+ output.endTime = this.endTime;
729
+ }
730
+ return output;
731
+ }
732
+
733
+ /**
734
+ * @param {object} json
735
+ * @returns {Itinerary}
736
+ */
737
+ static fromJson(json) {
738
+ const itinerary = new Itinerary();
739
+ itinerary.from = Coordinates.fromCompressedJson(json.from);
740
+ itinerary.to = Coordinates.fromCompressedJson(json.to);
741
+ itinerary.distance = json.distance;
742
+ itinerary.duration = json.duration;
743
+ itinerary.legs = json.legs.map(Leg.fromJson);
744
+ if (typeof json.startTime === 'number') {
745
+ itinerary.startTime = json.startTime;
746
+ }
747
+ if (typeof json.endTime === 'number') {
748
+ itinerary.endTime = json.endTime;
749
+ }
750
+ return itinerary;
751
+ }
752
+ }
753
+
754
+ class RouterResponse {
755
+
756
+ /** @type {!string|Array<string>} */
757
+ routerName;
758
+
759
+ /** @type {!Coordinates} */
760
+ from;
761
+
762
+ /** @type {!Coordinates} */
763
+ to;
764
+
765
+ /** @type {!(Itinerary[])} */
766
+ itineraries = [];
767
+
768
+ /** @type {?object} */
769
+ error = null;
770
+
771
+ /**
772
+ * @param {RouterResponse} obj1
773
+ * @param {RouterResponse} obj2
774
+ * @returns {Boolean}
775
+ */
776
+ static equalsTo(obj1, obj2) {
777
+ const intermediate = obj1.routerName === obj2.routerName
778
+ && obj1.from.equalsTo(obj2.from)
779
+ && obj1.to.equalsTo(obj2.to)
780
+ && obj1.itineraries.length === obj2.itineraries.length;
781
+
782
+ if (!intermediate) {
783
+ return false;
784
+ }
785
+
786
+ for (let i = 0; i < obj1.itineraries.length; i++) {
787
+ if (!obj1.itineraries[i].equalsTo(obj2.itineraries[i])) {
788
+ return false;
789
+ }
790
+ }
791
+
792
+ return true;
793
+ }
794
+
795
+ /**
796
+ * @param {RouterResponse} obj
797
+ * @returns {Boolean}
798
+ */
799
+ equalsTo(obj) {
800
+ return RouterResponse.equalsTo(this, obj);
801
+ }
802
+
803
+
804
+ /**
805
+ * @returns {object}
806
+ */
807
+ toJson() {
808
+ const json = {
809
+ routerName: this.routerName,
810
+ from: this.from.toCompressedJson(),
811
+ to: this.to.toCompressedJson(),
812
+ itineraries: this.itineraries.map(itinerary => itinerary.toJson())
813
+ };
814
+ if (this.error) {
815
+ json.error = this.error;
816
+ }
817
+ return json;
818
+ }
819
+
820
+ /**
821
+ * @param {object} json
822
+ * @returns {RouterResponse}
823
+ */
824
+ static fromJson(json) {
825
+ const routerResponse = new RouterResponse();
826
+ routerResponse.routerName = json.routerName;
827
+ routerResponse.from = Coordinates.fromCompressedJson(json.from);
828
+ routerResponse.to = Coordinates.fromCompressedJson(json.to);
829
+ routerResponse.itineraries = json.itineraries.map(Itinerary.fromJson);
830
+ if (json.error) {
831
+ routerResponse.error = json.error;
832
+ }
833
+ return routerResponse;
834
+ }
835
+ }
836
+
837
+ class ItineraryInfo {
838
+
839
+ /** @type {Step} */
840
+ nextStep;
841
+
842
+ /** @type {Step} */
843
+ previousStep;
844
+
845
+ /** @type {GraphProjection} */
846
+ projection;
847
+
848
+ /** @type {Leg} */
849
+ leg;
850
+
851
+ /** @type {number} */
852
+ traveledDistance;
853
+
854
+ /** @type {number} */
855
+ traveledPercentage;
856
+
857
+ /** @type {number} */
858
+ remainingDistance;
859
+
860
+ /** @type {number} */
861
+ remainingPercentage;
862
+
863
+ }
864
+
865
+ class WemapRouterOptions extends GraphRouterOptions {
866
+
867
+ /** @type {WemapRouterOptions} */
868
+ static DEFAULT = new WemapRouterOptions();
869
+
870
+ /**
871
+ * @returns {WemapRouterOptions}
872
+ */
873
+ static get WITHOUT_STAIRS() {
874
+ const options = new WemapRouterOptions();
875
+ options.acceptEdgeFn = edge => edge.builtFrom.tags.highway !== 'steps';
876
+ return options;
877
+ }
878
+
879
+ /**
880
+ * Get route duration
881
+ * @param {Number} speed in km/h
882
+ */
883
+ static getDurationFromLength(length, speed = 5) {
884
+ return length / (speed * 1000 / 3600);
885
+ }
886
+
887
+ /** @type {function(GraphEdge<OsmElement>):boolean} */
888
+ weightEdgeFn = edge => edge.builtFrom.isElevator ? 30 : WemapRouterOptions.getDurationFromLength(edge.length);
889
+
890
+
891
+ }
892
+
893
+ class WemapRouter extends GraphRouter {
894
+
895
+ /**
896
+ * @param {!Network<OsmElement>} network
897
+ */
898
+ constructor(network) {
899
+ super(network);
900
+ }
901
+
902
+
903
+ /** @type {string} */
904
+ static get rname() {
905
+ return 'wemap';
906
+ }
907
+
908
+ /**
909
+ * @param {Coordinates} start
910
+ * @param {Coordinates} end
911
+ * @param {WemapRouterOptions} options
912
+ * @returns {GraphItinerary<OsmElement>}
913
+ */
914
+ getShortestPath(start, end, options = new WemapRouterOptions()) {
915
+ return super.getShortestPath(start, end, options);
916
+ }
917
+
918
+ }
919
+
920
+ /* eslint-disable complexity */
921
+
922
+ const SKIP_STEP_ANGLE_MAX = deg2rad(20);
923
+
924
+ class WemapStepsGeneration {
925
+
926
+ /**
927
+ * @param {GraphItinerary<OsmElement>} itinerary
928
+ * @returns {Step[]}
929
+ */
930
+ static fromGraphItinerary(itinerary) {
931
+
932
+ const steps = [];
933
+
934
+ const { start, end, nodes, edges } = itinerary;
935
+
936
+ let currentStep, previousStep;
937
+ let previousBearing = start.bearingTo(nodes[0].coords);
938
+
939
+ for (let i = 0; i < nodes.length - 1; i++) {
940
+
941
+ const isFirstStep = !currentStep;
942
+
943
+ const node = nodes[i];
944
+ const nextNode = nodes[i + 1];
945
+ const edge = edges[i];
946
+
947
+ const bearing = edge.bearing;
948
+ const angle = diffAngle(previousBearing, bearing + Math.PI);
949
+
950
+ let splitByAngle = Math.abs(diffAngle(Math.PI, angle)) >= SKIP_STEP_ANGLE_MAX;
951
+
952
+ const splitByLevel = edge.level && edge.level.isRange
953
+ && node.coords.level && !node.coords.level.isRange;
954
+ splitByAngle = splitByAngle && !(node.coords.level && node.coords.level.isRange);
955
+
956
+ const splitStepCondition = splitByAngle || splitByLevel;
957
+
958
+ const isSubwayEntrance = node ? node.builtFrom.tags.railway === 'subway_entrance' : false;
959
+
960
+ // New step creation
961
+ if (isFirstStep || splitStepCondition || isSubwayEntrance) {
962
+
963
+ previousStep = currentStep;
964
+
965
+ currentStep = new Step();
966
+ currentStep.coords = node.coords;
967
+ currentStep.number = steps.length + 1;
968
+ currentStep.angle = angle;
969
+ currentStep.previousBearing = previousBearing;
970
+ currentStep.nextBearing = bearing;
971
+ currentStep.name = edge.builtFrom.tags.name || null;
972
+ currentStep.distance = 0;
973
+ currentStep.duration = 0;
974
+
975
+ if (isSubwayEntrance) {
976
+ currentStep.extras.subwayEntrance = true;
977
+ currentStep.name = node.builtFrom.tags.name;
978
+ if (node.builtFrom.tags.ref) {
979
+ currentStep.extras.subwayEntranceRef = node.builtFrom.tags.ref;
980
+ }
981
+ }
982
+
983
+ if (splitByLevel) {
984
+ currentStep.levelChange = WemapStepsGeneration.levelChangefromTwoNodes(node, nextNode);
985
+ }
986
+
987
+ steps.push(currentStep);
988
+
989
+ if (!previousStep) {
990
+ currentStep.firstStep = true;
991
+ }
992
+ }
993
+
994
+ currentStep.distance += edge.length;
995
+ currentStep.duration += itinerary.edgesWeights[i];
996
+ previousBearing = bearing;
997
+ }
998
+
999
+ const lastNode = nodes[nodes.length - 1];
1000
+
1001
+ // Create a last step if end is not on the network
1002
+ if (!Coordinates.equalsTo(lastNode.coords, end)) {
1003
+ const lastStep = new Step();
1004
+ lastStep.coords = lastNode.coords;
1005
+ lastStep.number = steps.length + 1;
1006
+ lastStep.previousBearing = previousBearing;
1007
+ lastStep.distance = lastNode.coords.distanceTo(end);
1008
+ lastStep.nextBearing = lastNode.coords.bearingTo(end);
1009
+ lastStep.angle = diffAngle(lastStep.previousBearing, lastStep.nextBearing + Math.PI);
1010
+ steps.push(lastStep);
1011
+ }
1012
+
1013
+ steps[steps.length - 1].lastStep = true;
1014
+
1015
+ return steps;
1016
+ }
1017
+
1018
+ /**
1019
+ * @param {GraphNode<OsmElement>} firstNode
1020
+ * @param {GraphNode<OsmElement>} secondNode
1021
+ * @returns {LevelChange}
1022
+ */
1023
+ static levelChangefromTwoNodes(firstNode, secondNode) {
1024
+
1025
+ const levelChange = new LevelChange();
1026
+
1027
+ const edge = GraphUtils.getEdgeByNodes(firstNode.edges, firstNode, secondNode);
1028
+
1029
+ if (edge.builtFrom.isElevator) {
1030
+ levelChange.type = 'elevator';
1031
+ } else if (edge.builtFrom.isConveying) {
1032
+ levelChange.type = 'conveyor';
1033
+ } else if (edge.builtFrom.areStairs) {
1034
+ levelChange.type = 'stairs';
1035
+ }
1036
+
1037
+ levelChange.difference = Level.diff(firstNode.coords.level, secondNode.coords.level);
1038
+ levelChange.direction = levelChange.difference > 0 ? 'up' : 'down';
1039
+
1040
+ return levelChange;
1041
+ }
1042
+ }
1043
+
1044
+ /**
1045
+ * @param {GraphItinerary<OsmElement>} graphItinerary
1046
+ * @param {string} mode
1047
+ * @returns {Leg}
1048
+ */
1049
+ function createLegFromGraphItinerary(graphItinerary,
1050
+ mode = Constants.ROUTING_MODE.WALK) {
1051
+
1052
+ const leg = new Leg();
1053
+
1054
+ leg.from = { coords: graphItinerary.start, name: null };
1055
+ leg.to = { coords: graphItinerary.end, name: null };
1056
+ leg.coords = graphItinerary.nodes.map(node => node.coords);
1057
+ leg.distance = graphItinerary.edges.reduce((acc, edge) => acc + edge.length, 0);
1058
+ leg.duration = graphItinerary.edgesWeights.reduce((acc, weight) => acc + weight, 0);
1059
+ leg.mode = mode;
1060
+ leg.steps = WemapStepsGeneration.fromGraphItinerary(graphItinerary);
1061
+ leg.steps.forEach(step => {
1062
+ step._idCoordsInLeg = leg.coords.findIndex(coords => coords === step.coords);
1063
+ });
1064
+
1065
+ return leg;
1066
+ }
1067
+
1068
+ /**
1069
+ * @param {GraphItinerary<OsmElement>} graphItinerary
1070
+ * @param {string} mode
1071
+ * @returns {Itinerary}
1072
+ */
1073
+ function createItineraryFromGraphItinerary(graphItinerary,
1074
+ mode = Constants.ROUTING_MODE.WALK) {
1075
+
1076
+ const leg = createLegFromGraphItinerary(graphItinerary, mode);
1077
+
1078
+ const itinerary = new Itinerary();
1079
+
1080
+ itinerary.from = graphItinerary.start;
1081
+ itinerary.to = graphItinerary.end;
1082
+ itinerary.distance = leg.distance;
1083
+ itinerary.duration = leg.duration;
1084
+ itinerary.legs.push(leg);
1085
+
1086
+ return itinerary;
1087
+ }
1088
+
1089
+ var WemapRouterUtils = /*#__PURE__*/Object.freeze({
1090
+ __proto__: null,
1091
+ createLegFromGraphItinerary: createLegFromGraphItinerary,
1092
+ createItineraryFromGraphItinerary: createItineraryFromGraphItinerary
1093
+ });
1094
+
1095
+ const HIGHWAYS_PEDESTRIANS = ['footway', 'steps', 'pedestrian', 'living_street', 'path', 'track', 'sidewalk'];
1096
+
1097
+ const DEFAULT_WAY_SELECTOR = way => {
1098
+ return HIGHWAYS_PEDESTRIANS.includes(way.tags.highway)
1099
+ || way.tags.footway === 'sidewalk'
1100
+ || way.tags.public_transport === 'platform'
1101
+ || way.tags.railway === 'platform';
1102
+ };
1103
+
1104
+ /**
1105
+ * @param {Network<OsmElement>} network
1106
+ * @param {string} name
1107
+ */
1108
+ function getNodeByName(network, name) {
1109
+ return network.nodes.find(({ builtFrom }) => builtFrom.tags.name === name);
1110
+ }
1111
+
1112
+ /**
1113
+ * @param {Network<OsmElement>} network
1114
+ * @param {string} name
1115
+ */
1116
+ function getEdgeByName(network, name) {
1117
+ return network.edges.find(({ builtFrom }) => builtFrom.tags.name === name);
1118
+ }
1119
+
1120
+ /**
1121
+ * @param {GraphEdge<OsmElement>} edge
1122
+ * @param {OsmWay} way
1123
+ * @returns {boolean}
1124
+ */
1125
+ function manageOneWay(edge, way) {
1126
+
1127
+ const { highway, oneway, conveying } = way.tags;
1128
+
1129
+ edge.isOneway = Boolean((oneway === 'yes' || oneway === 'true' || oneway === '1')
1130
+ || (conveying && highway && ['yes', 'forward', 'backward'].includes(conveying)));
1131
+
1132
+ if (edge.isOneway && conveying === 'backward') {
1133
+ const tmpNode = edge.node1;
1134
+ edge.node1 = edge.node2;
1135
+ edge.node2 = tmpNode;
1136
+ }
1137
+ }
1138
+
1139
+ /**
1140
+ * @param {Network} networkModel
1141
+ * @param {GraphNode} node
1142
+ */
1143
+ function createNodesAndEdgesFromElevator(networkModel, node) {
1144
+
1145
+ /** @type {GraphNode[]} */
1146
+ const createdNodes = [];
1147
+ const getOrCreateLevelNode = (level, builtFrom) => {
1148
+ let levelNode = createdNodes.find(({ coords }) => Level.equalsTo(level, coords.level));
1149
+ if (!levelNode) {
1150
+ levelNode = new GraphNode(node.coords.clone(), builtFrom);
1151
+ levelNode.coords.level = level;
1152
+ createdNodes.push(levelNode);
1153
+ networkModel.nodes.push(levelNode);
1154
+ }
1155
+ return levelNode;
1156
+ };
1157
+
1158
+ // Create nodes from node.edges
1159
+ node.edges.forEach(edge => {
1160
+ if (edge.level.isRange) {
1161
+ throw new Error('Cannot handle this elevator edge due to ambiguity');
1162
+ }
1163
+
1164
+ const levelNode = getOrCreateLevelNode(edge.level, node.builtFrom);
1165
+ if (edge.node1 === node) {
1166
+ edge.node1 = levelNode;
1167
+ } else {
1168
+ edge.node2 = levelNode;
1169
+ }
1170
+ levelNode.edges.push(edge);
1171
+ });
1172
+
1173
+ // Create edges from createdNodes
1174
+ for (let i = 0; i < createdNodes.length; i++) {
1175
+ for (let j = i + 1; j < createdNodes.length; j++) {
1176
+
1177
+ const createdNode1 = createdNodes[i];
1178
+ const createdNode2 = createdNodes[j];
1179
+
1180
+ const newEdge = new GraphEdge(
1181
+ createdNode1,
1182
+ createdNode2,
1183
+ new Level(createdNode1.coords.level.val, createdNode2.coords.level.val),
1184
+ node.builtFrom
1185
+ );
1186
+ networkModel.edges.push(newEdge);
1187
+ }
1188
+ }
1189
+
1190
+ // Remove the historical elevator node from the network
1191
+ networkModel.nodes = networkModel.nodes.filter(_node => _node !== node);
1192
+ }
1193
+
1194
+
1195
+ /**
1196
+ * @param {OsmModel} osmModel
1197
+ * @param {function} _waySelectionFilter
1198
+ * @returns {Network<OsmElement>}
1199
+ */
1200
+ function createNetworkFromOsmModel(
1201
+ osmModel,
1202
+ waySelectionFilter = DEFAULT_WAY_SELECTOR
1203
+ ) {
1204
+
1205
+ const networkModel = new Network();
1206
+
1207
+ const nodesCreated = {};
1208
+ const elevatorNodes = [];
1209
+
1210
+ /**
1211
+ * @param {OsmNode} osmNode
1212
+ */
1213
+ const getOrCreateNode = osmNode => {
1214
+ let node = nodesCreated[osmNode.id];
1215
+ if (!node) {
1216
+ node = new GraphNode(osmNode.coords, osmNode);
1217
+ nodesCreated[osmNode.id] = node;
1218
+ networkModel.nodes.push(node);
1219
+
1220
+ if (osmNode.tags.highway === 'elevator') {
1221
+ elevatorNodes.push(node);
1222
+ }
1223
+ }
1224
+ return node;
1225
+ };
1226
+
1227
+ osmModel.ways.forEach(way => {
1228
+ if (!waySelectionFilter(way)) {
1229
+ return;
1230
+ }
1231
+
1232
+ let firstNode = getOrCreateNode(way.nodes[0]);
1233
+ for (let i = 1; i < way.nodes.length; i++) {
1234
+ const secondNode = getOrCreateNode(way.nodes[i]);
1235
+
1236
+ const edge = new GraphEdge(firstNode, secondNode, way.level, way);
1237
+ manageOneWay(edge, way);
1238
+ networkModel.edges.push(edge);
1239
+ firstNode = secondNode;
1240
+ }
1241
+
1242
+ });
1243
+
1244
+ elevatorNodes.forEach(node => {
1245
+ // We have to clone this node for each connected edge
1246
+ createNodesAndEdgesFromElevator(networkModel, node);
1247
+ });
1248
+
1249
+ GraphNode.generateNodesLevels(networkModel.nodes);
1250
+
1251
+ return networkModel;
1252
+ }
1253
+
1254
+
1255
+ // /**
1256
+ // * @param {GraphNode} node
1257
+ // * @param {object} tags
1258
+ // */
1259
+ // static _applyNodePropertiesFromTags(node, tags) {
1260
+ // node.name = tags.name || null;
1261
+ // node.subwayEntrance = tags.railway === 'subway_entrance';
1262
+ // if (node.subwayEntrance && tags.ref) {
1263
+ // node.subwayEntranceRef = tags.ref;
1264
+ // }
1265
+ // }
1266
+
1267
+ // /**
1268
+ // * @param {GraphEdge} edge
1269
+ // * @param {object} tags
1270
+ // */
1271
+ // static _applyEdgePropertiesFromTags(edge, tags) {
1272
+ // const { highway, oneway, conveying, name } = tags;
1273
+ // edge.name = name || null;
1274
+ // edge.isStairs = highway === 'steps';
1275
+ // edge.isConveying = 'conveying' in tags;
1276
+ // edge.isOneway = Boolean((oneway === 'yes' || oneway === 'true' || oneway === '1')
1277
+ // || (conveying && highway && ['yes', 'forward', 'backward'].includes(conveying)));
1278
+
1279
+ // if (conveying === 'backward') {
1280
+ // const tmpNode = edge.node1;
1281
+ // edge.node1 = edge.node2;
1282
+ // edge.node2 = tmpNode;
1283
+ // }
1284
+
1285
+ // }
1286
+
1287
+ var WemapNetworkUtils = /*#__PURE__*/Object.freeze({
1288
+ __proto__: null,
1289
+ HIGHWAYS_PEDESTRIANS: HIGHWAYS_PEDESTRIANS,
1290
+ DEFAULT_WAY_SELECTOR: DEFAULT_WAY_SELECTOR,
1291
+ getNodeByName: getNodeByName,
1292
+ getEdgeByName: getEdgeByName,
1293
+ createNetworkFromOsmModel: createNetworkFromOsmModel
1294
+ });
1295
+
1296
+ class IOMap {
1297
+
1298
+ /** @type {?string} */
1299
+ name;
1300
+
1301
+ /** @type {!GraphRouter} */
1302
+ router;
1303
+
1304
+ /** @type {!([number, number][])} */
1305
+ bounds;
1306
+
1307
+
1308
+ /** @type {!(GraphNode[])} */
1309
+ entryPoints;
1310
+
1311
+ /**
1312
+ * @param {Network} network The network of the map
1313
+ * @param {GraphNode[]} entryPoints The map vertex that can be used to go inside / outside a IOMap
1314
+ * @param {Coordinates[]} bounds The bounds
1315
+ * @param {string} name The name of the map
1316
+ * @returns {IOMap}
1317
+ */
1318
+ constructor(network, entryPoints, bounds = null, name = null) {
1319
+
1320
+ this.name = name;
1321
+ this.router = new GraphRouter(network);
1322
+
1323
+ // Entry points
1324
+ entryPoints.forEach(entryPoint => {
1325
+ if (!network.nodes.includes(entryPoint)) {
1326
+ throw new Error(`Cannot find entry point ${entryPoint.coords.toString()} in network "${name}"`);
1327
+ }
1328
+ });
1329
+ this.entryPoints = entryPoints;
1330
+
1331
+ // Bounds
1332
+ if (bounds) {
1333
+ this.bounds = polygon([bounds.map(coords => [coords.lng, coords.lat])]);
1334
+ } else {
1335
+ const polygon = [network.nodes.map(node => [node.coords.lng, node.coords.lat])];
1336
+ const convexHull = convexHullFn(polygon);
1337
+ if (!convexHull) {
1338
+ throw new Error(`Cannot calculate convexHull of network "${name}"`);
1339
+ }
1340
+ this.bounds = convexHull;
1341
+ }
1342
+ }
1343
+
1344
+ /**
1345
+ * @param {string} osmXmlString
1346
+ * @param {string} name
1347
+ * @returns {IOMap}
1348
+ */
1349
+ static fromOsmXml(osmXmlString, name = null) {
1350
+
1351
+ const osmModel = OsmParser.parseOsmXmlString(osmXmlString);
1352
+ const network = createNetworkFromOsmModel(osmModel);
1353
+
1354
+ const entryPoints = osmModel.nodes
1355
+ .filter(({ tags }) => tags && tags['wemap:routing-io'])
1356
+ .map(osmNode => network.getNodeByCoords(osmNode.coords));
1357
+ if (entryPoints.some(el => el === null)
1358
+ || new Set(entryPoints).size !== entryPoints.length
1359
+ ) {
1360
+ throw new Error('Cannot parse wemap:routing-io correctly');
1361
+ }
1362
+
1363
+ const wayBounds = osmModel.ways.find(({ tags }) => tags['wemap:routing-bounds']);
1364
+ if (!wayBounds) {
1365
+ throw new Error('Search bounds is undefined. Please use OSM tag : "wemap:routing-bounds=yes"');
1366
+ }
1367
+ const bounds = wayBounds.nodes.map(node => node.coords);
1368
+ return new IOMap(network, entryPoints, bounds, name);
1369
+ }
1370
+
1371
+ /**
1372
+ * @param {Coordinates} coordinates
1373
+ * @returns {boolean}
1374
+ */
1375
+ isPointInside(coordinates) {
1376
+ return pointInPolygon([coordinates.lng, coordinates.lat], this.bounds);
1377
+ }
1378
+
1379
+ /**
1380
+ * Get the list of entry points sorted by the lowest distance between:
1381
+ * start -> entry point -> end
1382
+ * (as the crow flies)
1383
+ *
1384
+ * @param {Coordinates} start
1385
+ * @param {Coordinates} end
1386
+ * @returns {GraphNode[]}
1387
+ */
1388
+ getOrderedEntryPointsSortedByDistance(start, end) {
1389
+ const entryPointsCopy = [...this.entryPoints];
1390
+ return entryPointsCopy.sort((ep1, ep2) =>
1391
+ + ep1.coords.distanceTo(start) + ep1.coords.distanceTo(end)
1392
+ - ep2.coords.distanceTo(start) - ep2.coords.distanceTo(end)
1393
+ );
1394
+ }
1395
+
1396
+ /**
1397
+ * Get the best itinerary from any entry point to an end coordinates.
1398
+ *
1399
+ * The algorithm works as following:
1400
+ * 1 - Entry points are sorted using distance (as the crow flies) between start - entry point - end
1401
+ * 2 - Try to calculate an itinerary from the first entry point to the end coordinates.
1402
+ * 3 - If an itinerary is found, it is returned. Otherwise it tries from the next entry point.
1403
+ *
1404
+ * /!\ start is only used to sort the entry points (step 1).
1405
+ *
1406
+ * @param {Coordinates} start The start coordinates (which is outside the network)
1407
+ * @param {Coordinates} end Then end coordinates (which is inside the network)
1408
+ * @param {WemapRouterOptions} options
1409
+ * @returns {Itinerary}
1410
+ * @throws {NoRouteFoundError}
1411
+ */
1412
+ getBestItineraryFromEntryPointsToEnd(start, end, options) {
1413
+
1414
+ const sortedEntryPoints = this.getOrderedEntryPointsSortedByDistance(start, end);
1415
+ for (const entryPoint of sortedEntryPoints) {
1416
+ const itinerary = this.getItineraryInsideMap(entryPoint, end, options);
1417
+ if (itinerary) {
1418
+ return itinerary;
1419
+ }
1420
+ // no route found
1421
+ }
1422
+ throw new NoRouteFoundError(start, end,
1423
+ `No route found from entry points to ${end.toString()} in map: ${this.name}`
1424
+ );
1425
+ }
1426
+
1427
+
1428
+ /**
1429
+ * Get the best itinerary from start coordinates to any entry point.
1430
+ *
1431
+ * The algorithm works as following:
1432
+ * 1 - Entry points are sorted using distance (as the crow flies) between start - entry point - end
1433
+ * 2 - Try to calculate an itinerary from the start coordinates to the first entry point.
1434
+ * 3 - If an itinerary is found, it is returned. Otherwise it tries to the next entry point.
1435
+ *
1436
+ * /!\ end is only used to sort the entry points (step 1).
1437
+ *
1438
+ * @param {Coordinates} start The start coordinates (which is inside the network)
1439
+ * @param {Coordinates} end Then end coordinates (which is outside the network)
1440
+ * @param {WemapRouterOptions} options
1441
+ * @returns {Itinerary}
1442
+ * @throws {NoRouteFoundError}
1443
+ */
1444
+ getBestItineraryFromStartToEntryPoints(start, end, options) {
1445
+
1446
+ const sortedEntryPoints = this.getOrderedEntryPointsSortedByDistance(start, end);
1447
+ for (const entryPoint of sortedEntryPoints) {
1448
+ const itinerary = this.getItineraryInsideMap(start, entryPoint, options);
1449
+ if (itinerary) {
1450
+ return itinerary;
1451
+ }
1452
+ // no route found
1453
+ }
1454
+ throw new NoRouteFoundError(start, end,
1455
+ `No route found from ${start.toString()} to entry points in map: ${this.name}`
1456
+ );
1457
+ }
1458
+
1459
+ /**
1460
+ * @param {Coordinates} start the start coordinates
1461
+ * @param {Coordinates} end the end coordinates
1462
+ * @param {WemapRouterOptions} options
1463
+ * @returns {?Itinerary}
1464
+ * @throws {NoRouteFoundError}
1465
+ */
1466
+ getItineraryInsideMap(start, end, options) {
1467
+
1468
+ // Call the Wemap router to get the shortest path
1469
+ const graphItinerary = this.router.getShortestPath(start, end, options);
1470
+ if (graphItinerary === null) {
1471
+ return null;
1472
+ }
1473
+
1474
+ // Transform a network itinerary (nodes, edges...) to a router itinerary (legs, steps...)
1475
+ return createItineraryFromGraphItinerary(graphItinerary, Constants.ROUTING_MODE.WALK);
1476
+ }
1477
+ }
1478
+
1479
+ class WemapMetaRouterOptions {
1480
+
1481
+ /** @type {!boolean} */
1482
+ useStairs = true;
1483
+
1484
+ /** @type {?(string[])} */
1485
+ targetMaps = null;
1486
+
1487
+ /**
1488
+ * List of ordered remote routers (first of the list, first tested)
1489
+ * Algorithm will try the first router and if it fails continue
1490
+ * with the second, etc...
1491
+ * @type {!({name: string, endpointUrl: string}[])}
1492
+ */
1493
+ remoteRouters = [];
1494
+ }
1495
+
1496
+ class RemoteRouterOptions {
1497
+
1498
+ /** @type {!boolean} */
1499
+ useStairs = true;
1500
+
1501
+ /**
1502
+ * @returns {object}
1503
+ */
1504
+ toJson() {
1505
+ return { useStairs: this.useStairs };
1506
+ }
1507
+
1508
+ /**
1509
+ * @param {object}
1510
+ * @returns {RemoteRouterOptions}
1511
+ */
1512
+ static fromJson(json) {
1513
+ const obj = new RemoteRouterOptions();
1514
+ if ('useStairs' in json) {
1515
+ obj.useStairs = json.useStairs;
1516
+ }
1517
+ return obj;
1518
+ }
1519
+
1520
+ }
1521
+
1522
+ class RemoteRouter {
1523
+
1524
+ /**
1525
+ * Get the router name
1526
+ * @type {string} the router name
1527
+ */
1528
+ get rname() {
1529
+ return 'Unknown';
1530
+ }
1531
+
1532
+ /**
1533
+ * @abstract
1534
+ * @param {string} endpointUrl
1535
+ * @param {string} mode see Constants.ROUTING_MODE
1536
+ * @param {Array<Coordinates>} waypoints
1537
+ * @param {RemoteRouterOptions} options
1538
+ * @returns {!RouterResponse}
1539
+ */
1540
+ // eslint-disable-next-line no-unused-vars
1541
+ async getItineraries(endpointUrl, mode, waypoints, options = new RemoteRouterOptions()) {
1542
+ throw new Error('RemoteRouter "' + this.rname + '" does not @override getItineraries()');
1543
+ }
1544
+
1545
+ }
1546
+
1547
+ class IdfmRemoteRouterTokenError extends Error {
1548
+
1549
+ constructor() {
1550
+ super('An error occured with IDFM token request');
1551
+ }
1552
+ }
1553
+
1554
+ class RemoteRouterServerUnreachable extends Error {
1555
+
1556
+ /**
1557
+ * @param {!string} name
1558
+ * @param {!string} endpointUrl
1559
+ */
1560
+ constructor(name, endpointUrl) {
1561
+ super(`Remote router server ${name} is unreachable. URL: ${endpointUrl}`);
1562
+ }
1563
+ }
1564
+
1565
+ class RoutingModeCorrespondanceNotFound extends Error {
1566
+
1567
+ /** @type {!string} */
1568
+ routerName;
1569
+
1570
+ /** @type {!string} */
1571
+ routingMode;
1572
+
1573
+ /**
1574
+ * @param {!string} routerName
1575
+ * @param {!string} routingMode
1576
+ */
1577
+ constructor(routerName, routingMode) {
1578
+ this.routerName = routerName;
1579
+ this.routingMode = routingMode;
1580
+ super(`routing mode "${routingMode}" correspondance not found for router "${routerName}"`);
1581
+ }
1582
+ }
1583
+
1584
+ /**
1585
+ * @param {Itinerary} itinerary
1586
+ */
1587
+ function generateStepsMetadata(itinerary) {
1588
+
1589
+ let counter = 1;
1590
+
1591
+ itinerary.legs.forEach((leg, legId) => {
1592
+ leg.steps.forEach((step, stepId) => {
1593
+
1594
+ if (counter === 1) {
1595
+ step.firstStep = true;
1596
+ }
1597
+ if (legId === itinerary.legs.length - 1
1598
+ && stepId === leg.steps.length - 1) {
1599
+ step.lastStep = true;
1600
+ }
1601
+
1602
+ step.number = counter++;
1603
+
1604
+
1605
+ /*
1606
+ * Generate previousBearing, nextBearing and angle
1607
+ */
1608
+
1609
+ let coordsBeforeStep;
1610
+ if (step._idCoordsInLeg > 0) {
1611
+ coordsBeforeStep = leg.coords[step._idCoordsInLeg - 1];
1612
+ } else if (legId === 0) {
1613
+ coordsBeforeStep = itinerary.from;
1614
+ } else {
1615
+ coordsBeforeStep = itinerary.legs[legId - 1].to.coords;
1616
+ }
1617
+
1618
+ let coordsAfterStep;
1619
+ if (step._idCoordsInLeg !== leg.coords.length - 1) {
1620
+ coordsAfterStep = leg.coords[step._idCoordsInLeg + 1];
1621
+ } else if (legId === itinerary.legs.length - 1) {
1622
+ coordsAfterStep = itinerary.to;
1623
+ } else {
1624
+ coordsAfterStep = itinerary.legs[legId + 1].from.coords;
1625
+ }
1626
+
1627
+ step.previousBearing = coordsBeforeStep.bearingTo(step.coords);
1628
+ step.nextBearing = step.coords.bearingTo(coordsAfterStep);
1629
+ step.angle = diffAngle(step.previousBearing, step.nextBearing + Math.PI);
1630
+
1631
+ });
1632
+ });
1633
+ }
1634
+
1635
+
1636
+ /**
1637
+ * Create and return a date with a given timezone
1638
+ * @param {number} year
1639
+ * @param {number} month
1640
+ * @param {number} day
1641
+ * @param {number} hours
1642
+ * @param {number} minutes
1643
+ * @param {number} seconds
1644
+ * @param {string} timeZone - timezone name (e.g. 'Europe/Paris')
1645
+ */
1646
+ function dateWithTimeZone(year, month, day, hour, minute, second, timeZone = 'Europe/Paris') {
1647
+ const date = new Date(Date.UTC(year, month, day, hour, minute, second));
1648
+
1649
+ const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
1650
+ const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timeZone }));
1651
+ const offset = utcDate.getTime() - tzDate.getTime();
1652
+
1653
+ date.setTime(date.getTime() + offset);
1654
+
1655
+ return date;
1656
+ }
1657
+
1658
+ function isRoutingError(e) {
1659
+ return e instanceof RemoteRouterServerUnreachable
1660
+ || e instanceof RoutingModeCorrespondanceNotFound
1661
+ || e instanceof IdfmRemoteRouterTokenError
1662
+ || e instanceof NoRouteFoundError;
1663
+ }
1664
+
1665
+ /* eslint-disable max-depth */
1666
+
1667
+ /**
1668
+ * Input mode correspondance
1669
+ */
1670
+ const inputModeCorrespondance$2 = new Map();
1671
+ inputModeCorrespondance$2.set(Constants.ROUTING_MODE.CAR, 'Car');
1672
+ inputModeCorrespondance$2.set(Constants.ROUTING_MODE.WALK, 'Walk');
1673
+ inputModeCorrespondance$2.set(Constants.ROUTING_MODE.BIKE, 'Bike');
1674
+ inputModeCorrespondance$2.set(Constants.ROUTING_MODE.BUS, 'PT');
1675
+ inputModeCorrespondance$2.set(Constants.ROUTING_MODE.MULTI, 'PT');
1676
+
1677
+
1678
+ /**
1679
+ * List of all routing modes supported by the API
1680
+ */
1681
+ const routingModeCorrespondance$1 = new Map();
1682
+ routingModeCorrespondance$1.set('WALK', Constants.ROUTING_MODE.WALK);
1683
+ routingModeCorrespondance$1.set('BICYCLE', Constants.ROUTING_MODE.BIKE);
1684
+ routingModeCorrespondance$1.set('TRAMWAY', Constants.ROUTING_MODE.TRAM);
1685
+ routingModeCorrespondance$1.set('METRO', Constants.ROUTING_MODE.METRO);
1686
+ routingModeCorrespondance$1.set('FUNICULAR', Constants.ROUTING_MODE.FUNICULAR);
1687
+ routingModeCorrespondance$1.set('BUS', Constants.ROUTING_MODE.BUS);
1688
+ routingModeCorrespondance$1.set('COACH', Constants.ROUTING_MODE.BUS);
1689
+ routingModeCorrespondance$1.set('SCHOOL', Constants.ROUTING_MODE.BUS);
1690
+ routingModeCorrespondance$1.set('BUS_PMR', Constants.ROUTING_MODE.BUS);
1691
+ routingModeCorrespondance$1.set('MINIBUS', Constants.ROUTING_MODE.BUS);
1692
+ routingModeCorrespondance$1.set('TROLLEY_BUS', Constants.ROUTING_MODE.BUS);
1693
+ routingModeCorrespondance$1.set('TAXIBUS', Constants.ROUTING_MODE.BUS);
1694
+ routingModeCorrespondance$1.set('SHUTTLE', Constants.ROUTING_MODE.BUS);
1695
+ routingModeCorrespondance$1.set('TRAIN', Constants.ROUTING_MODE.TRAIN);
1696
+ routingModeCorrespondance$1.set('HST', Constants.ROUTING_MODE.TRAIN);
1697
+ routingModeCorrespondance$1.set('LOCAL_TRAIN', Constants.ROUTING_MODE.TRAIN);
1698
+ routingModeCorrespondance$1.set('AIR', Constants.ROUTING_MODE.AIRPLANE);
1699
+ routingModeCorrespondance$1.set('FERRY', Constants.ROUTING_MODE.BOAT);
1700
+ routingModeCorrespondance$1.set('TAXI', Constants.ROUTING_MODE.UNKNOWN);
1701
+ routingModeCorrespondance$1.set('CAR_POOL', Constants.ROUTING_MODE.UNKNOWN);
1702
+ routingModeCorrespondance$1.set('PRIVATE_VEHICLE', Constants.ROUTING_MODE.CAR);
1703
+ routingModeCorrespondance$1.set('SCOOTER', Constants.ROUTING_MODE.MOTO);
1704
+
1705
+ /**
1706
+ * List of all plan trip supported by the API
1707
+ * Routing mode UNKNOWN means that the itinerary will not be parsed by the router
1708
+ */
1709
+ const planTripType = new Map();
1710
+ planTripType.set(0, Constants.ROUTING_MODE.BUS);
1711
+ planTripType.set(1, Constants.ROUTING_MODE.WALK);
1712
+ planTripType.set(2, Constants.ROUTING_MODE.BIKE);
1713
+ planTripType.set(3, Constants.ROUTING_MODE.CAR);
1714
+ planTripType.set(4, Constants.ROUTING_MODE.UNKNOWN);
1715
+ planTripType.set(5, Constants.ROUTING_MODE.UNKNOWN);
1716
+ planTripType.set(6, Constants.ROUTING_MODE.UNKNOWN);
1717
+ planTripType.set(7, Constants.ROUTING_MODE.UNKNOWN);
1718
+ planTripType.set(8, Constants.ROUTING_MODE.UNKNOWN);
1719
+ planTripType.set(9, Constants.ROUTING_MODE.UNKNOWN);
1720
+ planTripType.set(10, Constants.ROUTING_MODE.UNKNOWN);
1721
+ planTripType.set(11, Constants.ROUTING_MODE.UNKNOWN);
1722
+ planTripType.set(12, Constants.ROUTING_MODE.UNKNOWN);
1723
+ planTripType.set(13, Constants.ROUTING_MODE.UNKNOWN);
1724
+ planTripType.set(14, Constants.ROUTING_MODE.UNKNOWN);
1725
+
1726
+
1727
+ /**
1728
+ * Singleton.
1729
+ */
1730
+ class CitywayRemoteRouter extends RemoteRouter {
1731
+
1732
+ /**
1733
+ * @override
1734
+ */
1735
+ get rname() {
1736
+ return 'cityway';
1737
+ }
1738
+
1739
+
1740
+ /**
1741
+ * @override
1742
+ * @throws {RoutingModeCorrespondanceNotFound}
1743
+ * @throws {RemoteRouterServerUnreachable}
1744
+ */
1745
+ async getItineraries(endpointUrl, mode, waypoints) {
1746
+ const url = this.getURL(endpointUrl, mode, waypoints);
1747
+ const res = await (fetch(url).catch(() => {
1748
+ throw new RemoteRouterServerUnreachable(this.rname, url);
1749
+ }));
1750
+
1751
+ const jsonResponse = await res.json().catch(() => {
1752
+ throw new RemoteRouterServerUnreachable(this.rname, url);
1753
+ });
1754
+ return this.createRouterResponseFromJson(jsonResponse);
1755
+ }
1756
+
1757
+
1758
+ /**
1759
+ * @param {string} endpointUrl
1760
+ * @param {string} mode
1761
+ * @param {Array<Coordinates>} waypoints
1762
+ * @throws {RoutingModeCorrespondanceNotFound}
1763
+ */
1764
+ getURL(endpointUrl, mode, waypoints) {
1765
+ const citywayMode = inputModeCorrespondance$2.get(mode);
1766
+ if (!citywayMode) {
1767
+ throw new RoutingModeCorrespondanceNotFound(this.rname, mode);
1768
+ }
1769
+ if (waypoints.length > 2) {
1770
+ Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
1771
+ }
1772
+ const fromPlace = `DepartureLatitude=${waypoints[0].latitude}&DepartureLongitude=${waypoints[0].longitude}`;
1773
+ const toPlace = `ArrivalLatitude=${waypoints[1].latitude}&ArrivalLongitude=${waypoints[1].longitude}`;
1774
+ const queryMode = `TripModes=${citywayMode}`;
1775
+
1776
+ const url = new URL(endpointUrl);
1777
+ let { search } = url;
1778
+ search = (search ? `${search}&` : '?') + `${fromPlace}&${toPlace}&${queryMode}`;
1779
+
1780
+ return `${url.origin}${url.pathname}${search}`;
1781
+ }
1782
+
1783
+ /**
1784
+ * Generate multi itineraries from Cityway JSON
1785
+ * @param {object} json JSON file provided by Cityway.
1786
+ * @returns {?RouterResponse}
1787
+ * @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
1788
+ */
1789
+ createRouterResponseFromJson(json) {
1790
+
1791
+ if (json.StatusCode !== 200 || !json.Data || !json.Data.length) {
1792
+ return null;
1793
+ }
1794
+
1795
+ const routerResponse = new RouterResponse();
1796
+ routerResponse.routerName = this.rname;
1797
+
1798
+
1799
+ // Do not know if it the best approach, but it works...
1800
+ const allJsonTrips = json.Data.reduce((acc, dataObj) => {
1801
+ acc.push(...dataObj.response.trips.Trip.map(trip => ({
1802
+ ...trip,
1803
+ ...(dataObj.hasOwnProperty('PlanTripType') ? { PlanTripType: dataObj.PlanTripType } : {})
1804
+ })));
1805
+ return acc;
1806
+ }, []);
1807
+
1808
+ // eslint-disable-next-line no-labels
1809
+ itineraryLoop:
1810
+ for (const trip of allJsonTrips) {
1811
+
1812
+ if (trip.hasOwnProperty('PlanTripType') && planTripType.get(trip.PlanTripType) === Constants.ROUTING_MODE.UNKNOWN) {
1813
+ continue;
1814
+ }
1815
+
1816
+ const itinerary = new Itinerary();
1817
+
1818
+ itinerary.duration = this.parseDuration(trip.Duration);
1819
+ itinerary.startTime = this.jsonDateToTimestamp(trip.Departure.Time);
1820
+ itinerary.from = this.jsonToCoordinates(trip.Departure.Site.Position);
1821
+ itinerary.endTime = this.jsonDateToTimestamp(trip.Arrival.Time);
1822
+ itinerary.to = this.jsonToCoordinates(trip.Arrival.Site.Position);
1823
+
1824
+ for (const jsonSection of trip.sections.Section) {
1825
+
1826
+ const jsonLeg = jsonSection.Leg ? jsonSection.Leg : jsonSection.PTRide;
1827
+
1828
+ const leg = new Leg();
1829
+
1830
+ leg.mode = routingModeCorrespondance$1.get(jsonLeg.TransportMode);
1831
+ leg.duration = this.parseDuration(jsonLeg.Duration);
1832
+ leg.startTime = this.jsonDateToTimestamp(jsonLeg.Departure.Time);
1833
+ leg.endTime = this.jsonDateToTimestamp(jsonLeg.Arrival.Time);
1834
+ leg.coords = [];
1835
+
1836
+ if (leg.mode === Constants.ROUTING_MODE.UNKNOWN) {
1837
+ // eslint-disable-next-line
1838
+ continue itineraryLoop;
1839
+ }
1840
+
1841
+ if (leg.mode === Constants.ROUTING_MODE.WALK
1842
+ || leg.mode === Constants.ROUTING_MODE.BIKE
1843
+ || leg.mode === Constants.ROUTING_MODE.CAR) {
1844
+
1845
+ leg.from = {
1846
+ name: jsonLeg.Departure.Site.Name,
1847
+ coords: this.jsonToCoordinates(jsonLeg.Departure.Site.Position)
1848
+ };
1849
+ leg.to = {
1850
+ name: jsonLeg.Arrival.Site.Name,
1851
+ coords: this.jsonToCoordinates(jsonLeg.Arrival.Site.Position)
1852
+ };
1853
+
1854
+ leg.steps = [];
1855
+ for (const jsonPathLink of jsonLeg.pathLinks.PathLink) {
1856
+ const step = new Step();
1857
+ let stepCoords;
1858
+ if (jsonPathLink.Geometry) {
1859
+ stepCoords = this.parseWKTGeometry(jsonPathLink.Geometry);
1860
+ } else {
1861
+ stepCoords = [leg.from.coords, leg.to.coords];
1862
+ }
1863
+ step.coords = stepCoords[0];
1864
+ step._idCoordsInLeg = leg.coords.length;
1865
+ stepCoords.forEach((coords, idx) => {
1866
+ if (
1867
+ idx !== 0
1868
+ || leg.coords.length === 0
1869
+ || !leg.coords[leg.coords.length - 1].equalsTo(coords)
1870
+ ) {
1871
+ leg.coords.push(coords);
1872
+ }
1873
+ });
1874
+
1875
+
1876
+ step.name = jsonPathLink.Departure.Site.Name;
1877
+ step.levelChange = null;
1878
+
1879
+ step.distance = jsonPathLink.Distance;
1880
+
1881
+ leg.steps.push(step);
1882
+ }
1883
+
1884
+ } else if (Constants.PUBLIC_TRANSPORT.includes(leg.mode)) {
1885
+
1886
+ leg.from = {
1887
+ name: jsonLeg.Departure.StopPlace.Name,
1888
+ coords: this.jsonToCoordinates(jsonLeg.Departure.StopPlace.Position)
1889
+ };
1890
+ leg.to = {
1891
+ name: jsonLeg.Arrival.StopPlace.Name,
1892
+ coords: this.jsonToCoordinates(jsonLeg.Arrival.StopPlace.Position)
1893
+ };
1894
+
1895
+ let transportName = jsonLeg.Line.Number;
1896
+ if (leg.mode === Constants.ROUTING_MODE.TRAM && transportName.toLowerCase().includes('tram')) {
1897
+ // In order to remove the "TRAM " prefix.
1898
+ transportName = transportName.substr(5);
1899
+ }
1900
+
1901
+ leg.transportInfo = {
1902
+ name: transportName,
1903
+ routeColor: jsonLeg.Line.Color,
1904
+ routeTextColor: jsonLeg.Line.TextColor,
1905
+ directionName: jsonLeg.Destination
1906
+ };
1907
+
1908
+ for (const jsonStep of jsonLeg.steps.Step) {
1909
+ const stepCoords = this.parseWKTGeometry(jsonStep.Geometry);
1910
+ stepCoords.forEach((coords, idx) => {
1911
+ if (
1912
+ idx !== 0
1913
+ || leg.coords.length === 0
1914
+ || !leg.coords[leg.coords.length - 1].equalsTo(coords)
1915
+ ) {
1916
+ leg.coords.push(coords);
1917
+ }
1918
+ });
1919
+ }
1920
+
1921
+ const legStep = new Step();
1922
+ legStep.coords = leg.coords[0];
1923
+ legStep._idCoordsInLeg = 0;
1924
+ legStep.name = jsonLeg.Line.Name;
1925
+ legStep.levelChange = null;
1926
+ legStep.distance = jsonLeg.Distance;
1927
+ leg.steps = [legStep];
1928
+ } else {
1929
+ Logger.warn(`[CitywayParser] Unknown leg mode: ${jsonLeg.TransportMode}`);
1930
+ }
1931
+
1932
+ leg.distance = leg.coords.reduce((acc, coords, idx, arr) => {
1933
+ if (idx === 0) {
1934
+ return acc;
1935
+ }
1936
+ return acc + arr[idx - 1].distanceTo(coords);
1937
+ }, 0);
1938
+
1939
+ itinerary.legs.push(leg);
1940
+
1941
+ }
1942
+
1943
+ routerResponse.itineraries.push(itinerary);
1944
+
1945
+ itinerary.distance = itinerary.coords.reduce((acc, coords, idx, arr) => {
1946
+ if (idx === 0) {
1947
+ return acc;
1948
+ }
1949
+ return acc + arr[idx - 1].distanceTo(coords);
1950
+ }, 0);
1951
+
1952
+ // All legs have to be parsed before computing steps metadata
1953
+ generateStepsMetadata(itinerary);
1954
+ }
1955
+
1956
+ routerResponse.from = routerResponse.itineraries[0].from;
1957
+ routerResponse.to = routerResponse.itineraries[routerResponse.itineraries.length - 1].to;
1958
+
1959
+ return routerResponse;
1960
+ }
1961
+
1962
+ /**
1963
+ * @param {object} json
1964
+ * @returns {Coordinates}
1965
+ */
1966
+ jsonToCoordinates(json) {
1967
+ return new Coordinates(json.Lat, json.Long);
1968
+ }
1969
+
1970
+ /**
1971
+ * @param {string} jsonDate
1972
+ * @returns {number}
1973
+ */
1974
+ jsonDateToTimestamp(jsonDate) {
1975
+ const [dateStr, timeStr] = jsonDate.split(' ');
1976
+ const [dayStr, monthStr, yearStr] = dateStr.split('/');
1977
+ const [hoursStr, minutesStr, secondsStr] = timeStr.split(':');
1978
+
1979
+ return dateWithTimeZone(
1980
+ Number(yearStr),
1981
+ Number(monthStr) - 1,
1982
+ Number(dayStr),
1983
+ Number(hoursStr),
1984
+ Number(minutesStr),
1985
+ Number(secondsStr)
1986
+ ).getTime();
1987
+ }
1988
+
1989
+ /**
1990
+ * @param {string} wktGeometry
1991
+ * @returns {Coordinates[]}
1992
+ */
1993
+ parseWKTGeometry(wktGeometry) {
1994
+ const tmpCoordsStr = wktGeometry.match(/LINESTRING \((.*)\)/i);
1995
+ const tmpCoordsPt = wktGeometry.match(/POINT \((.*)\)/i);
1996
+ if (!tmpCoordsStr && !tmpCoordsPt) {
1997
+ return null;
1998
+ }
1999
+
2000
+ if (tmpCoordsPt) {
2001
+ const [lng, lat] = tmpCoordsPt[1].split(' ');
2002
+ return [new Coordinates(Number(lat), Number(lng))];
2003
+ }
2004
+
2005
+ return tmpCoordsStr[1].split(',').map(str => {
2006
+ const sp = str.trim().split(' ');
2007
+ return new Coordinates(Number(sp[1]), Number(sp[0]));
2008
+ });
2009
+ }
2010
+
2011
+ /**
2012
+ * @param {string} iso8601Duration
2013
+ * @see https://stackoverflow.com/a/29153059/2239938
2014
+ */
2015
+ parseDuration(iso8601Duration) {
2016
+ const iso8601DurationRegex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
2017
+
2018
+ var matches = iso8601Duration.match(iso8601DurationRegex);
2019
+
2020
+ // const sign = typeof matches[1] === 'undefined' ? '+' : '-',
2021
+ const years = typeof matches[2] === 'undefined' ? 0 : Number(matches[2]);
2022
+ const months = typeof matches[3] === 'undefined' ? 0 : Number(matches[3]);
2023
+ const weeks = typeof matches[4] === 'undefined' ? 0 : Number(matches[4]);
2024
+ const days = typeof matches[5] === 'undefined' ? 0 : Number(matches[5]);
2025
+ const hours = typeof matches[6] === 'undefined' ? 0 : Number(matches[6]);
2026
+ const minutes = typeof matches[7] === 'undefined' ? 0 : Number(matches[7]);
2027
+ const seconds = typeof matches[8] === 'undefined' ? 0 : Number(matches[8]);
2028
+
2029
+ return seconds
2030
+ + minutes * 60
2031
+ + hours * 3600
2032
+ + days * 86400
2033
+ + weeks * (86400 * 7)
2034
+ + months * (86400 * 30)
2035
+ + years * (86400 * 365.25);
2036
+ }
2037
+ }
2038
+
2039
+ var CitywayRemoteRouter$1 = new CitywayRemoteRouter();
2040
+
2041
+ /* eslint-disable max-statements */
2042
+
2043
+ /**
2044
+ * Singleton.
2045
+ */
2046
+ class DeutscheBahnRemoteRouter extends RemoteRouter {
2047
+
2048
+ /**
2049
+ * @override
2050
+ */
2051
+ get rname() {
2052
+ return 'deutsche-bahn';
2053
+ }
2054
+
2055
+ /**
2056
+ * @override
2057
+ * @throws {RemoteRouterServerUnreachable}
2058
+ */
2059
+ async getItineraries(endpointUrl, mode, waypoints) {
2060
+ const url = this.getURL(endpointUrl, mode, waypoints);
2061
+ const res = await (fetch(url).catch(() => {
2062
+ throw new RemoteRouterServerUnreachable(this.rname, url);
2063
+ }));
2064
+
2065
+ const jsonResponse = await res.json().catch(() => {
2066
+ throw new RemoteRouterServerUnreachable(this.rname, url);
2067
+ });
2068
+ return this.createRouterResponseFromJson(jsonResponse, waypoints[0], waypoints[1]);
2069
+ }
2070
+
2071
+ /**
2072
+ * @param {string} endpointUrl
2073
+ * @param {string} mode
2074
+ * @param {Array<Coordinates>} waypoints
2075
+ */
2076
+ getURL(endpointUrl, mode, waypoints) {
2077
+ let url = endpointUrl + '/route/v1/walking/';
2078
+
2079
+ url += waypoints.map(waypoint => {
2080
+ if (waypoint.level) {
2081
+ const altitude = waypoint.level.isRange ? waypoint.level.low : waypoint.level.val;
2082
+
2083
+ return waypoint.longitude + ',' + waypoint.latitude + ',' + altitude;
2084
+ }
2085
+
2086
+ return waypoint.longitude + ',' + waypoint.latitude;
2087
+ }).join(';');
2088
+
2089
+ url += '?geometries=geojson&overview=full&steps=true';
2090
+
2091
+ return url;
2092
+ }
2093
+
2094
+ /**
2095
+ * Generate multi itineraries from the DB JSON
2096
+ * @param {object} json JSON file provided by the DB.
2097
+ * @param {Coordinates} from itinerary start
2098
+ * @param {Coordinates} to itinerary end
2099
+ * @returns {?RouterResponse}
2100
+ */
2101
+ createRouterResponseFromJson(json, from, to) {
2102
+
2103
+ const { segments: jsonSegments } = json;
2104
+
2105
+ if (!jsonSegments) {
2106
+ return null;
2107
+ }
2108
+
2109
+ const routerResponse = new RouterResponse();
2110
+ routerResponse.routerName = this.rname;
2111
+
2112
+ routerResponse.from = from;
2113
+ routerResponse.to = to;
2114
+
2115
+ /** @type {GraphEdge<OsmElement>[]} */
2116
+ const edges = [];
2117
+
2118
+ /** @type {GraphNode<OsmElement>[]} */
2119
+ const nodes = [];
2120
+
2121
+ /** @type {number[]} */
2122
+ const edgesWeights = [];
2123
+
2124
+ let id = 1;
2125
+ for (const jsonSegment of jsonSegments) {
2126
+
2127
+ const level = new Level(jsonSegment.fromLevel, jsonSegment.toLevel);
2128
+ const osmWay = new OsmWay(id++, null, level);
2129
+
2130
+ for (const jsonPoint of jsonSegment.polyline) {
2131
+ const coord = new Coordinates(jsonPoint.lat, jsonPoint.lon, null, level);
2132
+
2133
+ if (nodes.length !== 0
2134
+ && nodes[nodes.length - 1].coords.equalsTo(coord)) {
2135
+ continue;
2136
+ }
2137
+
2138
+ const osmNode = new OsmNode(id++, coord);
2139
+ const node = new GraphNode(osmNode.coords, osmNode);
2140
+
2141
+ if (nodes.length !== 0) {
2142
+ const prevNode = nodes[nodes.length - 1];
2143
+ const edge = new GraphEdge(prevNode, node, level, osmWay);
2144
+ edges.push(edge);
2145
+ edgesWeights.push(prevNode.coords.distanceTo(osmNode));
2146
+ }
2147
+
2148
+ nodes.push(node);
2149
+
2150
+ }
2151
+ }
2152
+
2153
+ /** @type {GraphItinerary<OsmElement>} */
2154
+ const graphItinerary = new GraphItinerary();
2155
+ graphItinerary.nodes = nodes;
2156
+ graphItinerary.edges = edges;
2157
+ graphItinerary.edgesWeights = edgesWeights;
2158
+ graphItinerary.start = nodes[0].coords;
2159
+ graphItinerary.end = nodes[nodes.length - 1].coords;
2160
+
2161
+ const points = nodes.map(node => node.coords);
2162
+ const itinerary = Itinerary.fromOrderedCoordinates(points, from, to);
2163
+ itinerary.legs[0].steps = WemapStepsGeneration.fromGraphItinerary(graphItinerary);
2164
+ itinerary.legs[0].steps.map((step, idx) => (step._idCoordsInLeg = idx));
2165
+
2166
+ // All legs have to be parsed before computing steps metadata
2167
+ generateStepsMetadata(itinerary);
2168
+
2169
+ routerResponse.itineraries.push(itinerary);
2170
+
2171
+ return routerResponse;
2172
+ }
2173
+ }
2174
+
2175
+ var DeutscheBahnRemoteRouter$1 = new DeutscheBahnRemoteRouter();
2176
+
2177
+ /* eslint-disable max-statements */
2178
+
2179
+ /**
2180
+ * List of all modes supported by the API
2181
+ * http://doc.navitia.io/#physical-mode
2182
+ */
2183
+
2184
+ const routingModeCorrespondance = new Map();
2185
+ routingModeCorrespondance.set('Air', Constants.ROUTING_MODE.AIRPLANE);
2186
+ routingModeCorrespondance.set('Boat', Constants.ROUTING_MODE.BOAT);
2187
+ routingModeCorrespondance.set('Bus', Constants.ROUTING_MODE.BUS);
2188
+ routingModeCorrespondance.set('BusRapidTransit', Constants.ROUTING_MODE.BUS);
2189
+ routingModeCorrespondance.set('Coach', Constants.ROUTING_MODE.BUS);
2190
+ routingModeCorrespondance.set('Ferry', Constants.ROUTING_MODE.FERRY);
2191
+ routingModeCorrespondance.set('Funicular', Constants.ROUTING_MODE.FUNICULAR);
2192
+ routingModeCorrespondance.set('LocalTrain', Constants.ROUTING_MODE.TRAIN);
2193
+ routingModeCorrespondance.set('LongDistanceTrain', Constants.ROUTING_MODE.TRAIN);
2194
+ routingModeCorrespondance.set('Metro', Constants.ROUTING_MODE.METRO);
2195
+ routingModeCorrespondance.set('Métro', Constants.ROUTING_MODE.METRO);
2196
+ routingModeCorrespondance.set('RailShuttle', Constants.ROUTING_MODE.TRAIN);
2197
+ routingModeCorrespondance.set('RapidTransit', Constants.ROUTING_MODE.BUS);
2198
+ routingModeCorrespondance.set('Shuttle', Constants.ROUTING_MODE.BUS);
2199
+ routingModeCorrespondance.set('SuspendedCableCar', Constants.ROUTING_MODE.FUNICULAR);
2200
+ routingModeCorrespondance.set('Taxi', Constants.ROUTING_MODE.TAXI);
2201
+ routingModeCorrespondance.set('Train', Constants.ROUTING_MODE.TRAIN);
2202
+ routingModeCorrespondance.set('RER', Constants.ROUTING_MODE.TRAIN);
2203
+ routingModeCorrespondance.set('Tramway', Constants.ROUTING_MODE.TRAM);
2204
+ routingModeCorrespondance.set('walking', Constants.ROUTING_MODE.WALK);
2205
+ routingModeCorrespondance.set('bike', Constants.ROUTING_MODE.BIKE);
2206
+
2207
+ /**
2208
+ * List of transports modes
2209
+ */
2210
+ const TRANSPORT_IDS = [
2211
+ 'physical_mode:Air',
2212
+ 'physical_mode:Boat',
2213
+ 'physical_mode:Bus',
2214
+ 'physical_mode:BusRapidTransit',
2215
+ 'physical_mode:Coach',
2216
+ 'physical_mode:Ferry',
2217
+ 'physical_mode:Funicular',
2218
+ 'physical_mode:LocalTrain',
2219
+ 'physical_mode:LongDistanceTrain',
2220
+ 'physical_mode:Metro',
2221
+ 'physical_mode:RailShuttle',
2222
+ 'physical_mode:RapidTransit',
2223
+ 'physical_mode:Shuttle',
2224
+ 'physical_mode:SuspendedCableCar',
2225
+ 'physical_mode:Taxi',
2226
+ 'physical_mode:Train',
2227
+ 'physical_mode:Tramway'
2228
+ ];
2229
+
2230
+ const clientId = '539eec73-3bb5-4327-bb5e-a52672658592';
2231
+ const clientSecret = '899f4bb9-f1d5-45d3-9f67-530827bb6734';
2232
+
2233
+ /**
2234
+ * Get last item of a given array
2235
+ * @param {Array} array
2236
+ * @returns {any}
2237
+ */
2238
+ function last(array) {
2239
+ return array[array.length - 1];
2240
+ }
2241
+
2242
+ /**
2243
+ * Singleton.
2244
+ */
2245
+ class IdfmRemoteRouter extends RemoteRouter {
2246
+
2247
+ isLogged = false;
2248
+ token = null;
2249
+ expiresAt = null;
2250
+
2251
+ /**
2252
+ * @override
2253
+ */
2254
+ get rname() {
2255
+ return 'idfm';
2256
+ }
2257
+
2258
+ /**
2259
+ * @override
2260
+ * @throws {IdfmRemoteRouterTokenError}
2261
+ * @throws {RemoteRouterServerUnreachable}
2262
+ */
2263
+ async getItineraries(endpointUrl, mode, waypoints) {
2264
+ if (!this.canRequestService()) {
2265
+ await this._connect();
2266
+ }
2267
+
2268
+ const url = this.getURL(endpointUrl, mode, waypoints);
2269
+
2270
+ const res = await (fetch(url, {
2271
+ method: 'GET',
2272
+ headers: { Authorization: 'Bearer ' + this.token }
2273
+ }).catch(() => {
2274
+ throw new RemoteRouterServerUnreachable(this.rname, url);
2275
+ }));
2276
+
2277
+
2278
+ const jsonResponse = await res.json().catch(() => {
2279
+ throw new RemoteRouterServerUnreachable(this.rname, url);
2280
+ });
2281
+
2282
+ // When IDFM failed to calculate an itinerary (ie. start or end
2283
+ // point is far from network), it respond a 404 with an error message
2284
+ // or a 200 with an error message
2285
+ if (jsonResponse && jsonResponse.error) {
2286
+ const routerResponse = new RouterResponse();
2287
+ routerResponse.routerName = this.rname;
2288
+ routerResponse.from = waypoints[0];
2289
+ routerResponse.to = waypoints[1];
2290
+ routerResponse.error = jsonResponse.error.message || 'no details.';
2291
+ return routerResponse;
2292
+ }
2293
+
2294
+ return this.createRouterResponseFromJson(jsonResponse);
2295
+ }
2296
+
2297
+ /**
2298
+ * @throws {IdfmRemoteRouterTokenError}
2299
+ */
2300
+ async _connect() {
2301
+
2302
+ const details = {
2303
+ 'grant_type': 'client_credentials',
2304
+ 'scope': 'read-data',
2305
+ 'client_id': clientId,
2306
+ 'client_secret': clientSecret
2307
+ };
2308
+
2309
+ const data = new URLSearchParams();
2310
+ for (const property in details) {
2311
+ if (details.hasOwnProperty(property)) {
2312
+ const encodedKey = encodeURIComponent(property);
2313
+ const encodedValue = encodeURIComponent(details[property]);
2314
+ data.append(encodedKey, encodedValue);
2315
+ }
2316
+ }
2317
+
2318
+ const headers = new Headers({
2319
+ 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
2320
+ });
2321
+
2322
+ const res = await fetch('https://idfm.getwemap.com/api/oauth/token', {
2323
+ method: 'POST',
2324
+ body: data,
2325
+ headers
2326
+ });
2327
+ if (res.status !== 200) {
2328
+ throw new IdfmRemoteRouterTokenError();
2329
+ }
2330
+ const response = await res.json();
2331
+
2332
+ if (response.access_token) {
2333
+ this.token = response.access_token;
2334
+ this.isLogged = true;
2335
+ this.expiresAt = new Date(new Date().getTime() + response.expires_in * 1000);
2336
+ }
2337
+ }
2338
+
2339
+ canRequestService() {
2340
+ return this.token && this.expiresAt && this.expiresAt - new Date() > 0;
2341
+ }
2342
+
2343
+ /**
2344
+ * @param {string} endpointUrl
2345
+ * @param {string} mode
2346
+ * @param {Array<Coordinates>} waypoints
2347
+ */
2348
+ getURL(endpointUrl, mode, waypoints) {
2349
+
2350
+ if (waypoints.length > 2) {
2351
+ Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
2352
+ }
2353
+
2354
+ const fromPlace = `from=${waypoints[0].longitude};${waypoints[0].latitude}`;
2355
+ const toPlace = `to=${waypoints[1].longitude};${waypoints[1].latitude}`;
2356
+
2357
+ let url = new URL(endpointUrl);
2358
+ let { search } = url;
2359
+ search = (search ? `${search}&` : '?') + `${fromPlace}&${toPlace}`;
2360
+
2361
+ let query = '';
2362
+ switch (mode) {
2363
+ case Constants.ROUTING_MODE.WALK:
2364
+ query = this.getWalkingQuery();
2365
+ break;
2366
+ case Constants.ROUTING_MODE.BIKE:
2367
+ query = this.getBikeQuery();
2368
+ break;
2369
+ case Constants.ROUTING_MODE.CAR:
2370
+ query = this.getCarQuery();
2371
+ break;
2372
+ }
2373
+
2374
+ url = `${url.origin}${url.pathname}${search}${query}`;
2375
+
2376
+ return url;
2377
+ }
2378
+
2379
+ getCarQuery() {
2380
+ const forbiddenTransport = TRANSPORT_IDS.map((id) => `forbidden_uris[]=${id}`).join('&');
2381
+ const allowCar = 'first_section_mode[]=walking&first_section_mode[]=car&last_section_mode[]=walking&last_section_mode[]=car';
2382
+
2383
+ return `&${forbiddenTransport}&${allowCar}`;
2384
+ }
2385
+
2386
+ getWalkingQuery() {
2387
+ const forbiddenTransport = TRANSPORT_IDS.map((id) => `forbidden_uris[]=${id}`).join('&');
2388
+ const allowWalking = 'first_section_mode[]=walking&last_section_mode[]=walking';
2389
+
2390
+ return `&${forbiddenTransport}&${allowWalking}`;
2391
+ }
2392
+
2393
+ getBikeQuery() {
2394
+ const forbiddenTransport = TRANSPORT_IDS.map((id) => `forbidden_uris[]=${id}`).join('&');
2395
+ const allowBike = 'first_section_mode[]=bike&last_section_mode[]=bike';
2396
+
2397
+ return `&${forbiddenTransport}&${allowBike}`;
2398
+ }
2399
+
2400
+ /**
2401
+ * @param {object} json
2402
+ * @returns {Coordinates}
2403
+ */
2404
+ jsonToCoordinates(json) {
2405
+ return new Coordinates(Number(json.lat), Number(json.lon));
2406
+ }
2407
+
2408
+ getSectionCoords(section) {
2409
+ const from = section.from.stop_point ? this.jsonToCoordinates(section.from.stop_point.coord) : this.jsonToCoordinates(section.from.address.coord);
2410
+ const to = section.to.stop_point ? this.jsonToCoordinates(section.to.stop_point.coord) : this.jsonToCoordinates(section.to.address.coord);
2411
+
2412
+ return {
2413
+ from,
2414
+ to
2415
+ };
2416
+ }
2417
+
2418
+ /**
2419
+ * Since the IDFM API does not provide coords for each step, we need to compute them
2420
+ * We trim the coordinates of the leg with the distance of each step and keep the last result as the coords of the step
2421
+ * @param {Leg} leg
2422
+ */
2423
+ findStepsCoord(leg) {
2424
+ const { steps, coords } = leg;
2425
+
2426
+ const duplicatedCoords = [...coords];
2427
+ let previousStep = steps[0];
2428
+ let accumulatedIndex = 0;
2429
+
2430
+ for (const [idx, step] of steps.entries()) {
2431
+ let newCoords;
2432
+
2433
+ if (idx === 0) {
2434
+ step._idCoordsInLeg = 0;
2435
+ newCoords = coords[0];
2436
+ } else if (idx === steps.length - 1) {
2437
+ step._idCoordsInLeg = coords.length - 1;
2438
+ newCoords = last(coords);
2439
+ } else if (duplicatedCoords.length === 1) {
2440
+ accumulatedIndex++;
2441
+
2442
+ step._idCoordsInLeg = accumulatedIndex;
2443
+
2444
+ newCoords = duplicatedCoords[0];
2445
+
2446
+ coords[step._idCoordsInLeg] = newCoords;
2447
+ } else {
2448
+ const result = Utils.trimRoute(duplicatedCoords, duplicatedCoords[0], previousStep.distance);
2449
+ accumulatedIndex += result.length - 1;
2450
+
2451
+ duplicatedCoords.splice(0, result.length - 1);
2452
+
2453
+ step._idCoordsInLeg = accumulatedIndex;
2454
+
2455
+ newCoords = last(result);
2456
+
2457
+ coords[step._idCoordsInLeg] = newCoords;
2458
+ }
2459
+
2460
+ step.coords = newCoords;
2461
+
2462
+ previousStep = step;
2463
+ }
2464
+ }
2465
+
2466
+ /**
2467
+ * @param {string} stringDate (e.g. 20211117T104516)
2468
+ * @returns {number}
2469
+ */
2470
+ dateStringToTimestamp(stringDate, timeZone) {
2471
+ const yearStr = stringDate.substr(0, 4);
2472
+ const monthStr = stringDate.substr(4, 2);
2473
+ const dayStr = stringDate.substr(6, 2);
2474
+ const hoursStr = stringDate.substr(9, 2);
2475
+ const minutesStr = stringDate.substr(11, 2);
2476
+ const secondsStr = stringDate.substr(13, 2);
2477
+
2478
+ return dateWithTimeZone(
2479
+ Number(yearStr),
2480
+ Number(monthStr) - 1,
2481
+ Number(dayStr),
2482
+ Number(hoursStr),
2483
+ Number(minutesStr),
2484
+ Number(secondsStr),
2485
+ timeZone
2486
+ ).getTime();
2487
+ }
2488
+
2489
+ /**
2490
+ * Generate multi itineraries from OTP JSON
2491
+ * @param {object} json JSON file provided by OTP.
2492
+ * @returns {?RouterResponse}
2493
+ */
2494
+ createRouterResponseFromJson(json) {
2495
+
2496
+ if (!json || !json.journeys) {
2497
+ return null;
2498
+ }
2499
+
2500
+ const routerResponse = new RouterResponse();
2501
+ routerResponse.routerName = this.rname;
2502
+
2503
+ routerResponse.from = this.getSectionCoords(json.journeys[0].sections[0]).from;
2504
+ routerResponse.to = this.getSectionCoords(last(json.journeys[0].sections)).to;
2505
+
2506
+ const timeZone = json.context.timezone;
2507
+
2508
+ for (const jsonItinerary of json.journeys) {
2509
+
2510
+ const itinerary = new Itinerary();
2511
+
2512
+ itinerary.duration = jsonItinerary.duration;
2513
+ itinerary.startTime = this.dateStringToTimestamp(jsonItinerary.departure_date_time, timeZone);
2514
+ itinerary.endTime = this.dateStringToTimestamp(jsonItinerary.arrival_date_time, timeZone);
2515
+ itinerary.from = routerResponse.from;
2516
+ itinerary.to = routerResponse.to;
2517
+ itinerary.distance = 0;
2518
+
2519
+ routerResponse.itineraries.push(itinerary);
2520
+
2521
+ for (const jsonSection of jsonItinerary.sections) {
2522
+
2523
+ if (jsonSection.type === 'waiting' || jsonSection.type === 'transfer') {
2524
+ continue;
2525
+ }
2526
+
2527
+ const leg = new Leg();
2528
+ let existingCoords = [];
2529
+ const { from, to } = this.getSectionCoords(jsonSection);
2530
+
2531
+ leg.distance = 0;
2532
+ leg.mode = routingModeCorrespondance.get(jsonSection.mode);
2533
+ leg.duration = jsonSection.duration;
2534
+ leg.startTime = this.dateStringToTimestamp(jsonSection.departure_date_time, timeZone);
2535
+ leg.endTime = this.dateStringToTimestamp(jsonSection.arrival_date_time, timeZone);
2536
+
2537
+ leg.from = {
2538
+ name: jsonSection.from.name,
2539
+ coords: from
2540
+ };
2541
+
2542
+ leg.to = {
2543
+ name: jsonSection.to.name,
2544
+ coords: to
2545
+ };
2546
+
2547
+ // A section can have multiple same coordinates, we need to remove them
2548
+ leg.coords = jsonSection.geojson.coordinates.reduce((acc, [lon, lat]) => {
2549
+ if (!existingCoords.includes(`${lon}-${lat}`)) {
2550
+ existingCoords = existingCoords.concat(`${lon}-${lat}`);
2551
+ acc.push(new Coordinates(lat, lon));
2552
+ }
2553
+
2554
+ return acc;
2555
+ }, []);
2556
+
2557
+ leg.steps = [];
2558
+
2559
+ if (jsonSection.path) {
2560
+ for (const jsonPathLink of jsonSection.path) {
2561
+ const step = new Step();
2562
+
2563
+ step.levelChange = null;
2564
+
2565
+ step.name = jsonPathLink.name;
2566
+ step.distance = jsonPathLink.length;
2567
+
2568
+ leg.distance += step.distance;
2569
+ leg.steps.push(step);
2570
+ }
2571
+
2572
+ this.findStepsCoord(leg);
2573
+ }
2574
+
2575
+ if (jsonSection.type === 'public_transport') {
2576
+ leg.transportInfo = {
2577
+ name: jsonSection.display_informations.code,
2578
+ routeColor: jsonSection.display_informations.color,
2579
+ routeTextColor: jsonSection.display_informations.text_color,
2580
+ directionName: jsonSection.display_informations.direction
2581
+ };
2582
+
2583
+ leg.mode = routingModeCorrespondance.get(jsonSection.display_informations.physical_mode);
2584
+
2585
+ const legStep = new Step();
2586
+ legStep.coords = leg.coords[0];
2587
+ legStep._idCoordsInLeg = 0;
2588
+ legStep.name = leg.transportInfo.directionName;
2589
+ legStep.levelChange = null;
2590
+ legStep.distance = jsonSection.geojson.properties[0].length;
2591
+
2592
+ leg.steps = [legStep];
2593
+ }
2594
+
2595
+ itinerary.distance += leg.distance;
2596
+
2597
+ itinerary.legs.push(leg);
2598
+
2599
+ }
2600
+
2601
+ // All legs have to be parsed before computing steps metadata
2602
+ generateStepsMetadata(itinerary);
2603
+ }
2604
+
2605
+ return routerResponse;
2606
+ }
2607
+ }
2608
+
2609
+ var IdfmRemoteRouter$1 = new IdfmRemoteRouter();
2610
+
2611
+ /* eslint-disable max-statements */
2612
+
2613
+ /**
2614
+ * Input mode correspondance
2615
+ */
2616
+ const inputModeCorrespondance$1 = new Map();
2617
+ inputModeCorrespondance$1.set(Constants.ROUTING_MODE.CAR, 'driving');
2618
+ inputModeCorrespondance$1.set(Constants.ROUTING_MODE.WALK, 'walking');
2619
+ inputModeCorrespondance$1.set(Constants.ROUTING_MODE.BIKE, 'bike');
2620
+ inputModeCorrespondance$1.set(Constants.ROUTING_MODE.BUS, 'bus');
2621
+ inputModeCorrespondance$1.set(Constants.ROUTING_MODE.MULTI, 'walking');
2622
+
2623
+
2624
+ /**
2625
+ * Singleton.
2626
+ */
2627
+ class OsrmRemoteRouter extends RemoteRouter {
2628
+
2629
+ /**
2630
+ * @override
2631
+ */
2632
+ get rname() {
2633
+ return 'osrm';
2634
+ }
2635
+
2636
+ /**
2637
+ * @override
2638
+ * @throws {RemoteRouterServerUnreachable}
2639
+ * @throws {RoutingModeCorrespondanceNotFound}
2640
+ */
2641
+ async getItineraries(endpointUrl, mode, waypoints) {
2642
+ const url = this.getURL(endpointUrl, mode, waypoints);
2643
+
2644
+ const res = await (fetch(url).catch(() => {
2645
+ throw new RemoteRouterServerUnreachable(this.rname, url);
2646
+ }));
2647
+
2648
+ const jsonResponse = await res.json().catch(() => {
2649
+ throw new RemoteRouterServerUnreachable(this.rname, url);
2650
+ });
2651
+
2652
+ return this.createRouterResponseFromJson(jsonResponse, waypoints[0], waypoints[1]);
2653
+ }
2654
+
2655
+ /**
2656
+ * @param {string} endpointUrl
2657
+ * @param {string} mode
2658
+ * @param {Array<Coordinates>} waypoints
2659
+ * @throws {RoutingModeCorrespondanceNotFound}
2660
+ */
2661
+ getURL(endpointUrl, mode, waypoints) {
2662
+
2663
+ const osrmMode = inputModeCorrespondance$1.get(mode);
2664
+ if (!osrmMode) {
2665
+ throw new RoutingModeCorrespondanceNotFound(this.rname, mode);
2666
+ }
2667
+
2668
+ let url = endpointUrl + '/route/v1/' + osrmMode + '/';
2669
+ url += waypoints.map(waypoint => [waypoint.longitude + ',' + waypoint.latitude]).join(';');
2670
+ url += '?geometries=geojson&overview=full&steps=true';
2671
+
2672
+ return url;
2673
+ }
2674
+
2675
+ /**
2676
+ * @param {Coordinates} coordinates
2677
+ * @returns {object}
2678
+ */
2679
+ coordinatesToJson(coordinates) {
2680
+ const output = [coordinates.lng, coordinates.lat];
2681
+ if (coordinates.level) {
2682
+ output.push(coordinates.level.toString());
2683
+ }
2684
+ return output;
2685
+ }
2686
+
2687
+ /**
2688
+ * @param {object} json
2689
+ * @returns {Coordinates}
2690
+ */
2691
+ jsonToCoordinates(json) {
2692
+ const output = new Coordinates(json[1], json[0]);
2693
+ if (json.length > 2) {
2694
+ output.level = Level.fromString(json[2]);
2695
+ }
2696
+ return output;
2697
+ }
2698
+
2699
+ nodesToJsonCoords(nodes) {
2700
+ return nodes.map(node => this.coordinatesToJson(node.coords));
2701
+ }
2702
+
2703
+
2704
+ getModifierFromAngle(_angle) {
2705
+
2706
+ const angle = positiveMod(rad2deg(_angle), 360);
2707
+
2708
+ if (angle > 0 && angle < 60) {
2709
+ return 'sharp right';
2710
+ }
2711
+ if (angle >= 60 && angle < 140) {
2712
+ return 'right';
2713
+ }
2714
+ if (angle >= 140 && angle < 160) {
2715
+ return 'slight right';
2716
+ }
2717
+ if (angle >= 160 && angle <= 200) {
2718
+ return 'straight';
2719
+ }
2720
+ if (angle > 200 && angle <= 220) {
2721
+ return 'slight left';
2722
+ }
2723
+ if (angle > 220 && angle <= 300) {
2724
+ return 'left';
2725
+ }
2726
+ if (angle > 300 && angle < 360) {
2727
+ return 'sharp left';
2728
+ }
2729
+ return 'u turn';
2730
+ }
2731
+
2732
+
2733
+ noRouteFoundJson(message) {
2734
+ return {
2735
+ 'code': 'NoRoute',
2736
+ message
2737
+ };
2738
+ }
2739
+
2740
+ /**
2741
+ * @param {Itinerary} itinerary
2742
+ * @returns {object}
2743
+ */
2744
+ itineraryToOsrmJson(itinerary) {
2745
+
2746
+ const lastLegId = itinerary.legs.length - 1;
2747
+
2748
+ const jsonLegs = itinerary.legs.map(({ distance, duration, coords, steps }, idLeg) => {
2749
+
2750
+ const lastStepId = steps.length - 1;
2751
+
2752
+ return {
2753
+ distance,
2754
+ duration,
2755
+ steps: steps.map((step, idStep, arr) => {
2756
+
2757
+ let type = idStep === 0 && idLeg === 0 ? 'depart' : 'turn';
2758
+ type = idStep === lastStepId && idLeg === lastLegId ? 'arrive' : type;
2759
+
2760
+ const stepCoordsIdx = coords.findIndex(p => p.equalsTo(step.coords));
2761
+ const nextStepCoordsIdx = idStep === lastStepId
2762
+ ? stepCoordsIdx
2763
+ : coords.findIndex(p => p.equalsTo(arr[idStep + 1].coords));
2764
+
2765
+ const jsonStep = {
2766
+ geometry: {
2767
+ type: 'LineString',
2768
+ coordinates: coords.slice(stepCoordsIdx, nextStepCoordsIdx + 1).map(this.coordinatesToJson)
2769
+ },
2770
+ distance: step.distance,
2771
+ duration: step.duration,
2772
+ name: step.name,
2773
+ maneuver: {
2774
+ bearing_before: rad2deg(step.previousBearing),
2775
+ bearing_after: rad2deg(step.nextBearing),
2776
+ location: this.coordinatesToJson(step.coords),
2777
+ modifier: this.getModifierFromAngle(step.angle),
2778
+ type
2779
+ }
2780
+ };
2781
+ if (step.levelChange !== null) {
2782
+ jsonStep.levelChange = step.levelChange.toJson();
2783
+ }
2784
+ if (typeof step.extras === 'object' && Object.keys(step.extras).length !== 0) {
2785
+ jsonStep.extras = step.extras;
2786
+ }
2787
+
2788
+ return jsonStep;
2789
+ })
2790
+ };
2791
+ });
2792
+
2793
+ return {
2794
+ 'code': 'Ok',
2795
+ 'routes': [
2796
+ {
2797
+ 'geometry': {
2798
+ 'type': 'LineString',
2799
+ 'coordinates': itinerary.coords.map(this.coordinatesToJson)
2800
+ },
2801
+ 'legs': jsonLegs,
2802
+ 'distance': itinerary.distance,
2803
+ 'duration': itinerary.duration,
2804
+ 'weight_name': 'routability',
2805
+ 'weight': 0
2806
+ }
2807
+ ],
2808
+ 'waypoints': []
2809
+ };
2810
+ }
2811
+
2812
+ /**
2813
+ * @param {object} jsonSteps
2814
+ * @param {Coordinates[]} legCoords
2815
+ * @returns {Step[]}
2816
+ */
2817
+ parseJsonSteps(jsonSteps, legCoords) {
2818
+
2819
+ if (!jsonSteps) {
2820
+ return [];
2821
+ }
2822
+
2823
+ return jsonSteps.map(jsonStep => {
2824
+
2825
+ const step = new Step();
2826
+ step.coords = this.jsonToCoordinates(jsonStep.maneuver.location);
2827
+
2828
+ // Sometimes, OSRM step does not have the same coordinates than a point in legCoords.
2829
+ // 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
2830
+ // That is why we look for the closest point.
2831
+ const distances = legCoords.map(coords => coords.distanceTo(step.coords));
2832
+ const idStepCoordsInLeg = distances.indexOf(Math.min(...distances));
2833
+ if (idStepCoordsInLeg < 0) {
2834
+ throw new Error('Osrm Parser: Cannot find step coords in leg coordinates');
2835
+ }
2836
+ step._idCoordsInLeg = idStepCoordsInLeg;
2837
+
2838
+ step.name = jsonStep.name;
2839
+ step.levelChange = jsonStep.levelChange ? LevelChange.fromJson(jsonStep.levelChange) : null;
2840
+
2841
+ step.distance = jsonStep.distance;
2842
+ step.duration = jsonStep.duration;
2843
+
2844
+ if (jsonStep.extras && jsonStep.extras.subwayEntrance) {
2845
+ step.extras.subwayEntrance = true;
2846
+ if (jsonStep.extras.subwayEntranceRef) {
2847
+ step.extras.subwayEntranceRef = jsonStep.extras.subwayEntranceRef;
2848
+ }
2849
+ }
2850
+
2851
+ return step;
2852
+ });
2853
+ }
2854
+
2855
+ /**
2856
+ * Generate multi itineraries from OSRM JSON
2857
+ * @param {object} json JSON file provided by OSRM.
2858
+ * @param {Coordinates} from itinerary start
2859
+ * @param {Coordinates} to itinerary end
2860
+ * @param {?string} routingMode [walking|driving|bicycle]
2861
+ * @returns {?RouterResponse}
2862
+ */
2863
+ createRouterResponseFromJson(json, from, to, routingMode = 'walking') {
2864
+ const { routes: jsonRoutes } = json;
2865
+
2866
+ if (!jsonRoutes) {
2867
+ return null;
2868
+ }
2869
+
2870
+ const routingModeCorrespondance = new Map();
2871
+ routingModeCorrespondance.set('walking', 'WALK');
2872
+ routingModeCorrespondance.set('driving', 'CAR');
2873
+ routingModeCorrespondance.set('bicycle', 'BIKE');
2874
+ const mode = routingModeCorrespondance.get(routingMode) || null;
2875
+
2876
+ const routerResponse = new RouterResponse();
2877
+ routerResponse.routerName = this.rname;
2878
+
2879
+ routerResponse.from = from;
2880
+ routerResponse.to = to;
2881
+
2882
+ for (const jsonItinerary of jsonRoutes) {
2883
+
2884
+ const itinerary = new Itinerary();
2885
+
2886
+ // itinerary.coords = jsonItinerary.geometry.coordinates.map(jsonToCoordinates);
2887
+ itinerary.distance = jsonItinerary.distance;
2888
+ itinerary.duration = jsonItinerary.duration;
2889
+ itinerary.from = from;
2890
+ itinerary.to = to;
2891
+
2892
+ routerResponse.itineraries.push(itinerary);
2893
+
2894
+ for (const jsonLeg of jsonItinerary.legs) {
2895
+
2896
+ const leg = new Leg();
2897
+
2898
+ leg.mode = mode;
2899
+ leg.distance = jsonLeg.distance;
2900
+ leg.duration = jsonLeg.duration;
2901
+
2902
+ leg.coords = jsonLeg.steps
2903
+ .map(step => step.geometry.coordinates.map(this.jsonToCoordinates))
2904
+ .flat()
2905
+ // Remove duplicates
2906
+ .filter((coords, idx, arr) => idx === 0 || !arr[idx - 1].equalsTo(coords));
2907
+
2908
+ leg.from = {
2909
+ name: null,
2910
+ coords: leg.coords[0]
2911
+ };
2912
+ leg.to = {
2913
+ name: null,
2914
+ coords: leg.coords[leg.coords.length - 1]
2915
+ };
2916
+
2917
+ leg.steps = this.parseJsonSteps(jsonLeg.steps, leg.coords);
2918
+
2919
+ itinerary.legs.push(leg);
2920
+ }
2921
+
2922
+ // All legs have to be parsed before computing steps metadata
2923
+ generateStepsMetadata(itinerary);
2924
+
2925
+ }
2926
+
2927
+ return routerResponse;
2928
+ }
2929
+ }
2930
+
2931
+ var OsrmRemoteRouter$1 = new OsrmRemoteRouter();
2932
+
2933
+ /* eslint-disable max-statements */
2934
+
2935
+ /**
2936
+ * Input mode correspondance
2937
+ */
2938
+ const inputModeCorrespondance = new Map();
2939
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.CAR, 'CAR');
2940
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.WALK, 'WALK');
2941
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.BIKE, 'BICYCLE');
2942
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.BUS, 'WALK,TRANSIT');
2943
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.MULTI, 'WALK,TRANSIT');
2944
+
2945
+ /**
2946
+ * Singleton.
2947
+ */
2948
+ class OtpRemoteRouter extends RemoteRouter {
2949
+
2950
+ /**
2951
+ * @override
2952
+ */
2953
+ get rname() {
2954
+ return 'otp';
2955
+ }
2956
+
2957
+ /**
2958
+ * @override
2959
+ * @throws {RemoteRouterServerUnreachable}
2960
+ * @throws {RoutingModeCorrespondanceNotFound}
2961
+ */
2962
+ async getItineraries(endpointUrl, mode, waypoints) {
2963
+ const url = this.getURL(endpointUrl, mode, waypoints);
2964
+ const res = await (fetch(url).catch(() => {
2965
+ throw new RemoteRouterServerUnreachable(this.rname, url);
2966
+ }));
2967
+
2968
+ const jsonResponse = await res.json().catch(() => {
2969
+ throw new RemoteRouterServerUnreachable(this.rname, url);
2970
+ });
2971
+ return this.createRouterResponseFromJson(jsonResponse);
2972
+ }
2973
+
2974
+ /**
2975
+ * @param {string} endpointUrl
2976
+ * @param {string} mode
2977
+ * @param {Array<Coordinates>} waypoints
2978
+ * @throws {RoutingModeCorrespondanceNotFound}
2979
+ */
2980
+ getURL(endpointUrl, mode, waypoints) {
2981
+
2982
+ const otpMode = inputModeCorrespondance.get(mode);
2983
+ if (!otpMode) {
2984
+ throw new RoutingModeCorrespondanceNotFound(this.rname, mode);
2985
+ }
2986
+
2987
+ if (waypoints.length > 2) {
2988
+ Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
2989
+ }
2990
+
2991
+ const fromPlace = `fromPlace=${waypoints[0].latitude},${waypoints[0].longitude}`;
2992
+ const toPlace = `toPlace=${waypoints[1].latitude},${waypoints[1].longitude}`;
2993
+ const queryMode = `mode=${otpMode}`;
2994
+
2995
+ const url = new URL(endpointUrl);
2996
+ let { search } = url;
2997
+ search = (search ? `${search}&` : '?') + `${fromPlace}&${toPlace}&${queryMode}`;
2998
+
2999
+ return `${url.origin}${url.pathname}${search}`;
3000
+ }
3001
+
3002
+ /**
3003
+ * @param {object} json
3004
+ * @returns {Coordinates}
3005
+ */
3006
+ jsonToCoordinates(json) {
3007
+ return new Coordinates(json.lat, json.lon);
3008
+ }
3009
+
3010
+ /**
3011
+ * @param {object} jsonSteps
3012
+ * @param {Coordinates[]} legCoords
3013
+ * @returns {Step[]}
3014
+ */
3015
+ parseJsonSteps(jsonSteps, legCoords) {
3016
+
3017
+ if (!jsonSteps) {
3018
+ return [];
3019
+ }
3020
+
3021
+ return jsonSteps.map(jsonStep => {
3022
+
3023
+ const step = new Step();
3024
+ const stepCoords = this.jsonToCoordinates(jsonStep);
3025
+
3026
+ // OTP step does not have the same coordinates than a point in legCoords.
3027
+ // That is why we look for the closest point.
3028
+ const distances = legCoords.map(coords => coords.distanceTo(stepCoords));
3029
+ const idStepCoordsInLeg = distances.indexOf(Math.min(...distances));
3030
+ if (idStepCoordsInLeg < 0) {
3031
+ throw new Error('OTP Parser: Cannot find closest step');
3032
+ }
3033
+ step.coords = legCoords[idStepCoordsInLeg];
3034
+ step._idCoordsInLeg = idStepCoordsInLeg;
3035
+
3036
+ step.name = jsonStep.streetName;
3037
+ step.levelChange = null;
3038
+
3039
+ step.distance = jsonStep.distance;
3040
+
3041
+ return step;
3042
+ });
3043
+ }
3044
+
3045
+ /**
3046
+ * Generate multi itineraries from OTP JSON
3047
+ * @param {object} json JSON file provided by OTP.
3048
+ * @returns {?RouterResponse}
3049
+ */
3050
+ createRouterResponseFromJson(json) {
3051
+
3052
+ const { plan: jsonPlan } = json;
3053
+
3054
+ if (!jsonPlan) {
3055
+ return null;
3056
+ }
3057
+
3058
+ const routerResponse = new RouterResponse();
3059
+ routerResponse.routerName = this.rname;
3060
+
3061
+ routerResponse.from = this.jsonToCoordinates(jsonPlan.from);
3062
+ routerResponse.to = this.jsonToCoordinates(jsonPlan.to);
3063
+
3064
+ for (const jsonItinerary of jsonPlan.itineraries) {
3065
+
3066
+ const itinerary = new Itinerary();
3067
+
3068
+ itinerary.duration = jsonItinerary.duration;
3069
+ itinerary.startTime = jsonItinerary.startTime;
3070
+ itinerary.endTime = jsonItinerary.endTime;
3071
+ itinerary.from = routerResponse.from;
3072
+ itinerary.to = routerResponse.to;
3073
+
3074
+ routerResponse.itineraries.push(itinerary);
3075
+
3076
+ for (const jsonLeg of jsonItinerary.legs) {
3077
+
3078
+ const leg = new Leg();
3079
+
3080
+ leg.mode = jsonLeg.mode;
3081
+ leg.duration = jsonLeg.duration;
3082
+ leg.startTime = jsonLeg.startTime;
3083
+ leg.endTime = jsonLeg.endTime;
3084
+ leg.from = {
3085
+ name: jsonLeg.from.name,
3086
+ coords: this.jsonToCoordinates(jsonLeg.from)
3087
+ };
3088
+ leg.to = {
3089
+ name: jsonLeg.to.name,
3090
+ coords: this.jsonToCoordinates(jsonLeg.to)
3091
+ };
3092
+ leg.coords = Polyline.decode(jsonLeg.legGeometry.points).map(([lat, lon]) => new Coordinates(lat, lon));
3093
+
3094
+ leg.steps = this.parseJsonSteps(jsonLeg.steps, leg.coords);
3095
+
3096
+ if (leg.mode === 'BUS' || leg.mode === 'TRAM') {
3097
+ leg.transportInfo = {
3098
+ name: jsonLeg.route,
3099
+ routeColor: jsonLeg.routeColor,
3100
+ routeTextColor: jsonLeg.routeTextColor,
3101
+ directionName: jsonLeg.headsign
3102
+ };
3103
+
3104
+ const legStep = new Step();
3105
+ legStep.coords = leg.coords[0];
3106
+ legStep._idCoordsInLeg = 0;
3107
+ legStep.name = jsonLeg.headsign;
3108
+ legStep.levelChange = null;
3109
+ legStep.distance = jsonLeg.distance;
3110
+ leg.steps = [legStep];
3111
+ }
3112
+
3113
+ // jsonLeg.distance is not reliable when compared to the array of leg coords.
3114
+ // leg.distance = jsonLeg.distance;
3115
+ leg.distance = leg.coords.reduce((acc, coords, idx, arr) => {
3116
+ if (idx === 0) {
3117
+ return acc;
3118
+ }
3119
+ return acc + arr[idx - 1].distanceTo(coords);
3120
+ }, 0);
3121
+
3122
+ itinerary.legs.push(leg);
3123
+
3124
+ }
3125
+
3126
+ itinerary.distance = itinerary.coords.reduce((acc, coords, idx, arr) => {
3127
+ if (idx === 0) {
3128
+ return acc;
3129
+ }
3130
+ return acc + arr[idx - 1].distanceTo(coords);
3131
+ }, 0);
3132
+
3133
+ // All legs have to be parsed before computing steps metadata
3134
+ generateStepsMetadata(itinerary);
3135
+ }
3136
+
3137
+ return routerResponse;
3138
+ }
3139
+ }
3140
+
3141
+ var OtpRemoteRouter$1 = new OtpRemoteRouter();
3142
+
3143
+ // Also extends properties of WemapMetaRouterOptions
3144
+ class WemapMetaRemoteRouterOptions extends RemoteRouterOptions {
3145
+
3146
+ constructor() {
3147
+ super();
3148
+ // Ugly multiple inheritance trick
3149
+ Object.assign(this, new WemapMetaRouterOptions());
3150
+ }
3151
+
3152
+ /**
3153
+ * @returns {object}
3154
+ */
3155
+ toJson() {
3156
+ const json = super.toJson();
3157
+ json.remoteRouters = this.remoteRouters;
3158
+ if (this.targetMaps) {
3159
+ json.targetMaps = this.targetMaps;
3160
+ }
3161
+ return json;
3162
+ }
3163
+
3164
+ /**
3165
+ * @param {object}
3166
+ * @returns {WemapMetaRemoteRouterOptions}
3167
+ */
3168
+ static fromJson(json) {
3169
+ const obj = new WemapMetaRemoteRouterOptions();
3170
+ if ('useStairs' in json) {
3171
+ obj.useStairs = json.useStairs;
3172
+ }
3173
+ obj.remoteRouters = json.remoteRouters ? json.remoteRouters : [];
3174
+ obj.targetMaps = json.targetMaps ? json.targetMaps : null;
3175
+ return obj;
3176
+ }
3177
+ }
3178
+
3179
+ class WemapMetaRemoteRouterPayload {
3180
+
3181
+ /** @type {!(Coordinates[])} */
3182
+ waypoints;
3183
+
3184
+ /** @type {!string} */
3185
+ mode;
3186
+
3187
+ /** @type {!WemapMetaRemoteRouterOptions} */
3188
+ options = null;
3189
+
3190
+ /**
3191
+ * @returns {object}
3192
+ */
3193
+ toJson() {
3194
+ const json = {
3195
+ waypoints: this.waypoints.map(coords => coords.toCompressedJson()),
3196
+ mode: this.mode
3197
+ };
3198
+ if (this.options) {
3199
+ json.options = this.options.toJson();
3200
+ }
3201
+ return json;
3202
+ }
3203
+
3204
+ /**
3205
+ * @param {object}
3206
+ * @returns {WemapMetaRemoteRouterPayload}
3207
+ */
3208
+ static fromJson(json) {
3209
+ const obj = new WemapMetaRemoteRouterPayload();
3210
+ obj.waypoints = json.waypoints.map(coords => Coordinates.fromCompressedJson(coords));
3211
+ obj.mode = json.mode;
3212
+ if (json.options) {
3213
+ obj.options = WemapMetaRemoteRouterOptions.fromJson(json.options);
3214
+ }
3215
+ return obj;
3216
+ }
3217
+ }
3218
+
3219
+ /* eslint-disable max-statements */
3220
+
3221
+
3222
+ /**
3223
+ * Singleton.
3224
+ */
3225
+ class WemapMetaRemoteRouter extends RemoteRouter {
3226
+
3227
+ /**
3228
+ * @override
3229
+ */
3230
+ get rname() {
3231
+ return 'wemap-meta';
3232
+ }
3233
+
3234
+ /**
3235
+ * @param {!string} endpointUrl
3236
+ * @param {!string} mode see Constants.ROUTING_MODE
3237
+ * @param {Array<Coordinates>} waypoints
3238
+ * @param {?WemapMetaRemoteRouterOptions} options
3239
+ * @returns {!RouterResponse}
3240
+ * @throws {RemoteRouterServerUnreachable}
3241
+ */
3242
+ async getItineraries(endpointUrl, mode, waypoints,
3243
+ options = new WemapMetaRemoteRouterOptions()) {
3244
+
3245
+ const payload = new WemapMetaRemoteRouterPayload();
3246
+ payload.waypoints = waypoints;
3247
+ payload.mode = mode;
3248
+ payload.options = options;
3249
+
3250
+ const res = await (fetch(endpointUrl, {
3251
+ method: 'POST',
3252
+ headers: {
3253
+ 'Accept': 'application/json',
3254
+ 'Content-Type': 'application/json'
3255
+ },
3256
+ body: JSON.stringify(payload.toJson())
3257
+ }).catch(() => {
3258
+ throw new RemoteRouterServerUnreachable(this.rname, endpointUrl);
3259
+ }));
3260
+
3261
+ const jsonResponse = await res.json().catch(() => {
3262
+ throw new RemoteRouterServerUnreachable(this.rname, endpointUrl);
3263
+ });
3264
+
3265
+ return RouterResponse.fromJson(jsonResponse);
3266
+ }
3267
+
3268
+ }
3269
+
3270
+ var WemapMetaRemoteRouter$1 = new WemapMetaRemoteRouter();
3271
+
3272
+ /**
3273
+ * Singleton
3274
+ */
3275
+ class RemoteRouterManager {
3276
+
3277
+ /** @type {RemoteRouter[]} */
3278
+ remoteRouters = [
3279
+ CitywayRemoteRouter$1,
3280
+ DeutscheBahnRemoteRouter$1,
3281
+ IdfmRemoteRouter$1,
3282
+ OsrmRemoteRouter$1,
3283
+ OtpRemoteRouter$1,
3284
+ WemapMetaRemoteRouter$1
3285
+ ];
3286
+
3287
+ /**
3288
+ * @param {string} name
3289
+ * @returns {RemoteRouter}
3290
+ */
3291
+ getRouterByName(name) {
3292
+ return this.remoteRouters.find(remoteRouter => remoteRouter.rname === name);
3293
+ }
3294
+
3295
+ /**
3296
+ * @param {!string} endpointUrl
3297
+ * @param {!string} mode see Constants.ROUTING_MODE
3298
+ * @param {Array<Coordinates>} waypoints
3299
+ * @param {?RemoteRouterOptions} options
3300
+ * @returns {!RouterResponse}
3301
+ * @throws {RemoteRouterServerUnreachable}
3302
+ * @throws {RoutingModeCorrespondanceNotFound}
3303
+ * @throws {IdfmRemoteRouterTokenError}
3304
+ */
3305
+ async getItineraries(name, endpointUrl, mode, waypoints, options = new RemoteRouterOptions()) {
3306
+ const router = this.getRouterByName(name);
3307
+ if (!router) {
3308
+ throw new Error(`Unknown "${this.rname}" remote router`);
3309
+ }
3310
+ return router.getItineraries(endpointUrl, mode, waypoints, options);
3311
+ }
3312
+
3313
+
3314
+ /**
3315
+ * @param {!{name: string, endpointUrl: string}[]} remoteRouters
3316
+ * @param {!string} mode see Constants.ROUTING_MODE
3317
+ * @param {!Array<Coordinates>} waypoints
3318
+ * @param {?RemoteRouterOptions} options
3319
+ * @returns {!RouterResponse}
3320
+ * @throws {RemoteRouterServerUnreachable}
3321
+ * @throws {RoutingModeCorrespondanceNotFound}
3322
+ * @throws {IdfmRemoteRouterTokenError}
3323
+ */
3324
+ async getItinerariesWithFallback(remoteRouters, mode, waypoints, options = new RemoteRouterOptions()) {
3325
+ let routerResponse;
3326
+ for (const { name, endpointUrl } of remoteRouters) {
3327
+ routerResponse = await this.getItineraries(name, endpointUrl, mode, waypoints, options);
3328
+ if (routerResponse.itineraries.length) {
3329
+ return routerResponse;
3330
+ }
3331
+ }
3332
+ if (!routerResponse) {
3333
+ routerResponse = new RouterResponse();
3334
+ routerResponse.from = waypoints[0];
3335
+ routerResponse.to = waypoints[waypoints.length];
3336
+ }
3337
+ return routerResponse;
3338
+ }
3339
+
3340
+ }
3341
+
3342
+ var RemoteRouterManager$1 = new RemoteRouterManager();
3343
+
3344
+ /* eslint-disable complexity */
3345
+
3346
+
3347
+ class WemapMetaRouter {
3348
+
3349
+ /** @type {!(IOMap[])} */
3350
+ maps = [];
3351
+
3352
+
3353
+ /** @type {string} */
3354
+ get rname() {
3355
+ return 'wemap-meta';
3356
+ }
3357
+
3358
+ /**
3359
+ * @param {!IOMap} ioMap
3360
+ */
3361
+ addIOMap(ioMap) {
3362
+ this.maps.push(ioMap);
3363
+ }
3364
+
3365
+ /**
3366
+ * @param {!IOMap} ioMap
3367
+ */
3368
+ removeIOMap(ioMap) {
3369
+ this.maps = this.maps.filter(map => map !== ioMap);
3370
+ }
3371
+
3372
+ /**
3373
+ * @param {string} mode see Constants.ROUTING_MODE
3374
+ * @param {Coordinates[]} waypoints
3375
+ * @param {WemapMetaRouterOptions} options
3376
+ * @returns {RouterResponse}
3377
+ */
3378
+ async getItineraries(mode, waypoints, options) {
3379
+
3380
+ /*
3381
+ * Here, we try to get the shortest path using io maps networks and a remote router server.
3382
+ */
3383
+
3384
+ /**
3385
+ * ----- 1 -----
3386
+ * Parse function params
3387
+ * -------------
3388
+ */
3389
+
3390
+ if (waypoints.length > 2) {
3391
+ Logger.warn(`WemapMetaRouter uses only the first 2 waypoints (asked ${waypoints.length})`);
3392
+ }
3393
+ const start = waypoints[0];
3394
+ const end = waypoints[1];
3395
+
3396
+ const routerResponse = new RouterResponse();
3397
+ routerResponse.routerName = this.rname;
3398
+ routerResponse.from = start;
3399
+ routerResponse.to = end;
3400
+
3401
+
3402
+ // Avoid cycles on remoteRouters
3403
+ const remoteRouters = options.remoteRouters.filter(
3404
+ ({ name }) => name !== WemapMetaRemoteRouterOptions.rname
3405
+ );
3406
+
3407
+ /*
3408
+ * ----- 2 -----
3409
+ * Retrieve the IO maps to consider for this itinerary
3410
+ * -------------
3411
+ *
3412
+ * By default, all maps in this.maps are considered
3413
+ * If options.targetMaps is defined, only use this subset
3414
+ */
3415
+ let ioMapsToTest = this.maps;
3416
+
3417
+ const { targetMaps } = options;
3418
+ if (targetMaps) {
3419
+
3420
+ ioMapsToTest = this.maps.filter(map => targetMaps.includes(map.name));
3421
+
3422
+ // Send a warning if one of the request ioMap (from targetMaps) is not present in this.maps
3423
+ if (ioMapsToTest.length !== targetMaps.length) {
3424
+ ioMapsToTest.forEach(map => {
3425
+ if (!targetMaps.includes(map.name)) {
3426
+ Logger.warn(`IOMap "${map.name}" not found in WemapMetaRouter`);
3427
+ }
3428
+ });
3429
+ }
3430
+ }
3431
+
3432
+ /*
3433
+ * If there is no local map to test, use remote router directly.
3434
+ * This should happen:
3435
+ * 1 - this.maps is empty
3436
+ * 2 - options.targetMaps is defined but empty
3437
+ * 3 - intersection of this.maps and options.targetMaps is empty
3438
+ */
3439
+ if (!ioMapsToTest.length) {
3440
+ try {
3441
+ return await RemoteRouterManager$1.getItinerariesWithFallback(remoteRouters, mode, waypoints);
3442
+ } catch (e) {
3443
+ if (!isRoutingError(e)) {
3444
+ throw e;
3445
+ }
3446
+ routerResponse.error = e.message;
3447
+ return routerResponse;
3448
+ }
3449
+ }
3450
+
3451
+
3452
+ /**
3453
+ * ----- 3 -----
3454
+ * Run the IO Maps - Remote Routers logic
3455
+ * -------------
3456
+ *
3457
+ * For this purpose we have to consider 5 use cases
3458
+ *
3459
+ */
3460
+
3461
+ /** @type {Itinerary} */
3462
+ let ioMapItinerary;
3463
+
3464
+ // Find the first map where the "start" is inside.
3465
+ const mapWithStart = ioMapsToTest.find(map => map.isPointInside(start));
3466
+
3467
+ // Create WemapRouterOptions from WemapMetaRouterOptions
3468
+ const wemapRouterOptions = options.useStairs
3469
+ ? WemapRouterOptions.DEFAULT
3470
+ : WemapRouterOptions.WITHOUT_STAIRS;
3471
+
3472
+ /*
3473
+ * Case 1
3474
+ *
3475
+ * If "start" and "end" are in the same map, use the local router.
3476
+ */
3477
+ if (mapWithStart && mapWithStart.isPointInside(end)) {
3478
+
3479
+ try {
3480
+ ioMapItinerary = mapWithStart.getItineraryInsideMap(start, end, wemapRouterOptions);
3481
+ } catch (e) {
3482
+ if (!isRoutingError(e)) {
3483
+ throw e;
3484
+ }
3485
+ routerResponse.error = `${e.message} - on map ${mapWithStart.name}.`;
3486
+ return routerResponse;
3487
+ }
3488
+
3489
+ routerResponse.itineraries.push(ioMapItinerary);
3490
+ routerResponse.routerName = [this.rname, WemapRouter.rname];
3491
+
3492
+ return routerResponse;
3493
+ }
3494
+
3495
+ // Find the first map where the "end" is inside.
3496
+ // Note: At this step, mapWithEnd is necessarily different from mapWithStart
3497
+ const mapWithEnd = ioMapsToTest.find(map => map.isPointInside(end));
3498
+
3499
+
3500
+ /** @type {RouterResponse} */
3501
+ let remoteRouterResponse;
3502
+
3503
+ /*
3504
+ * Case 2
3505
+ *
3506
+ * If no io map have been found for "start" and "end", therefore use remote router.
3507
+ */
3508
+ if (!mapWithStart && !mapWithEnd) {
3509
+ try {
3510
+ return await RemoteRouterManager$1.getItinerariesWithFallback(remoteRouters, mode, waypoints);
3511
+ } catch (e) {
3512
+ if (!isRoutingError(e)) {
3513
+ throw e;
3514
+ }
3515
+ routerResponse.error = e.message;
3516
+ return routerResponse;
3517
+ }
3518
+ }
3519
+
3520
+ /**
3521
+ * Case 3
3522
+ *
3523
+ * If a map has been found for the "start" but not for the "end", so:
3524
+ * - A first itinerary (firstRoute) is calculated from "start" to an "entrypoint"
3525
+ * of the IO map network using the wemap router.
3526
+ * - A second itinerary (secondRoute) is calculated from an "entrypoint" to the
3527
+ * "end" using remote routers.
3528
+ * Itinerary returned is the concatenation of the both itineraries.
3529
+ *
3530
+ * Note: Check the mapWithEnd.getBestItineraryFromEntryPointsToEnd to understand
3531
+ * which "entrypoint" is chosen by the algorithm
3532
+ */
3533
+ if (mapWithStart && !mapWithEnd) {
3534
+
3535
+ if (!mapWithStart.entryPoints.length) {
3536
+ routerResponse.error = `A map including the "start" but the "end" has been
3537
+ found (${mapWithStart.name}), however, no "entrypoints" have been found to go out`;
3538
+ return routerResponse;
3539
+ }
3540
+
3541
+ try {
3542
+ ioMapItinerary = mapWithStart.getBestItineraryFromStartToEntryPoints(start, end, wemapRouterOptions);
3543
+ remoteRouterResponse = await RemoteRouterManager$1.getItinerariesWithFallback(
3544
+ remoteRouters, mode, [ioMapItinerary.to, end]
3545
+ );
3546
+ if (!remoteRouterResponse.itineraries.length) {
3547
+ throw new NoRouteFoundError(ioMapItinerary.to, end, remoteRouterResponse.error);
3548
+ }
3549
+ } catch (e) {
3550
+ if (!isRoutingError(e)) {
3551
+ throw e;
3552
+ }
3553
+ routerResponse.error = 'Tried to calculate an itinerary from "start" '
3554
+ + `to "entrypoints" using wemap router on local map "${mapWithStart.name}" and `
3555
+ + 'an itinerary from "entrypoints" to "end" using remote routers '
3556
+ + `(${remoteRouters.map(r => r.name).join(', ')}), but failed. `
3557
+ + `Details: ${e.message}.`;
3558
+ return routerResponse;
3559
+ }
3560
+
3561
+ // Concat the the IO map itinerary with the remote router response (for each itinerary)
3562
+ routerResponse.itineraries = remoteRouterResponse.itineraries.map(
3563
+ remoteRouterItinerary => Itinerary.fromItineraries(ioMapItinerary, remoteRouterItinerary)
3564
+ );
3565
+ routerResponse.routerName = [this.rname, remoteRouterResponse.routerName, WemapRouter.rname];
3566
+ return routerResponse;
3567
+ }
3568
+
3569
+ /*
3570
+ * Case 4
3571
+ *
3572
+ * If a map has been found for the "end" but not for the "start", so:
3573
+ * - A first itinerary (remoteRouterResponse) is calculated from "start" to an "entrypoint"
3574
+ * of the IO map network using remote routers.
3575
+ * - A second itinerary (ioMapItinerary) is calculated from an "entrypoint" to the
3576
+ * "end" using the wemap router.
3577
+ * Itinerary returned is the concatenation of the both itineraries.
3578
+ *
3579
+ * Note: Check the mapWithEnd.getBestItineraryFromEntryPointsToEnd to understand
3580
+ * which "entrypoint" is chosen by the algorithm
3581
+ */
3582
+ if (!mapWithStart && mapWithEnd) {
3583
+
3584
+ if (!mapWithEnd.entryPoints.length) {
3585
+ routerResponse.error = `A map including the "end" but the "start" has been
3586
+ found (${mapWithEnd.name}), however, no "entrypoints" have been found to go in`;
3587
+ return routerResponse;
3588
+ }
3589
+
3590
+ /*
3591
+ * ioMapItinerary is computed before the remoteRouterResponse because it is less expensive to
3592
+ * calculate all the routes to entrypoints using local router than all the routes with the
3593
+ * remote router.
3594
+ */
3595
+ try {
3596
+ ioMapItinerary = mapWithEnd.getBestItineraryFromEntryPointsToEnd(start, end, wemapRouterOptions);
3597
+ remoteRouterResponse = await RemoteRouterManager$1.getItinerariesWithFallback(
3598
+ remoteRouters, mode, [start, ioMapItinerary.from]
3599
+ );
3600
+ if (!remoteRouterResponse.itineraries.length) {
3601
+ throw new NoRouteFoundError(start, ioMapItinerary.from, remoteRouterResponse.error);
3602
+ }
3603
+ } catch (e) {
3604
+ if (!isRoutingError(e)) {
3605
+ throw e;
3606
+ }
3607
+ routerResponse.error = 'Tried to calculate an itinerary from "start" to "entrypoints" '
3608
+ + `using remote routers (${remoteRouters.map(r => r.name).join(', ')}) and an `
3609
+ + 'itinerary from "entrypoints" to "end" using wemap router on local map '
3610
+ + `"${mapWithEnd.name}", but failed. `
3611
+ + `Details: ${e.message}.`;
3612
+ return routerResponse;
3613
+ }
3614
+
3615
+ // Concat the remote router response (for each itinerary) with the IO map itinerary
3616
+ routerResponse.itineraries = remoteRouterResponse.itineraries.map(
3617
+ remoteRouterItinerary => Itinerary.fromItineraries(remoteRouterItinerary, ioMapItinerary)
3618
+ );
3619
+ routerResponse.routerName = [this.rname, remoteRouterResponse.routerName, WemapRouter.rname];
3620
+ return routerResponse;
3621
+ }
3622
+
3623
+ /**
3624
+ * Case 5
3625
+ *
3626
+ * If maps have been found for the "start" and the "end" but they are different, so:
3627
+ * - A first itinerary (ioMapItinerary1) is calculated from "start" to an "entrypoint" of
3628
+ * the mapWithStart using the wemap router.
3629
+ * - A second itinerary (remoteRouterResponse) is calculated from an "entrypoint" of the
3630
+ * mapWithStart to an "entrypoint" of the endWithMap using remote routers.
3631
+ * - A third itinerary (ioMapItinerary2) is calculated from an "entrypoint" of the mapWithEnd
3632
+ * to the "end" using the wemap router.
3633
+ * Itinerary returned is the concatenation of the three itineraries.
3634
+ */
3635
+ // if (startMap && endMap) {
3636
+
3637
+ if (!mapWithStart.entryPoints.length) {
3638
+ routerResponse.error = `One map including the "start" (${mapWithStart.name}) and another
3639
+ including the "end" (${mapWithEnd.name}) has been found, however, no "entrypoints" have
3640
+ been found to go out of the start map`;
3641
+ return routerResponse;
3642
+ }
3643
+
3644
+ if (!mapWithEnd.entryPoints.length) {
3645
+ routerResponse.error = `One map including the "start" (${mapWithStart.name}) and another
3646
+ including the "end" (${mapWithEnd.name}) has been found, however, no "entrypoints" have
3647
+ been found to go in the second map`;
3648
+ return routerResponse;
3649
+ }
3650
+
3651
+ let ioMapItinerary1, ioMapItinerary2;
3652
+ try {
3653
+ ioMapItinerary1 = mapWithStart.getBestItineraryFromStartToEntryPoints(start, end, wemapRouterOptions);
3654
+ ioMapItinerary2 = mapWithEnd.getBestItineraryFromEntryPointsToEnd(start, end, wemapRouterOptions);
3655
+ remoteRouterResponse = await RemoteRouterManager$1.getItinerariesWithFallback(
3656
+ remoteRouters, mode, [ioMapItinerary1.to, ioMapItinerary2.from]
3657
+ );
3658
+ if (!remoteRouterResponse.itineraries.length) {
3659
+ throw new NoRouteFoundError(ioMapItinerary1.to, ioMapItinerary2.from, remoteRouterResponse.error);
3660
+ }
3661
+ } catch (e) {
3662
+ if (!isRoutingError(e)) {
3663
+ throw e;
3664
+ }
3665
+ routerResponse.error = 'Tried to calculate an itinerary from "start" to "entrypoints1" '
3666
+ + `using wemap router on local map "${mapWithStart.name}", an itinerary from `
3667
+ + '"entrypoints1" to "entrypoints2" using remote routers '
3668
+ + `(${remoteRouters.map(r => r.name).join(', ')}) and an itinerary from "entrypoints2" `
3669
+ + `to "end" using wemap router on local map "${mapWithEnd.name}", but failed. `
3670
+ + `Details: ${e.message}.`;
3671
+ return routerResponse;
3672
+ }
3673
+
3674
+ // Concat the IO map itinerary 2 with the remote router response (for each itinerary)
3675
+ // and the IO map itinerary 2
3676
+ routerResponse.itineraries = remoteRouterResponse.itineraries.map(
3677
+ remoteRouterItinerary => Itinerary.fromItineraries(
3678
+ ioMapItinerary1,
3679
+ remoteRouterItinerary,
3680
+ ioMapItinerary2
3681
+ )
3682
+ );
3683
+ routerResponse.routerName = [this.rname, remoteRouterResponse.routerName, WemapRouter.rname];
3684
+ return routerResponse;
3685
+
3686
+ // }
3687
+ }
3688
+
3689
+ }
3690
+
3691
+ /* eslint-disable max-statements */
3692
+
3693
+ class ItineraryInfoManager {
3694
+
3695
+ /** @type {Itinerary} */
3696
+ _itinerary;
3697
+
3698
+ /** @type {Network} */
3699
+ _network;
3700
+
3701
+ /** @type {Network} */
3702
+ _mapMatching;
3703
+
3704
+ /** @type {Step[]} */
3705
+ _steps;
3706
+
3707
+ /** @type {Step[]} */
3708
+ _coordsNextStep;
3709
+
3710
+ /** @type {Step[]} */
3711
+ _coordsPreviousStep;
3712
+
3713
+ /** @type {number[]} */
3714
+ _coordsDistanceTraveled;
3715
+
3716
+ /** @type {Leg[]} */
3717
+ _coordsLeg;
3718
+
3719
+ /** @type {Itinerary} */
3720
+ set itinerary(itinerary) {
3721
+
3722
+ if (itinerary === null) {
3723
+ this._itinerary = null;
3724
+ return;
3725
+ }
3726
+
3727
+ this._itinerary = itinerary;
3728
+ this._steps = itinerary.steps;
3729
+ this._network = itinerary.toNetwork();
3730
+ this._mapMatching = new MapMatching(this._network);
3731
+
3732
+ this._coordsNextStep = new Array(itinerary.coords.length);
3733
+ this._coordsPreviousStep = new Array(itinerary.coords.length);
3734
+ this._coordsDistanceTraveled = new Array(itinerary.coords.length);
3735
+ this._coordsLeg = new Array(itinerary.coords.length);
3736
+
3737
+ let stepId = 0;
3738
+ let previousStep = null;
3739
+ let nextStep = this._steps[0];
3740
+ let distanceTraveled = 0;
3741
+
3742
+ itinerary.coords.forEach((coords, idx, arr) => {
3743
+ if (stepId < this._steps.length && this._steps[stepId].coords.equalsTo(coords)) {
3744
+ previousStep = this._steps[stepId];
3745
+ nextStep = stepId === this._steps.length - 1 ? null : this._steps[stepId + 1];
3746
+ stepId++;
3747
+ }
3748
+ if (idx !== 0) {
3749
+ distanceTraveled += arr[idx - 1].distanceTo(coords);
3750
+ }
3751
+
3752
+ this._coordsNextStep[idx] = nextStep;
3753
+ this._coordsPreviousStep[idx] = previousStep;
3754
+ this._coordsDistanceTraveled[idx] = distanceTraveled;
3755
+ this._coordsLeg[idx] = itinerary.legs.find(leg => leg.coords.includes(coords));
3756
+ });
3757
+ }
3758
+
3759
+ /**
3760
+ * @param {Coordinates} position
3761
+ * @returns {ItineraryInfo}
3762
+ */
3763
+ getInfo(position) {
3764
+
3765
+ if (!this._itinerary) {
3766
+ return null;
3767
+ }
3768
+
3769
+ if (!(position instanceof Coordinates)) {
3770
+ return null;
3771
+ }
3772
+
3773
+ const projection = this._mapMatching.getProjection(position);
3774
+ if (!projection) {
3775
+ return null;
3776
+ }
3777
+
3778
+ let itineraryInfo = null;
3779
+
3780
+ if (projection.nearestElement instanceof GraphNode) {
3781
+ const idx = this._itinerary.coords.findIndex(
3782
+ coords => projection.nearestElement.coords === coords
3783
+ );
3784
+ if (idx === -1) {
3785
+ throw new Error('ItineraryInfoManager: could not find projection in itinerary (Node)');
3786
+ }
3787
+
3788
+ itineraryInfo = new ItineraryInfo();
3789
+ itineraryInfo.nextStep = this._coordsNextStep[idx];
3790
+ itineraryInfo.previousStep = this._coordsPreviousStep[idx];
3791
+ itineraryInfo.projection = projection;
3792
+ itineraryInfo.leg = this._coordsLeg[idx];
3793
+ itineraryInfo.traveledDistance = this._coordsDistanceTraveled[idx];
3794
+ itineraryInfo.remainingDistance = this._itinerary.distance - itineraryInfo.traveledDistance;
3795
+ itineraryInfo.traveledPercentage = itineraryInfo.traveledDistance / this._itinerary.distance;
3796
+ itineraryInfo.remainingPercentage = itineraryInfo.remainingDistance / this._itinerary.distance;
3797
+
3798
+ } else if (projection.nearestElement instanceof GraphEdge) {
3799
+
3800
+ let firstNode = projection.nearestElement.node1.coords;
3801
+ let idx = this._itinerary.coords.findIndex(coords => firstNode === coords);
3802
+ if (idx === -1) {
3803
+ throw new Error('ItineraryInfoManager: could not find projection in itinerary (Edge)');
3804
+ }
3805
+
3806
+ // graphEdge is not necessarly ordered. We have to look for the first point
3807
+ if (idx === this._itinerary.coords.length - 1
3808
+ || this._itinerary.coords[idx + 1] !== projection.nearestElement.node2.coords
3809
+ ) {
3810
+ firstNode = projection.nearestElement.node2.coords;
3811
+ idx--;
3812
+ }
3813
+
3814
+ itineraryInfo = new ItineraryInfo();
3815
+ itineraryInfo.nextStep = this._coordsNextStep[idx];
3816
+ itineraryInfo.previousStep = this._coordsPreviousStep[idx];
3817
+ itineraryInfo.projection = projection;
3818
+ itineraryInfo.leg = this._coordsLeg[idx];
3819
+ itineraryInfo.traveledDistance = this._coordsDistanceTraveled[idx]
3820
+ + projection.projection.distanceTo(firstNode);
3821
+ itineraryInfo.remainingDistance = this._itinerary.distance - itineraryInfo.traveledDistance;
3822
+ itineraryInfo.traveledPercentage = itineraryInfo.traveledDistance / this._itinerary.distance;
3823
+ itineraryInfo.remainingPercentage = itineraryInfo.remainingDistance / this._itinerary.distance;
3824
+
3825
+ }
3826
+
3827
+ return itineraryInfo;
3828
+ }
3829
+
3830
+ }
3831
+
3832
+ export { CitywayRemoteRouter$1 as CitywayRemoteRouter, Constants, DeutscheBahnRemoteRouter$1 as DeutscheBahnRemoteRouter, IdfmRemoteRouter$1 as IdfmRemoteRouter, Itinerary, ItineraryInfo, ItineraryInfoManager, Leg, LevelChange, OsrmRemoteRouter$1 as OsrmRemoteRouter, OtpRemoteRouter$1 as OtpRemoteRouter, RemoteRouterManager$1 as RemoteRouterManager, RouterResponse, Step, WemapMetaRemoteRouter$1 as WemapMetaRemoteRouter, WemapMetaRemoteRouterOptions, WemapMetaRemoteRouterPayload, WemapMetaRouter, IOMap as WemapMetaRouterIOMap, WemapMetaRouterOptions, WemapNetworkUtils, WemapRouter, WemapRouterOptions, WemapRouterUtils, getDurationFromLength, multiplyRouterResponseLevel };
3833
+ //# sourceMappingURL=wemap-routers.es.js.map