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