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