@wemap/routers 6.2.2 → 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 +1811 -695
- package/dist/wemap-routers.es.js.map +1 -1
- package/index.js +13 -5
- package/package.json +9 -6
- package/src/Constants.js +4 -2
- 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 -252
- package/src/deutsche-bahn/DeutscheBahnRouterUtils.js +0 -91
- package/src/idfm/IdfmUtils.js +0 -247
- package/src/osrm/OsrmUtils.js +0 -269
- package/src/otp/OtpUtils.js +0 -150
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
|
|
3
|
+
import { Level, Coordinates } from '@wemap/geo';
|
|
4
|
+
import { rad2deg, positiveMod } from '@wemap/maths';
|
|
5
|
+
|
|
6
|
+
import Itinerary from '../../model/Itinerary.js';
|
|
7
|
+
import { generateStepsMetadata } from '../RemoteRouterUtils.js';
|
|
8
|
+
import RouterResponse from '../../model/RouterResponse.js';
|
|
9
|
+
import Leg from '../../model/Leg.js';
|
|
10
|
+
import Step from '../../model/Step.js';
|
|
11
|
+
import LevelChange from '../../model/LevelChange.js';
|
|
12
|
+
import RemoteRouter from '../RemoteRouter.js';
|
|
13
|
+
import Constants from '../../Constants.js';
|
|
14
|
+
import RoutingModeCorrespondanceNotFound from '../RoutingModeCorrespondanceNotFound.js';
|
|
15
|
+
import RemoteRouterServerUnreachable from '../RemoteRouterServerUnreachable.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Input mode correspondance
|
|
19
|
+
*/
|
|
20
|
+
const inputModeCorrespondance = new Map();
|
|
21
|
+
inputModeCorrespondance.set(Constants.ROUTING_MODE.CAR, 'driving');
|
|
22
|
+
inputModeCorrespondance.set(Constants.ROUTING_MODE.WALK, 'walking');
|
|
23
|
+
inputModeCorrespondance.set(Constants.ROUTING_MODE.BIKE, 'bike');
|
|
24
|
+
inputModeCorrespondance.set(Constants.ROUTING_MODE.BUS, 'bus');
|
|
25
|
+
inputModeCorrespondance.set(Constants.ROUTING_MODE.MULTI, 'walking');
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Singleton.
|
|
30
|
+
*/
|
|
31
|
+
class OsrmRemoteRouter extends RemoteRouter {
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @override
|
|
35
|
+
*/
|
|
36
|
+
get rname() {
|
|
37
|
+
return 'osrm';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @override
|
|
42
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
43
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
44
|
+
*/
|
|
45
|
+
async getItineraries(endpointUrl, mode, waypoints) {
|
|
46
|
+
const url = this.getURL(endpointUrl, mode, waypoints);
|
|
47
|
+
const res = await fetch(url);
|
|
48
|
+
if (res.status !== 200) {
|
|
49
|
+
throw new RemoteRouterServerUnreachable(this.rname, url);
|
|
50
|
+
}
|
|
51
|
+
const response = await res.json();
|
|
52
|
+
return this.createRouterResponseFromJson(response, waypoints[0], waypoints[1]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} endpointUrl
|
|
57
|
+
* @param {string} mode
|
|
58
|
+
* @param {Array<Coordinates>} waypoints
|
|
59
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
60
|
+
*/
|
|
61
|
+
getURL(endpointUrl, mode, waypoints) {
|
|
62
|
+
|
|
63
|
+
const osrmMode = inputModeCorrespondance.get(mode);
|
|
64
|
+
if (!osrmMode) {
|
|
65
|
+
throw new RoutingModeCorrespondanceNotFound(this.rname, mode);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let url = endpointUrl + '/route/v1/' + osrmMode + '/';
|
|
69
|
+
url += waypoints.map(waypoint => [waypoint.longitude + ',' + waypoint.latitude]).join(';');
|
|
70
|
+
url += '?geometries=geojson&overview=full&steps=true';
|
|
71
|
+
|
|
72
|
+
return url;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {Coordinates} coordinates
|
|
77
|
+
* @returns {object}
|
|
78
|
+
*/
|
|
79
|
+
coordinatesToJson(coordinates) {
|
|
80
|
+
const output = [coordinates.lng, coordinates.lat];
|
|
81
|
+
if (coordinates.level) {
|
|
82
|
+
output.push(coordinates.level.toString());
|
|
83
|
+
}
|
|
84
|
+
return output;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {object} json
|
|
89
|
+
* @returns {Coordinates}
|
|
90
|
+
*/
|
|
91
|
+
jsonToCoordinates(json) {
|
|
92
|
+
const output = new Coordinates(json[1], json[0]);
|
|
93
|
+
if (json.length > 2) {
|
|
94
|
+
output.level = Level.fromString(json[2]);
|
|
95
|
+
}
|
|
96
|
+
return output;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
nodesToJsonCoords(nodes) {
|
|
100
|
+
return nodes.map(node => this.coordinatesToJson(node.coords));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
getModifierFromAngle(_angle) {
|
|
105
|
+
|
|
106
|
+
const angle = positiveMod(rad2deg(_angle), 360);
|
|
107
|
+
|
|
108
|
+
if (angle > 0 && angle < 60) {
|
|
109
|
+
return 'sharp right';
|
|
110
|
+
}
|
|
111
|
+
if (angle >= 60 && angle < 140) {
|
|
112
|
+
return 'right';
|
|
113
|
+
}
|
|
114
|
+
if (angle >= 140 && angle < 160) {
|
|
115
|
+
return 'slight right';
|
|
116
|
+
}
|
|
117
|
+
if (angle >= 160 && angle <= 200) {
|
|
118
|
+
return 'straight';
|
|
119
|
+
}
|
|
120
|
+
if (angle > 200 && angle <= 220) {
|
|
121
|
+
return 'slight left';
|
|
122
|
+
}
|
|
123
|
+
if (angle > 220 && angle <= 300) {
|
|
124
|
+
return 'left';
|
|
125
|
+
}
|
|
126
|
+
if (angle > 300 && angle < 360) {
|
|
127
|
+
return 'sharp left';
|
|
128
|
+
}
|
|
129
|
+
return 'u turn';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
noRouteFoundJson(message) {
|
|
134
|
+
return {
|
|
135
|
+
'code': 'NoRoute',
|
|
136
|
+
message
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {Itinerary} itinerary
|
|
142
|
+
* @returns {object}
|
|
143
|
+
*/
|
|
144
|
+
itineraryToOsrmJson(itinerary) {
|
|
145
|
+
|
|
146
|
+
const lastLegId = itinerary.legs.length - 1;
|
|
147
|
+
|
|
148
|
+
const jsonLegs = itinerary.legs.map(({ distance, duration, coords, steps }, idLeg) => {
|
|
149
|
+
|
|
150
|
+
const lastStepId = steps.length - 1;
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
distance,
|
|
154
|
+
duration,
|
|
155
|
+
steps: steps.map((step, idStep, arr) => {
|
|
156
|
+
|
|
157
|
+
let type = idStep === 0 && idLeg === 0 ? 'depart' : 'turn';
|
|
158
|
+
type = idStep === lastStepId && idLeg === lastLegId ? 'arrive' : type;
|
|
159
|
+
|
|
160
|
+
const stepCoordsIdx = coords.findIndex(p => p.equalsTo(step.coords));
|
|
161
|
+
const nextStepCoordsIdx = idStep === lastStepId
|
|
162
|
+
? stepCoordsIdx
|
|
163
|
+
: coords.findIndex(p => p.equalsTo(arr[idStep + 1].coords));
|
|
164
|
+
|
|
165
|
+
const jsonStep = {
|
|
166
|
+
geometry: {
|
|
167
|
+
type: 'LineString',
|
|
168
|
+
coordinates: coords.slice(stepCoordsIdx, nextStepCoordsIdx + 1).map(this.coordinatesToJson)
|
|
169
|
+
},
|
|
170
|
+
distance: step.distance,
|
|
171
|
+
duration: step.duration,
|
|
172
|
+
name: step.name,
|
|
173
|
+
maneuver: {
|
|
174
|
+
bearing_before: rad2deg(step.previousBearing),
|
|
175
|
+
bearing_after: rad2deg(step.nextBearing),
|
|
176
|
+
location: this.coordinatesToJson(step.coords),
|
|
177
|
+
modifier: this.getModifierFromAngle(step.angle),
|
|
178
|
+
type
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
if (step.levelChange !== null) {
|
|
182
|
+
jsonStep.levelChange = step.levelChange.toJson();
|
|
183
|
+
}
|
|
184
|
+
if (typeof step.extras === 'object' && Object.keys(step.extras).length !== 0) {
|
|
185
|
+
jsonStep.extras = step.extras;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return jsonStep;
|
|
189
|
+
})
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
'code': 'Ok',
|
|
195
|
+
'routes': [
|
|
196
|
+
{
|
|
197
|
+
'geometry': {
|
|
198
|
+
'type': 'LineString',
|
|
199
|
+
'coordinates': itinerary.coords.map(this.coordinatesToJson)
|
|
200
|
+
},
|
|
201
|
+
'legs': jsonLegs,
|
|
202
|
+
'distance': itinerary.distance,
|
|
203
|
+
'duration': itinerary.duration,
|
|
204
|
+
'weight_name': 'routability',
|
|
205
|
+
'weight': 0
|
|
206
|
+
}
|
|
207
|
+
],
|
|
208
|
+
'waypoints': []
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @param {object} jsonSteps
|
|
214
|
+
* @param {Coordinates[]} legCoords
|
|
215
|
+
* @returns {Step[]}
|
|
216
|
+
*/
|
|
217
|
+
parseJsonSteps(jsonSteps, legCoords) {
|
|
218
|
+
|
|
219
|
+
if (!jsonSteps) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return jsonSteps.map(jsonStep => {
|
|
224
|
+
|
|
225
|
+
const step = new Step();
|
|
226
|
+
step.coords = this.jsonToCoordinates(jsonStep.maneuver.location);
|
|
227
|
+
|
|
228
|
+
// Sometimes, OSRM step does not have the same coordinates than a point in legCoords.
|
|
229
|
+
// 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
|
|
230
|
+
// That is why we look for the closest point.
|
|
231
|
+
const distances = legCoords.map(coords => coords.distanceTo(step.coords));
|
|
232
|
+
const idStepCoordsInLeg = distances.indexOf(Math.min(...distances));
|
|
233
|
+
if (idStepCoordsInLeg < 0) {
|
|
234
|
+
throw new Error('Osrm Parser: Cannot find step coords in leg coordinates');
|
|
235
|
+
}
|
|
236
|
+
step._idCoordsInLeg = idStepCoordsInLeg;
|
|
237
|
+
|
|
238
|
+
step.name = jsonStep.name;
|
|
239
|
+
step.levelChange = jsonStep.levelChange ? LevelChange.fromJson(jsonStep.levelChange) : null;
|
|
240
|
+
|
|
241
|
+
step.distance = jsonStep.distance;
|
|
242
|
+
step.duration = jsonStep.duration;
|
|
243
|
+
|
|
244
|
+
if (jsonStep.extras && jsonStep.extras.subwayEntrance) {
|
|
245
|
+
step.extras.subwayEntrance = true;
|
|
246
|
+
if (jsonStep.extras.subwayEntranceRef) {
|
|
247
|
+
step.extras.subwayEntranceRef = jsonStep.extras.subwayEntranceRef;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return step;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Generate multi itineraries from OSRM JSON
|
|
257
|
+
* @param {object} json JSON file provided by OSRM.
|
|
258
|
+
* @param {Coordinates} from itinerary start
|
|
259
|
+
* @param {Coordinates} to itinerary end
|
|
260
|
+
* @param {?string} routingMode [walking|driving|bicycle]
|
|
261
|
+
* @returns {?RouterResponse}
|
|
262
|
+
*/
|
|
263
|
+
createRouterResponseFromJson(json, from, to, routingMode = 'walking') {
|
|
264
|
+
const { routes: jsonRoutes } = json;
|
|
265
|
+
|
|
266
|
+
if (!jsonRoutes) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const routingModeCorrespondance = new Map();
|
|
271
|
+
routingModeCorrespondance.set('walking', 'WALK');
|
|
272
|
+
routingModeCorrespondance.set('driving', 'CAR');
|
|
273
|
+
routingModeCorrespondance.set('bicycle', 'BIKE');
|
|
274
|
+
const mode = routingModeCorrespondance.get(routingMode) || null;
|
|
275
|
+
|
|
276
|
+
const routerResponse = new RouterResponse();
|
|
277
|
+
routerResponse.routerName = this.rname;
|
|
278
|
+
|
|
279
|
+
routerResponse.from = from;
|
|
280
|
+
routerResponse.to = to;
|
|
281
|
+
|
|
282
|
+
for (const jsonItinerary of jsonRoutes) {
|
|
283
|
+
|
|
284
|
+
const itinerary = new Itinerary();
|
|
285
|
+
|
|
286
|
+
// itinerary.coords = jsonItinerary.geometry.coordinates.map(jsonToCoordinates);
|
|
287
|
+
itinerary.distance = jsonItinerary.distance;
|
|
288
|
+
itinerary.duration = jsonItinerary.duration;
|
|
289
|
+
itinerary.from = from;
|
|
290
|
+
itinerary.to = to;
|
|
291
|
+
|
|
292
|
+
routerResponse.itineraries.push(itinerary);
|
|
293
|
+
|
|
294
|
+
for (const jsonLeg of jsonItinerary.legs) {
|
|
295
|
+
|
|
296
|
+
const leg = new Leg();
|
|
297
|
+
|
|
298
|
+
leg.mode = mode;
|
|
299
|
+
leg.distance = jsonLeg.distance;
|
|
300
|
+
leg.duration = jsonLeg.duration;
|
|
301
|
+
|
|
302
|
+
leg.coords = jsonLeg.steps
|
|
303
|
+
.map(step => step.geometry.coordinates.map(this.jsonToCoordinates))
|
|
304
|
+
.flat()
|
|
305
|
+
// Remove duplicates
|
|
306
|
+
.filter((coords, idx, arr) => idx === 0 || !arr[idx - 1].equalsTo(coords));
|
|
307
|
+
|
|
308
|
+
leg.from = {
|
|
309
|
+
name: null,
|
|
310
|
+
coords: leg.coords[0]
|
|
311
|
+
};
|
|
312
|
+
leg.to = {
|
|
313
|
+
name: null,
|
|
314
|
+
coords: leg.coords[leg.coords.length - 1]
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
leg.steps = this.parseJsonSteps(jsonLeg.steps, leg.coords);
|
|
318
|
+
|
|
319
|
+
itinerary.legs.push(leg);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// All legs have to be parsed before computing steps metadata
|
|
323
|
+
generateStepsMetadata(itinerary);
|
|
324
|
+
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return routerResponse;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export default new OsrmRemoteRouter();
|
|
@@ -10,21 +10,15 @@ import { Coordinates } from '@wemap/geo';
|
|
|
10
10
|
|
|
11
11
|
// import OsmParser from '../../model/OsmParser.js';
|
|
12
12
|
// import OsmNetworkUtils from '../../network/OsmNetworkUtils.js';
|
|
13
|
-
import
|
|
14
|
-
// itineraryToOsrmJson,
|
|
15
|
-
// jsonToCoordinates,
|
|
16
|
-
// createItineraryFromJson,
|
|
17
|
-
// getModifierFromAngle,
|
|
18
|
-
// noRouteFoundJson,
|
|
19
|
-
createRouterResponseFromJson
|
|
20
|
-
} from './OsrmUtils.js';
|
|
13
|
+
import OsrmRemoteRouter from './OsrmRemoteRouter.js';
|
|
21
14
|
|
|
22
|
-
import
|
|
15
|
+
import checkRouterResponseType from '../../model/RouterResponse.type.spec.js';
|
|
23
16
|
|
|
24
17
|
const { expect } = chai;
|
|
25
18
|
chai.use(chaiAlmost(0.1));
|
|
26
19
|
|
|
27
20
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const assetsPath = path.resolve(__dirname, '../../../assets');
|
|
28
22
|
|
|
29
23
|
|
|
30
24
|
// const load = fileName => {
|
|
@@ -392,14 +386,14 @@ describe('OsrmUtils - createRouterResponseFromJson', () => {
|
|
|
392
386
|
|
|
393
387
|
it('RouterResponse - 1', () => {
|
|
394
388
|
|
|
395
|
-
const filePath = path.resolve(
|
|
389
|
+
const filePath = path.resolve(assetsPath, 'itinerary-montpellier-outdoor.json');
|
|
396
390
|
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
397
391
|
const json = JSON.parse(fileString);
|
|
398
392
|
|
|
399
393
|
const from = new Coordinates(43.6007871, 3.8757218000000004);
|
|
400
394
|
const to = new Coordinates(43.598877, 3.873866);
|
|
401
|
-
const routerResponse = createRouterResponseFromJson(json, from, to);
|
|
402
|
-
|
|
395
|
+
const routerResponse = OsrmRemoteRouter.createRouterResponseFromJson(json, from, to);
|
|
396
|
+
checkRouterResponseType(routerResponse);
|
|
403
397
|
|
|
404
398
|
expect(routerResponse.routerName).equal('osrm');
|
|
405
399
|
expect(routerResponse.itineraries.length).equal(1);
|
|
@@ -434,14 +428,14 @@ describe('OsrmUtils - createRouterResponseFromJson', () => {
|
|
|
434
428
|
|
|
435
429
|
it('RouterResponse - 2', () => {
|
|
436
430
|
|
|
437
|
-
const filePath = path.resolve(
|
|
431
|
+
const filePath = path.resolve(assetsPath, 'itinerary-montpellier-osrm-3.json');
|
|
438
432
|
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
439
433
|
const json = JSON.parse(fileString);
|
|
440
434
|
|
|
441
435
|
const from = new Coordinates(43.605663, 3.887244);
|
|
442
436
|
const to = new Coordinates(43.6054106, 3.8878106);
|
|
443
|
-
const routerResponse = createRouterResponseFromJson(json, from, to);
|
|
444
|
-
|
|
437
|
+
const routerResponse = OsrmRemoteRouter.createRouterResponseFromJson(json, from, to);
|
|
438
|
+
checkRouterResponseType(routerResponse);
|
|
445
439
|
|
|
446
440
|
expect(routerResponse.routerName).equal('osrm');
|
|
447
441
|
expect(routerResponse.itineraries.length).equal(1);
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
import Polyline from '@mapbox/polyline';
|
|
3
|
+
|
|
4
|
+
import { Coordinates } from '@wemap/geo';
|
|
5
|
+
import Logger from '@wemap/logger';
|
|
6
|
+
|
|
7
|
+
import Itinerary from '../../model/Itinerary.js';
|
|
8
|
+
import Leg from '../../model/Leg.js';
|
|
9
|
+
import RouterResponse from '../../model/RouterResponse.js';
|
|
10
|
+
import Step from '../../model/Step.js';
|
|
11
|
+
import { generateStepsMetadata } from '../RemoteRouterUtils.js';
|
|
12
|
+
import Constants from '../../Constants.js';
|
|
13
|
+
import RemoteRouter from '../RemoteRouter.js';
|
|
14
|
+
import RoutingModeCorrespondanceNotFound from '../RoutingModeCorrespondanceNotFound.js';
|
|
15
|
+
import RemoteRouterServerUnreachable from '../RemoteRouterServerUnreachable.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Input mode correspondance
|
|
19
|
+
*/
|
|
20
|
+
const inputModeCorrespondance = new Map();
|
|
21
|
+
inputModeCorrespondance.set(Constants.ROUTING_MODE.CAR, 'CAR');
|
|
22
|
+
inputModeCorrespondance.set(Constants.ROUTING_MODE.WALK, 'WALK');
|
|
23
|
+
inputModeCorrespondance.set(Constants.ROUTING_MODE.BIKE, 'BICYCLE');
|
|
24
|
+
inputModeCorrespondance.set(Constants.ROUTING_MODE.BUS, 'WALK,TRANSIT');
|
|
25
|
+
inputModeCorrespondance.set(Constants.ROUTING_MODE.MULTI, 'WALK,TRANSIT');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Singleton.
|
|
29
|
+
*/
|
|
30
|
+
class OtpRemoteRouter extends RemoteRouter {
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @override
|
|
34
|
+
*/
|
|
35
|
+
get rname() {
|
|
36
|
+
return 'otp';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @override
|
|
41
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
42
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
43
|
+
*/
|
|
44
|
+
async getItineraries(endpointUrl, mode, waypoints) {
|
|
45
|
+
const url = this.getURL(endpointUrl, mode, waypoints);
|
|
46
|
+
const res = await fetch(url);
|
|
47
|
+
if (res.status !== 200) {
|
|
48
|
+
throw new RemoteRouterServerUnreachable(this.rname, url);
|
|
49
|
+
}
|
|
50
|
+
const response = await res.json();
|
|
51
|
+
return this.createRouterResponseFromJson(response);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} endpointUrl
|
|
56
|
+
* @param {string} mode
|
|
57
|
+
* @param {Array<Coordinates>} waypoints
|
|
58
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
59
|
+
*/
|
|
60
|
+
getURL(endpointUrl, mode, waypoints) {
|
|
61
|
+
|
|
62
|
+
const otpMode = inputModeCorrespondance.get(mode);
|
|
63
|
+
if (!otpMode) {
|
|
64
|
+
throw new RoutingModeCorrespondanceNotFound(this.rname, mode);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (waypoints.length > 2) {
|
|
68
|
+
Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const fromPlace = `fromPlace=${waypoints[0].latitude},${waypoints[0].longitude}`;
|
|
72
|
+
const toPlace = `toPlace=${waypoints[1].latitude},${waypoints[1].longitude}`;
|
|
73
|
+
const queryMode = `mode=${otpMode}`;
|
|
74
|
+
|
|
75
|
+
const url = new URL(endpointUrl);
|
|
76
|
+
let { search } = url;
|
|
77
|
+
search = (search ? `${search}&` : '?') + `${fromPlace}&${toPlace}&${queryMode}`;
|
|
78
|
+
|
|
79
|
+
return `${url.origin}${url.pathname}${search}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {object} json
|
|
84
|
+
* @returns {Coordinates}
|
|
85
|
+
*/
|
|
86
|
+
jsonToCoordinates(json) {
|
|
87
|
+
return new Coordinates(json.lat, json.lon);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {object} jsonSteps
|
|
92
|
+
* @param {Coordinates[]} legCoords
|
|
93
|
+
* @returns {Step[]}
|
|
94
|
+
*/
|
|
95
|
+
parseJsonSteps(jsonSteps, legCoords) {
|
|
96
|
+
|
|
97
|
+
if (!jsonSteps) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return jsonSteps.map(jsonStep => {
|
|
102
|
+
|
|
103
|
+
const step = new Step();
|
|
104
|
+
const stepCoords = this.jsonToCoordinates(jsonStep);
|
|
105
|
+
|
|
106
|
+
// OTP step does not have the same coordinates than a point in legCoords.
|
|
107
|
+
// That is why we look for the closest point.
|
|
108
|
+
const distances = legCoords.map(coords => coords.distanceTo(stepCoords));
|
|
109
|
+
const idStepCoordsInLeg = distances.indexOf(Math.min(...distances));
|
|
110
|
+
if (idStepCoordsInLeg < 0) {
|
|
111
|
+
throw new Error('OTP Parser: Cannot find closest step');
|
|
112
|
+
}
|
|
113
|
+
step.coords = legCoords[idStepCoordsInLeg];
|
|
114
|
+
step._idCoordsInLeg = idStepCoordsInLeg;
|
|
115
|
+
|
|
116
|
+
step.name = jsonStep.streetName;
|
|
117
|
+
step.levelChange = null;
|
|
118
|
+
|
|
119
|
+
step.distance = jsonStep.distance;
|
|
120
|
+
|
|
121
|
+
return step;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Generate multi itineraries from OTP JSON
|
|
127
|
+
* @param {object} json JSON file provided by OTP.
|
|
128
|
+
* @returns {?RouterResponse}
|
|
129
|
+
*/
|
|
130
|
+
createRouterResponseFromJson(json) {
|
|
131
|
+
|
|
132
|
+
const { plan: jsonPlan } = json;
|
|
133
|
+
|
|
134
|
+
if (!jsonPlan) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const routerResponse = new RouterResponse();
|
|
139
|
+
routerResponse.routerName = this.rname;
|
|
140
|
+
|
|
141
|
+
routerResponse.from = this.jsonToCoordinates(jsonPlan.from);
|
|
142
|
+
routerResponse.to = this.jsonToCoordinates(jsonPlan.to);
|
|
143
|
+
|
|
144
|
+
for (const jsonItinerary of jsonPlan.itineraries) {
|
|
145
|
+
|
|
146
|
+
const itinerary = new Itinerary();
|
|
147
|
+
|
|
148
|
+
itinerary.duration = jsonItinerary.duration;
|
|
149
|
+
itinerary.startTime = jsonItinerary.startTime;
|
|
150
|
+
itinerary.endTime = jsonItinerary.endTime;
|
|
151
|
+
itinerary.from = routerResponse.from;
|
|
152
|
+
itinerary.to = routerResponse.to;
|
|
153
|
+
|
|
154
|
+
routerResponse.itineraries.push(itinerary);
|
|
155
|
+
|
|
156
|
+
for (const jsonLeg of jsonItinerary.legs) {
|
|
157
|
+
|
|
158
|
+
const leg = new Leg();
|
|
159
|
+
|
|
160
|
+
leg.mode = jsonLeg.mode;
|
|
161
|
+
leg.duration = jsonLeg.duration;
|
|
162
|
+
leg.startTime = jsonLeg.startTime;
|
|
163
|
+
leg.endTime = jsonLeg.endTime;
|
|
164
|
+
leg.from = {
|
|
165
|
+
name: jsonLeg.from.name,
|
|
166
|
+
coords: this.jsonToCoordinates(jsonLeg.from)
|
|
167
|
+
};
|
|
168
|
+
leg.to = {
|
|
169
|
+
name: jsonLeg.to.name,
|
|
170
|
+
coords: this.jsonToCoordinates(jsonLeg.to)
|
|
171
|
+
};
|
|
172
|
+
leg.coords = Polyline.decode(jsonLeg.legGeometry.points).map(([lat, lon]) => new Coordinates(lat, lon));
|
|
173
|
+
|
|
174
|
+
leg.steps = this.parseJsonSteps(jsonLeg.steps, leg.coords);
|
|
175
|
+
|
|
176
|
+
if (leg.mode === 'BUS' || leg.mode === 'TRAM') {
|
|
177
|
+
leg.transportInfo = {
|
|
178
|
+
name: jsonLeg.route,
|
|
179
|
+
routeColor: jsonLeg.routeColor,
|
|
180
|
+
routeTextColor: jsonLeg.routeTextColor,
|
|
181
|
+
directionName: jsonLeg.headsign
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const legStep = new Step();
|
|
185
|
+
legStep.coords = leg.coords[0];
|
|
186
|
+
legStep._idCoordsInLeg = 0;
|
|
187
|
+
legStep.name = jsonLeg.headsign;
|
|
188
|
+
legStep.levelChange = null;
|
|
189
|
+
legStep.distance = jsonLeg.distance;
|
|
190
|
+
leg.steps = [legStep];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// jsonLeg.distance is not reliable when compared to the array of leg coords.
|
|
194
|
+
// leg.distance = jsonLeg.distance;
|
|
195
|
+
leg.distance = leg.coords.reduce((acc, coords, idx, arr) => {
|
|
196
|
+
if (idx === 0) {
|
|
197
|
+
return acc;
|
|
198
|
+
}
|
|
199
|
+
return acc + arr[idx - 1].distanceTo(coords);
|
|
200
|
+
}, 0);
|
|
201
|
+
|
|
202
|
+
itinerary.legs.push(leg);
|
|
203
|
+
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
itinerary.distance = itinerary.coords.reduce((acc, coords, idx, arr) => {
|
|
207
|
+
if (idx === 0) {
|
|
208
|
+
return acc;
|
|
209
|
+
}
|
|
210
|
+
return acc + arr[idx - 1].distanceTo(coords);
|
|
211
|
+
}, 0);
|
|
212
|
+
|
|
213
|
+
// All legs have to be parsed before computing steps metadata
|
|
214
|
+
generateStepsMetadata(itinerary);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return routerResponse;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export default new OtpRemoteRouter();
|
|
222
|
+
|
|
@@ -7,26 +7,27 @@ import { fileURLToPath } from 'url';
|
|
|
7
7
|
|
|
8
8
|
import { Coordinates } from '@wemap/geo';
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import OtpRemoteRouter from './OtpRemoteRouter.js';
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import checkRouterResponseType from '../../model/RouterResponse.type.spec.js';
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
const { expect } = chai;
|
|
16
16
|
chai.use(chaiAlmost(0.1));
|
|
17
17
|
|
|
18
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const assetsPath = path.resolve(__dirname, '../../../assets');
|
|
19
20
|
|
|
20
|
-
describe('
|
|
21
|
+
describe('OtpRouter - createRouterResponseFromJson', () => {
|
|
21
22
|
|
|
22
23
|
it('RouterResponse - 1', () => {
|
|
23
24
|
|
|
24
|
-
const filePath = path.resolve(
|
|
25
|
+
const filePath = path.resolve(assetsPath, 'itinerary-grenoble-otp-1.json');
|
|
25
26
|
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
26
27
|
const json = JSON.parse(fileString);
|
|
27
28
|
|
|
28
|
-
const routerResponse = createRouterResponseFromJson(json);
|
|
29
|
-
|
|
29
|
+
const routerResponse = OtpRemoteRouter.createRouterResponseFromJson(json);
|
|
30
|
+
checkRouterResponseType(routerResponse);
|
|
30
31
|
|
|
31
32
|
expect(routerResponse.routerName).equal('otp');
|
|
32
33
|
expect(routerResponse.itineraries.length).equal(2);
|
|
@@ -65,12 +66,12 @@ describe('OtpUtils - createRouterResponseFromJson', () => {
|
|
|
65
66
|
|
|
66
67
|
it('RouterResponse - 2', () => {
|
|
67
68
|
|
|
68
|
-
const filePath = path.resolve(
|
|
69
|
+
const filePath = path.resolve(assetsPath, 'itinerary-grenoble-otp-2.json');
|
|
69
70
|
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
70
71
|
const json = JSON.parse(fileString);
|
|
71
72
|
|
|
72
|
-
const routerResponse = createRouterResponseFromJson(json);
|
|
73
|
-
|
|
73
|
+
const routerResponse = OtpRemoteRouter.createRouterResponseFromJson(json);
|
|
74
|
+
checkRouterResponseType(routerResponse);
|
|
74
75
|
|
|
75
76
|
expect(routerResponse.itineraries.length).equal(2);
|
|
76
77
|
expect(routerResponse.from.equalsTo(new Coordinates(45.192514856662456, 5.731452541397871))).true;
|