@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.
Files changed (57) hide show
  1. package/assets/biocbon-bergere-rdc-network.osm +163 -0
  2. package/assets/gare-de-lest-network-pp-bounds.osm +1615 -0
  3. package/dist/wemap-routers.es.js +1811 -695
  4. package/dist/wemap-routers.es.js.map +1 -1
  5. package/index.js +13 -5
  6. package/package.json +9 -6
  7. package/src/Constants.js +4 -2
  8. package/src/ItineraryInfoManager.spec.js +2 -2
  9. package/src/Utils.js +0 -77
  10. package/src/model/Itinerary.js +41 -5
  11. package/src/model/Itinerary.spec.js +91 -0
  12. package/src/model/Itinerary.type.spec.js +3 -78
  13. package/src/model/Leg.js +89 -19
  14. package/src/model/Leg.spec.js +110 -0
  15. package/src/model/Leg.type.spec.js +48 -0
  16. package/src/model/LevelChange.js +14 -24
  17. package/src/model/LevelChange.spec.js +78 -0
  18. package/src/model/LevelChange.type.spec.js +26 -0
  19. package/src/model/RouterResponse.js +70 -1
  20. package/src/model/RouterResponse.spec.js +85 -0
  21. package/src/model/RouterResponse.type.spec.js +7 -4
  22. package/src/model/Step.js +45 -6
  23. package/src/model/Step.spec.js +100 -0
  24. package/src/model/Step.type.spec.js +52 -0
  25. package/src/remote/RemoteRouter.js +31 -0
  26. package/src/remote/RemoteRouterManager.js +84 -0
  27. package/src/remote/RemoteRouterOptions.js +25 -0
  28. package/src/remote/RemoteRouterServerUnreachable.js +10 -0
  29. package/src/remote/RemoteRouterUtils.js +78 -0
  30. package/src/remote/RoutingModeCorrespondanceNotFound.js +18 -0
  31. package/src/remote/cityway/CitywayRemoteRouter.js +386 -0
  32. package/src/{cityway/CitywayUtils.spec.js → remote/cityway/CitywayRemoteRouter.spec.js} +19 -18
  33. package/src/remote/deutsche-bahn/DeutscheBahnRemoteRouter.js +143 -0
  34. package/src/{deutsche-bahn/DeutscheBahnRouterUtils.spec.js → remote/deutsche-bahn/DeutscheBahnRemoteRouter.spec.js} +7 -6
  35. package/src/remote/idfm/IdfmRemoteRouter.js +432 -0
  36. package/src/{idfm/IdfmUtils.spec.js → remote/idfm/IdfmRemoteRouter.spec.js} +7 -6
  37. package/src/remote/idfm/IdfmRemoteRouterTokenError.js +6 -0
  38. package/src/remote/osrm/OsrmRemoteRouter.js +331 -0
  39. package/src/{osrm/OsrmUtils.spec.js → remote/osrm/OsrmRemoteRouter.spec.js} +9 -15
  40. package/src/remote/otp/OtpRemoteRouter.js +222 -0
  41. package/src/{otp/OtpUtils.spec.js → remote/otp/OtpRemoteRouter.spec.js} +10 -9
  42. package/src/remote/wemap-meta/WemapMetaRemoteRouter.js +57 -0
  43. package/src/remote/wemap-meta/WemapMetaRemoteRouter.spec.js +22 -0
  44. package/src/remote/wemap-meta/WemapMetaRemoteRouterOptions.js +36 -0
  45. package/src/remote/wemap-meta/WemapMetaRemoteRouterPayload.js +44 -0
  46. package/src/wemap/WemapRouter.js +6 -0
  47. package/src/wemap/WemapRouterUtils.js +10 -4
  48. package/src/wemap/WemapStepsGeneration.js +36 -9
  49. package/src/wemap-meta/IOMap.js +191 -0
  50. package/src/wemap-meta/WemapMetaRouter.js +314 -0
  51. package/src/wemap-meta/WemapMetaRouter.spec.js +119 -0
  52. package/src/wemap-meta/WemapMetaRouterOptions.js +20 -0
  53. package/src/cityway/CitywayUtils.js +0 -252
  54. package/src/deutsche-bahn/DeutscheBahnRouterUtils.js +0 -91
  55. package/src/idfm/IdfmUtils.js +0 -247
  56. package/src/osrm/OsrmUtils.js +0 -269
  57. 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 { verifyRouterResponseData } from '../model/RouterResponse.type.spec.js';
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(__dirname, '../../assets/itinerary-montpellier-outdoor.json');
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
- verifyRouterResponseData(routerResponse);
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(__dirname, '../../assets/itinerary-montpellier-osrm-3.json');
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
- verifyRouterResponseData(routerResponse);
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 { createRouterResponseFromJson } from './OtpUtils.js';
10
+ import OtpRemoteRouter from './OtpRemoteRouter.js';
11
11
 
12
- import { verifyRouterResponseData } from '../model/RouterResponse.type.spec.js';
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('OtpUtils - createRouterResponseFromJson', () => {
21
+ describe('OtpRouter - createRouterResponseFromJson', () => {
21
22
 
22
23
  it('RouterResponse - 1', () => {
23
24
 
24
- const filePath = path.resolve(__dirname, '../../assets/itinerary-grenoble-otp-1.json');
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
- verifyRouterResponseData(routerResponse);
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(__dirname, '../../assets/itinerary-grenoble-otp-2.json');
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
- verifyRouterResponseData(routerResponse);
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;