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