@wemap/routers 7.2.0 → 7.2.2

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