@wemap/routers 6.0.2 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,247 @@
1
+ /* eslint-disable max-statements */
2
+ import { Coordinates, Utils as GeoUtils } from '@wemap/geo';
3
+
4
+ import Itinerary from '../model/Itinerary.js';
5
+ import Leg from '../model/Leg.js';
6
+ import RouterResponse from '../model/RouterResponse.js';
7
+ import Step from '../model/Step.js';
8
+ import { generateStepsMetadata, dateWithTimeZone } from '../Utils.js';
9
+ import Constants from '../Constants.js';
10
+
11
+ /**
12
+ * List of all modes supported by the API
13
+ * http://doc.navitia.io/#physical-mode
14
+ */
15
+
16
+ const routingModeCorrespondance = new Map();
17
+ routingModeCorrespondance.set('Air', Constants.ROUTING_MODE.AIRPLANE);
18
+ routingModeCorrespondance.set('Boat', Constants.ROUTING_MODE.BOAT);
19
+ routingModeCorrespondance.set('Bus', Constants.ROUTING_MODE.BUS);
20
+ routingModeCorrespondance.set('BusRapidTransit', Constants.ROUTING_MODE.BUS);
21
+ routingModeCorrespondance.set('Coach', Constants.ROUTING_MODE.BUS);
22
+ routingModeCorrespondance.set('Ferry', Constants.ROUTING_MODE.FERRY);
23
+ routingModeCorrespondance.set('Funicular', Constants.ROUTING_MODE.FUNICULAR);
24
+ routingModeCorrespondance.set('LocalTrain', Constants.ROUTING_MODE.TRAIN);
25
+ routingModeCorrespondance.set('LongDistanceTrain', Constants.ROUTING_MODE.TRAIN);
26
+ routingModeCorrespondance.set('Metro', Constants.ROUTING_MODE.METRO);
27
+ routingModeCorrespondance.set('Métro', Constants.ROUTING_MODE.METRO);
28
+ routingModeCorrespondance.set('RailShuttle', Constants.ROUTING_MODE.TRAIN);
29
+ routingModeCorrespondance.set('RapidTransit', Constants.ROUTING_MODE.BUS);
30
+ routingModeCorrespondance.set('Shuttle', Constants.ROUTING_MODE.BUS);
31
+ routingModeCorrespondance.set('SuspendedCableCar', Constants.ROUTING_MODE.FUNICULAR);
32
+ routingModeCorrespondance.set('Taxi', Constants.ROUTING_MODE.TAXI);
33
+ routingModeCorrespondance.set('Train', Constants.ROUTING_MODE.TRAIN);
34
+ routingModeCorrespondance.set('Tramway', Constants.ROUTING_MODE.TRAM);
35
+ routingModeCorrespondance.set('walking', Constants.ROUTING_MODE.WALK);
36
+ routingModeCorrespondance.set('bike', Constants.ROUTING_MODE.BIKE);
37
+
38
+ /**
39
+ * @param {object} json
40
+ * @returns {Coordinates}
41
+ */
42
+ export function jsonToCoordinates(json) {
43
+ return new Coordinates(Number(json.lat), Number(json.lon));
44
+ }
45
+
46
+ function getSectionCoords(section) {
47
+ const from = section.from.stop_point ? jsonToCoordinates(section.from.stop_point.coord) : jsonToCoordinates(section.from.address.coord);
48
+ const to = section.to.stop_point ? jsonToCoordinates(section.to.stop_point.coord) : jsonToCoordinates(section.to.address.coord);
49
+
50
+ return {
51
+ from,
52
+ to
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Get last item of a given array
58
+ * @param {Array} array
59
+ * @returns {any}
60
+ */
61
+ function last(array) {
62
+ return array[array.length - 1];
63
+ }
64
+
65
+ /**
66
+ * Since the IDFM API does not provide coords for each step, we need to compute them
67
+ * We trim the coordinates of the leg with the distance of each step and keep the last result as the coords of the step
68
+ * @param {Leg} leg
69
+ */
70
+ function findStepsCoord(leg) {
71
+ const { steps, coords } = leg;
72
+
73
+ const duplicatedCoords = [...coords];
74
+ let previousStep = steps[0];
75
+ let accumulatedIndex = 0;
76
+
77
+ for (const [idx, step] of steps.entries()) {
78
+ let newCoords;
79
+
80
+ if (idx === 0) {
81
+ step._idCoordsInLeg = 0;
82
+ newCoords = coords[0];
83
+ } else if (idx === steps.length - 1) {
84
+ step._idCoordsInLeg = coords.length - 1;
85
+ newCoords = last(coords);
86
+ } else {
87
+ const result = GeoUtils.trimRoute(duplicatedCoords, duplicatedCoords[0], previousStep.distance);
88
+ accumulatedIndex += result.length - 1;
89
+
90
+ duplicatedCoords.splice(0, result.length - 1);
91
+
92
+ step._idCoordsInLeg = accumulatedIndex;
93
+
94
+ newCoords = last(result);
95
+
96
+ coords[step._idCoordsInLeg] = newCoords;
97
+ }
98
+
99
+ step.coords = newCoords;
100
+
101
+ previousStep = step;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * @param {string} stringDate (e.g. 20211117T104516)
107
+ * @returns {number}
108
+ */
109
+ function dateStringToTimestamp(stringDate, timeZone) {
110
+ const yearStr = stringDate.substr(0, 4);
111
+ const monthStr = stringDate.substr(4, 2);
112
+ const dayStr = stringDate.substr(6, 2);
113
+ const hoursStr = stringDate.substr(9, 2);
114
+ const minutesStr = stringDate.substr(11, 2);
115
+ const secondsStr = stringDate.substr(13, 2);
116
+
117
+ return dateWithTimeZone(
118
+ Number(yearStr),
119
+ Number(monthStr) - 1,
120
+ Number(dayStr),
121
+ Number(hoursStr),
122
+ Number(minutesStr),
123
+ Number(secondsStr),
124
+ timeZone
125
+ ).getTime();
126
+ }
127
+
128
+ /**
129
+ * Generate multi itineraries from OTP JSON
130
+ * @param {object} json JSON file provided by OTP.
131
+ * @returns {?RouterResponse}
132
+ */
133
+ export function createRouterResponseFromJson(json) {
134
+
135
+ if (!json || !json.journeys) {
136
+ return null;
137
+ }
138
+
139
+ const routerResponse = new RouterResponse();
140
+ routerResponse.routerName = 'idfm';
141
+
142
+ routerResponse.from = getSectionCoords(json.journeys[0].sections[0]).from;
143
+ routerResponse.to = getSectionCoords(last(json.journeys[0].sections)).to;
144
+
145
+ const timeZone = json.context.timezone;
146
+
147
+ for (const jsonItinerary of json.journeys) {
148
+
149
+ const itinerary = new Itinerary();
150
+
151
+ itinerary.duration = jsonItinerary.duration;
152
+ itinerary.startTime = dateStringToTimestamp(jsonItinerary.departure_date_time, timeZone);
153
+ itinerary.endTime = dateStringToTimestamp(jsonItinerary.arrival_date_time, timeZone);
154
+ itinerary.from = routerResponse.from;
155
+ itinerary.to = routerResponse.to;
156
+ itinerary.distance = 0;
157
+
158
+ routerResponse.itineraries.push(itinerary);
159
+
160
+ for (const jsonSection of jsonItinerary.sections) {
161
+
162
+ if (jsonSection.type === 'waiting' || jsonSection.type === 'transfer') {
163
+ continue;
164
+ }
165
+
166
+ const leg = new Leg();
167
+ let existingCoords = [];
168
+ const { from, to } = getSectionCoords(jsonSection);
169
+
170
+ leg.distance = 0;
171
+ leg.mode = routingModeCorrespondance.get(jsonSection.mode);
172
+ leg.duration = jsonSection.duration;
173
+ leg.startTime = dateStringToTimestamp(jsonSection.departure_date_time, timeZone);
174
+ leg.endTime = dateStringToTimestamp(jsonSection.arrival_date_time, timeZone);
175
+
176
+ leg.from = {
177
+ name: jsonSection.from.name,
178
+ coords: from
179
+ };
180
+
181
+ leg.to = {
182
+ name: jsonSection.to.name,
183
+ coords: to
184
+ };
185
+
186
+ // A section can have multiple same coordinates, we need to remove them
187
+ leg.coords = jsonSection.geojson.coordinates.reduce((acc, [lon, lat]) => {
188
+ if (!existingCoords.includes(`${lon}-${lat}`)) {
189
+ existingCoords = existingCoords.concat(`${lon}-${lat}`);
190
+ acc.push(new Coordinates(lat, lon));
191
+ }
192
+
193
+ return acc;
194
+ }, []);
195
+
196
+ leg.steps = [];
197
+
198
+ if (jsonSection.path) {
199
+ for (const jsonPathLink of jsonSection.path) {
200
+ const step = new Step();
201
+
202
+ step.levelChange = null;
203
+
204
+ step.name = jsonPathLink.name;
205
+ step.distance = jsonPathLink.length;
206
+
207
+ leg.distance += step.distance;
208
+ leg.steps.push(step);
209
+ }
210
+
211
+ findStepsCoord(leg);
212
+ }
213
+
214
+ if (jsonSection.type === 'public_transport') {
215
+ leg.transportInfo = {
216
+ name: jsonSection.display_informations.code,
217
+ routeColor: jsonSection.display_informations.color,
218
+ routeTextColor: jsonSection.display_informations.text_color,
219
+ directionName: jsonSection.display_informations.direction
220
+ };
221
+
222
+ leg.mode = routingModeCorrespondance.get(jsonSection.display_informations.physical_mode);
223
+
224
+ const legStep = new Step();
225
+ legStep.coords = leg.coords[0];
226
+ legStep._idCoordsInLeg = 0;
227
+ legStep.name = leg.transportInfo.directionName;
228
+ legStep.levelChange = null;
229
+ legStep.distance = jsonSection.geojson.properties[0].length;
230
+
231
+ leg.steps = [legStep];
232
+ }
233
+
234
+ itinerary.distance += leg.distance;
235
+
236
+ itinerary.legs.push(leg);
237
+
238
+ }
239
+
240
+ // All legs have to be parsed before computing steps metadata
241
+ generateStepsMetadata(itinerary);
242
+ }
243
+
244
+ return routerResponse;
245
+ }
246
+
247
+
@@ -0,0 +1,64 @@
1
+ /* eslint-disable max-statements */
2
+ import chai from 'chai';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ import { Coordinates } from '@wemap/geo';
8
+
9
+ import { createRouterResponseFromJson } from './IdfmUtils.js';
10
+
11
+ import { verifyRouterResponseData } from '../model/RouterResponse.type.spec.js';
12
+
13
+
14
+ const { expect } = chai;
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+
18
+ describe('IdfmUtils - createRouterResponseFromJson', () => {
19
+
20
+ it('RouterResponse - 1', () => {
21
+
22
+ const filePath = path.resolve(__dirname, '../../assets/itinerary-paris-idfm.json');
23
+ const fileString = fs.readFileSync(filePath, 'utf8');
24
+ const json = JSON.parse(fileString);
25
+
26
+ const routerResponse = createRouterResponseFromJson(json);
27
+ verifyRouterResponseData(routerResponse);
28
+
29
+ expect(routerResponse.routerName).equal('idfm');
30
+ expect(routerResponse.itineraries.length).equal(5);
31
+ expect(routerResponse.from.equalsTo(new Coordinates(48.875877, 2.301357))).true;
32
+ expect(routerResponse.to.equalsTo(new Coordinates(48.877877, 2.351929))).true;
33
+
34
+ const itinerary1 = routerResponse.itineraries[0];
35
+ expect(itinerary1.distance).to.be.closeTo(1242, 1);
36
+ expect(itinerary1.duration).equal(1842);
37
+ expect(itinerary1.mode).equal('PT');
38
+ // Do not work because of the input time format
39
+ expect(itinerary1.startTime).equal(1637596640000);
40
+ expect(itinerary1.endTime).equal(1637598482000);
41
+ expect(itinerary1.legs.length).equal(3);
42
+
43
+ const itinerary1leg1 = itinerary1.legs[0];
44
+ // Do not work because of the input time format
45
+ expect(itinerary1leg1.startTime).equal(1637596640000);
46
+ expect(itinerary1leg1.endTime).equal(1637597040000);
47
+ expect(itinerary1leg1.distance).to.be.closeTo(468, 1);
48
+ expect(itinerary1leg1.mode).equal('WALK');
49
+ expect(itinerary1leg1.transportInfo).is.null;
50
+ expect(itinerary1leg1.from.name).equal('8 Avenue Bertie Albrecht (Paris)');
51
+ expect(itinerary1leg1.from.coords.equalsTo(new Coordinates(48.875877, 2.301357))).true;
52
+ expect(itinerary1leg1.to.name).equal('Ternes (Paris)');
53
+ expect(itinerary1leg1.to.coords.equalsTo(new Coordinates(48.878228, 2.298113))).true;
54
+
55
+ const itinerary1leg2 = itinerary1.legs[1];
56
+ expect(itinerary1leg2.mode).equal('METRO');
57
+ expect(itinerary1leg2.transportInfo).is.not.null;
58
+ expect(itinerary1leg2.transportInfo.name).equal('2');
59
+ expect(itinerary1leg2.transportInfo.routeColor).equal('003CA6');
60
+ expect(itinerary1leg2.transportInfo.routeTextColor).equal('FFFFFF');
61
+ expect(itinerary1leg2.transportInfo.directionName).equal('Nation (Paris)');
62
+ });
63
+ });
64
+
@@ -2,6 +2,7 @@
2
2
  import { Coordinates, Network } from '@wemap/geo';
3
3
  import Leg from './Leg.js';
4
4
  import Step from './Step.js';
5
+ import Constants from '../Constants.js';
5
6
  import { getDurationFromLength } from '../Utils.js';
6
7
 
7
8
  /**
@@ -78,9 +79,9 @@ class Itinerary {
78
79
  let isDriving;
79
80
 
80
81
  this.legs.forEach((leg) => {
81
- isPublicTransport = isPublicTransport || ['BUS', 'FUNICULAR', 'TRAM', 'TRAIN'].includes(leg.mode);
82
- isBicycle = isBicycle || ['BIKE', 'BICYCLE'].includes(leg.mode);
83
- isDriving = isDriving || leg.mode === 'CAR';
82
+ isPublicTransport = isPublicTransport || Constants.PUBLIC_TRANSPORT.includes(leg.mode);
83
+ isBicycle = isBicycle || leg.mode === Constants.ROUTING_MODE.BIKE;
84
+ isDriving = isDriving || leg.mode === Constants.ROUTING_MODE.CAR;
84
85
  });
85
86
 
86
87
  if (isPublicTransport) {
package/src/model/Leg.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { Coordinates, Network } from '@wemap/geo';
2
2
 
3
3
  import Step from './Step.js';
4
+ import Constants from '../Constants.js';
4
5
 
5
6
  class Leg {
6
7
 
7
- /** @type {!string} can be WALK, BIKE, BUS, TRAM, CAR, FUNICULAR */
8
+ /** @type {!string} can be values in Constants.ROUTING_MODE */
8
9
  mode;
9
10
 
10
11
  /** @type {!number} */
@@ -34,6 +35,10 @@ class Leg {
34
35
  /** @type {?(Step[])} */
35
36
  steps = null;
36
37
 
38
+ isPublicTransport() {
39
+ return Constants.PUBLIC_TRANSPORT.includes(this.mode);
40
+ }
41
+
37
42
  /**
38
43
  * @returns {Network}
39
44
  */