@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,52 @@
1
+ import chai from 'chai';
2
+
3
+ import { Coordinates } from '@wemap/geo';
4
+
5
+ import LevelChange from './LevelChange.js';
6
+ import Step from './Step.js';
7
+ import checkLevelChangeType from './LevelChange.type.spec.js';
8
+
9
+ const { expect } = chai;
10
+
11
+
12
+ const isNullOrNumber = val => val === null || typeof val === 'number';
13
+ const isNullOrString = val => val === null || typeof val === 'string';
14
+ const isNullOrObject = val => val === null || typeof val === 'object';
15
+ const isNullOrLevelChange = val => val === null || val instanceof LevelChange;
16
+
17
+ const isUndefinedOrBoolean = val => typeof val === 'undefined' || typeof val === 'boolean';
18
+ const isUndefinedOrString = val => typeof val === 'undefined' || typeof val === 'string';
19
+
20
+ const stepExtraProperties = ['subwayEntrance', 'subwayEntranceRef'];
21
+
22
+ /**
23
+ * @param {Step} step
24
+ */
25
+ export default function checkStepType(step) {
26
+
27
+ expect(step).instanceOf(Step);
28
+ expect(step.firstStep).be.a('boolean');
29
+ expect(step.lastStep).be.a('boolean');
30
+ expect(step.number).be.a('number');
31
+ expect(step.coords).instanceOf(Coordinates);
32
+ expect(step.angle).be.a('number');
33
+ expect(step.previousBearing).be.a('number');
34
+ expect(step.nextBearing).be.a('number');
35
+ expect(step.distance).be.a('number');
36
+ expect(step.duration).satisfies(isNullOrNumber);
37
+ expect(step.name).satisfies(isNullOrString);
38
+ expect(step.levelChange).satisfies(isNullOrLevelChange);
39
+ if (step.levelChange) {
40
+ checkLevelChangeType(step.levelChange);
41
+ }
42
+ expect(step._idCoordsInLeg).be.a('number');
43
+
44
+ expect(step.extras).satisfies(isNullOrObject);
45
+ if (step.extras !== null) {
46
+ for (const key of Object.keys(step.extras)) {
47
+ expect(stepExtraProperties).includes(key);
48
+ }
49
+ expect(step.extras.subwayEntrance).satisfies(isUndefinedOrBoolean);
50
+ expect(step.extras.subwayEntranceRef).satisfies(isUndefinedOrString);
51
+ }
52
+ }
@@ -0,0 +1,31 @@
1
+ import { Coordinates } from '@wemap/geo';
2
+
3
+ import RouterResponse from '../model/RouterResponse.js';
4
+ import RemoteRouterOptions from './RemoteRouterOptions.js';
5
+
6
+ class RemoteRouter {
7
+
8
+ /**
9
+ * Get the router name
10
+ * @type {string} the router name
11
+ */
12
+ get rname() {
13
+ return 'Unknown';
14
+ }
15
+
16
+ /**
17
+ * @abstract
18
+ * @param {string} endpointUrl
19
+ * @param {string} mode see Constants.ROUTING_MODE
20
+ * @param {Array<Coordinates>} waypoints
21
+ * @param {RemoteRouterOptions} options
22
+ * @returns {!RouterResponse}
23
+ */
24
+ // eslint-disable-next-line no-unused-vars
25
+ async getItineraries(endpointUrl, mode, waypoints, options = new RemoteRouterOptions()) {
26
+ throw new Error('RemoteRouter "' + this.rname + '" does not @override getItineraries()');
27
+ }
28
+
29
+ }
30
+
31
+ export default RemoteRouter;
@@ -0,0 +1,84 @@
1
+ import { Coordinates } from '@wemap/geo';
2
+
3
+ import RouterResponse from '../model/RouterResponse.js';
4
+ import RemoteRouter from './RemoteRouter.js';
5
+ import CitywayRemoteRouter from './cityway/CitywayRemoteRouter.js';
6
+ import DeutscheBahnRemoteRouter from './deutsche-bahn/DeutscheBahnRemoteRouter.js';
7
+ import IdfmRemoteRouter from './idfm/IdfmRemoteRouter.js';
8
+ import OsrmRemoteRouter from './osrm/OsrmRemoteRouter.js';
9
+ import OtpRemoteRouter from './otp/OtpRemoteRouter.js';
10
+ import RemoteRouterOptions from './RemoteRouterOptions.js';
11
+ import RemoteRouterServerUnreachable from './RemoteRouterServerUnreachable.js';
12
+ import RoutingModeCorrespondanceNotFound from './RoutingModeCorrespondanceNotFound.js';
13
+ import IdfmRemoteRouterTokenError from './idfm/IdfmRemoteRouterTokenError.js';
14
+
15
+ /**
16
+ * Singleton
17
+ */
18
+ class RemoteRouterManager {
19
+
20
+ /** @type {RemoteRouter[]} */
21
+ remoteRouters = [
22
+ CitywayRemoteRouter,
23
+ DeutscheBahnRemoteRouter,
24
+ IdfmRemoteRouter,
25
+ OsrmRemoteRouter,
26
+ OtpRemoteRouter
27
+ ];
28
+
29
+ /**
30
+ * @param {string} name
31
+ * @returns {RemoteRouter}
32
+ */
33
+ getRouterByName(name) {
34
+ return this.remoteRouters.find(remoteRouter => remoteRouter.rname === name);
35
+ }
36
+
37
+ /**
38
+ * @param {!string} endpointUrl
39
+ * @param {!string} mode see Constants.ROUTING_MODE
40
+ * @param {Array<Coordinates>} waypoints
41
+ * @param {?RemoteRouterOptions} options
42
+ * @returns {!RouterResponse}
43
+ * @throws {RemoteRouterServerUnreachable}
44
+ * @throws {RoutingModeCorrespondanceNotFound}
45
+ * @throws {IdfmRemoteRouterTokenError}
46
+ */
47
+ async getItineraries(name, endpointUrl, mode, waypoints, options = new RemoteRouterOptions()) {
48
+ const router = this.getRouterByName(name);
49
+ if (!router) {
50
+ throw new Error(`Unknown "${this.rname}" remote router`);
51
+ }
52
+ return router.getItineraries(endpointUrl, mode, waypoints, options);
53
+ }
54
+
55
+
56
+ /**
57
+ * @param {!{name: string, endpointUrl: string}[]} remoteRouters
58
+ * @param {!string} mode see Constants.ROUTING_MODE
59
+ * @param {!Array<Coordinates>} waypoints
60
+ * @param {?RemoteRouterOptions} options
61
+ * @returns {!RouterResponse}
62
+ * @throws {RemoteRouterServerUnreachable}
63
+ * @throws {RoutingModeCorrespondanceNotFound}
64
+ * @throws {IdfmRemoteRouterTokenError}
65
+ */
66
+ async getItinerariesWithFallback(remoteRouters, mode, waypoints, options = new RemoteRouterOptions()) {
67
+ let routerResponse;
68
+ for (const { name, endpointUrl } of remoteRouters) {
69
+ routerResponse = await this.getItineraries(name, endpointUrl, mode, waypoints, options);
70
+ if (routerResponse.itineraries.length) {
71
+ return routerResponse;
72
+ }
73
+ }
74
+ if (!routerResponse) {
75
+ routerResponse = new RouterResponse();
76
+ routerResponse.from = waypoints[0];
77
+ routerResponse.to = waypoints[waypoints.length];
78
+ }
79
+ return routerResponse;
80
+ }
81
+
82
+ }
83
+
84
+ export default new RemoteRouterManager();
@@ -0,0 +1,25 @@
1
+ class RemoteRouterOptions {
2
+
3
+ /** @type {boolean} */
4
+ useStairs = true;
5
+
6
+ /**
7
+ * @returns {object}
8
+ */
9
+ toJson() {
10
+ return { useStairs: this.useStairs };
11
+ }
12
+
13
+ /**
14
+ * @param {object}
15
+ * @returns {RemoteRouterOptions}
16
+ */
17
+ static fromJson(json) {
18
+ const obj = new RemoteRouterOptions();
19
+ obj.useStairs = json.useStairs;
20
+ return obj;
21
+ }
22
+
23
+ }
24
+
25
+ export default RemoteRouterOptions;
@@ -0,0 +1,10 @@
1
+ export default class RemoteRouterServerUnreachable extends Error {
2
+
3
+ /**
4
+ * @param {!string} name
5
+ * @param {!string} endpointUrl
6
+ */
7
+ constructor(name, endpointUrl) {
8
+ super(`Remote router server ${name} is unreachable. URL: ${endpointUrl}`);
9
+ }
10
+ }
@@ -0,0 +1,78 @@
1
+ import { diffAngle } from '@wemap/maths';
2
+
3
+ import Itinerary from '../model/Itinerary.js';
4
+
5
+
6
+ /**
7
+ * @param {Itinerary} itinerary
8
+ */
9
+ export function generateStepsMetadata(itinerary) {
10
+
11
+ let counter = 1;
12
+
13
+ itinerary.legs.forEach((leg, legId) => {
14
+ leg.steps.forEach((step, stepId) => {
15
+
16
+ if (counter === 1) {
17
+ step.firstStep = true;
18
+ }
19
+ if (legId === itinerary.legs.length - 1
20
+ && stepId === leg.steps.length - 1) {
21
+ step.lastStep = true;
22
+ }
23
+
24
+ step.number = counter++;
25
+
26
+
27
+ /*
28
+ * Generate previousBearing, nextBearing and angle
29
+ */
30
+
31
+ let coordsBeforeStep;
32
+ if (step._idCoordsInLeg > 0) {
33
+ coordsBeforeStep = leg.coords[step._idCoordsInLeg - 1];
34
+ } else if (legId === 0) {
35
+ coordsBeforeStep = itinerary.from;
36
+ } else {
37
+ coordsBeforeStep = itinerary.legs[legId - 1].to.coords;
38
+ }
39
+
40
+ let coordsAfterStep;
41
+ if (step._idCoordsInLeg !== leg.coords.length - 1) {
42
+ coordsAfterStep = leg.coords[step._idCoordsInLeg + 1];
43
+ } else if (legId === itinerary.legs.length - 1) {
44
+ coordsAfterStep = itinerary.to;
45
+ } else {
46
+ coordsAfterStep = itinerary.legs[legId + 1].from.coords;
47
+ }
48
+
49
+ step.previousBearing = coordsBeforeStep.bearingTo(step.coords);
50
+ step.nextBearing = step.coords.bearingTo(coordsAfterStep);
51
+ step.angle = diffAngle(step.previousBearing, step.nextBearing + Math.PI);
52
+
53
+ });
54
+ });
55
+ }
56
+
57
+
58
+ /**
59
+ * Create and return a date with a given timezone
60
+ * @param {number} year
61
+ * @param {number} month
62
+ * @param {number} day
63
+ * @param {number} hours
64
+ * @param {number} minutes
65
+ * @param {number} seconds
66
+ * @param {string} timeZone - timezone name (e.g. 'Europe/Paris')
67
+ */
68
+ export function dateWithTimeZone(year, month, day, hour, minute, second, timeZone = 'Europe/Paris') {
69
+ const date = new Date(Date.UTC(year, month, day, hour, minute, second));
70
+
71
+ const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
72
+ const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timeZone }));
73
+ const offset = utcDate.getTime() - tzDate.getTime();
74
+
75
+ date.setTime(date.getTime() + offset);
76
+
77
+ return date;
78
+ }
@@ -0,0 +1,18 @@
1
+ export default class RoutingModeCorrespondanceNotFound extends Error {
2
+
3
+ /** @type {!string} */
4
+ routerName;
5
+
6
+ /** @type {!string} */
7
+ routingMode;
8
+
9
+ /**
10
+ * @param {!string} routerName
11
+ * @param {!string} routingMode
12
+ */
13
+ constructor(routerName, routingMode) {
14
+ this.routerName = routerName;
15
+ this.routingMode = routingMode;
16
+ super(`routing mode "${routingMode}" correspondance not found for router "${routerName}"`);
17
+ }
18
+ }
@@ -0,0 +1,386 @@
1
+ /* eslint-disable max-depth */
2
+ /* eslint-disable max-statements */
3
+ import { Coordinates } from '@wemap/geo';
4
+ import Logger from '@wemap/logger';
5
+
6
+ import RemoteRouter from '../RemoteRouter.js';
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, dateWithTimeZone } from '../RemoteRouterUtils.js';
12
+ import Constants from '../../Constants.js';
13
+ import RemoteRouterServerUnreachable from '../RemoteRouterServerUnreachable.js';
14
+ import RoutingModeCorrespondanceNotFound from '../RoutingModeCorrespondanceNotFound.js';
15
+
16
+ /**
17
+ * Input mode correspondance
18
+ */
19
+ const inputModeCorrespondance = new Map();
20
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.CAR, 'Car');
21
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.WALK, 'Walk');
22
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.BIKE, 'Bike');
23
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.BUS, 'PT');
24
+ inputModeCorrespondance.set(Constants.ROUTING_MODE.MULTI, 'PT');
25
+
26
+
27
+ /**
28
+ * List of all routing modes supported by the API
29
+ */
30
+ const routingModeCorrespondance = new Map();
31
+ routingModeCorrespondance.set('WALK', Constants.ROUTING_MODE.WALK);
32
+ routingModeCorrespondance.set('BICYCLE', Constants.ROUTING_MODE.BIKE);
33
+ routingModeCorrespondance.set('TRAMWAY', Constants.ROUTING_MODE.TRAM);
34
+ routingModeCorrespondance.set('METRO', Constants.ROUTING_MODE.METRO);
35
+ routingModeCorrespondance.set('FUNICULAR', Constants.ROUTING_MODE.FUNICULAR);
36
+ routingModeCorrespondance.set('BUS', Constants.ROUTING_MODE.BUS);
37
+ routingModeCorrespondance.set('COACH', Constants.ROUTING_MODE.BUS);
38
+ routingModeCorrespondance.set('SCHOOL', Constants.ROUTING_MODE.BUS);
39
+ routingModeCorrespondance.set('BUS_PMR', Constants.ROUTING_MODE.BUS);
40
+ routingModeCorrespondance.set('MINIBUS', Constants.ROUTING_MODE.BUS);
41
+ routingModeCorrespondance.set('TROLLEY_BUS', Constants.ROUTING_MODE.BUS);
42
+ routingModeCorrespondance.set('TAXIBUS', Constants.ROUTING_MODE.BUS);
43
+ routingModeCorrespondance.set('SHUTTLE', Constants.ROUTING_MODE.BUS);
44
+ routingModeCorrespondance.set('TRAIN', Constants.ROUTING_MODE.TRAIN);
45
+ routingModeCorrespondance.set('HST', Constants.ROUTING_MODE.TRAIN);
46
+ routingModeCorrespondance.set('LOCAL_TRAIN', Constants.ROUTING_MODE.TRAIN);
47
+ routingModeCorrespondance.set('AIR', Constants.ROUTING_MODE.AIRPLANE);
48
+ routingModeCorrespondance.set('FERRY', Constants.ROUTING_MODE.BOAT);
49
+ routingModeCorrespondance.set('TAXI', Constants.ROUTING_MODE.UNKNOWN);
50
+ routingModeCorrespondance.set('CAR_POOL', Constants.ROUTING_MODE.UNKNOWN);
51
+ routingModeCorrespondance.set('PRIVATE_VEHICLE', Constants.ROUTING_MODE.CAR);
52
+ routingModeCorrespondance.set('SCOOTER', Constants.ROUTING_MODE.MOTO);
53
+
54
+ /**
55
+ * List of all plan trip supported by the API
56
+ * Routing mode UNKNOWN means that the itinerary will not be parsed by the router
57
+ */
58
+ const planTripType = new Map();
59
+ planTripType.set(0, Constants.ROUTING_MODE.BUS);
60
+ planTripType.set(1, Constants.ROUTING_MODE.WALK);
61
+ planTripType.set(2, Constants.ROUTING_MODE.BIKE);
62
+ planTripType.set(3, Constants.ROUTING_MODE.CAR);
63
+ planTripType.set(4, Constants.ROUTING_MODE.UNKNOWN);
64
+ planTripType.set(5, Constants.ROUTING_MODE.UNKNOWN);
65
+ planTripType.set(6, Constants.ROUTING_MODE.UNKNOWN);
66
+ planTripType.set(7, Constants.ROUTING_MODE.UNKNOWN);
67
+ planTripType.set(8, Constants.ROUTING_MODE.UNKNOWN);
68
+ planTripType.set(9, Constants.ROUTING_MODE.UNKNOWN);
69
+ planTripType.set(10, Constants.ROUTING_MODE.UNKNOWN);
70
+ planTripType.set(11, Constants.ROUTING_MODE.UNKNOWN);
71
+ planTripType.set(12, Constants.ROUTING_MODE.UNKNOWN);
72
+ planTripType.set(13, Constants.ROUTING_MODE.UNKNOWN);
73
+ planTripType.set(14, Constants.ROUTING_MODE.UNKNOWN);
74
+
75
+
76
+ /**
77
+ * Singleton.
78
+ */
79
+ class CitywayRemoteRouter extends RemoteRouter {
80
+
81
+ /**
82
+ * @override
83
+ */
84
+ get rname() {
85
+ return 'cityway';
86
+ }
87
+
88
+
89
+ /**
90
+ * @override
91
+ * @throws {RoutingModeCorrespondanceNotFound}
92
+ * @throws {RemoteRouterServerUnreachable}
93
+ */
94
+ async getItineraries(endpointUrl, mode, waypoints) {
95
+ const url = this.getURL(endpointUrl, mode, waypoints);
96
+ const res = await fetch(url);
97
+ if (res.status !== 200) {
98
+ throw new RemoteRouterServerUnreachable(this.rname, url);
99
+ }
100
+ const response = await res.json();
101
+ return this.createRouterResponseFromJson(response);
102
+ }
103
+
104
+
105
+ /**
106
+ * @param {string} endpointUrl
107
+ * @param {string} mode
108
+ * @param {Array<Coordinates>} waypoints
109
+ * @throws {RoutingModeCorrespondanceNotFound}
110
+ */
111
+ getURL(endpointUrl, mode, waypoints) {
112
+ const citywayMode = inputModeCorrespondance.get(mode);
113
+ if (!citywayMode) {
114
+ throw new RoutingModeCorrespondanceNotFound(this.rname, mode);
115
+ }
116
+ if (waypoints.length > 2) {
117
+ Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
118
+ }
119
+ const fromPlace = `DepartureLatitude=${waypoints[0].latitude}&DepartureLongitude=${waypoints[0].longitude}`;
120
+ const toPlace = `ArrivalLatitude=${waypoints[1].latitude}&ArrivalLongitude=${waypoints[1].longitude}`;
121
+ const queryMode = `TripModes=${citywayMode}`;
122
+
123
+ const url = new URL(endpointUrl);
124
+ let { search } = url;
125
+ search = (search ? `${search}&` : '?') + `${fromPlace}&${toPlace}&${queryMode}`;
126
+
127
+ return `${url.origin}${url.pathname}${search}`;
128
+ }
129
+
130
+ /**
131
+ * Generate multi itineraries from Cityway JSON
132
+ * @param {object} json JSON file provided by Cityway.
133
+ * @returns {?RouterResponse}
134
+ * @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
135
+ */
136
+ createRouterResponseFromJson(json) {
137
+
138
+ if (json.StatusCode !== 200 || !json.Data || !json.Data.length) {
139
+ return null;
140
+ }
141
+
142
+ const routerResponse = new RouterResponse();
143
+ routerResponse.routerName = this.rname;
144
+
145
+
146
+ // Do not know if it the best approach, but it works...
147
+ const allJsonTrips = json.Data.reduce((acc, dataObj) => {
148
+ acc.push(...dataObj.response.trips.Trip.map(trip => ({
149
+ ...trip,
150
+ ...(dataObj.hasOwnProperty('PlanTripType') ? { PlanTripType: dataObj.PlanTripType } : {})
151
+ })));
152
+ return acc;
153
+ }, []);
154
+
155
+ // eslint-disable-next-line no-labels
156
+ itineraryLoop:
157
+ for (const trip of allJsonTrips) {
158
+
159
+ if (trip.hasOwnProperty('PlanTripType') && planTripType.get(trip.PlanTripType) === Constants.ROUTING_MODE.UNKNOWN) {
160
+ continue;
161
+ }
162
+
163
+ const itinerary = new Itinerary();
164
+
165
+ itinerary.duration = this.parseDuration(trip.Duration);
166
+ itinerary.startTime = this.jsonDateToTimestamp(trip.Departure.Time);
167
+ itinerary.from = this.jsonToCoordinates(trip.Departure.Site.Position);
168
+ itinerary.endTime = this.jsonDateToTimestamp(trip.Arrival.Time);
169
+ itinerary.to = this.jsonToCoordinates(trip.Arrival.Site.Position);
170
+
171
+ for (const jsonSection of trip.sections.Section) {
172
+
173
+ const jsonLeg = jsonSection.Leg ? jsonSection.Leg : jsonSection.PTRide;
174
+
175
+ const leg = new Leg();
176
+
177
+ leg.mode = routingModeCorrespondance.get(jsonLeg.TransportMode);
178
+ leg.duration = this.parseDuration(jsonLeg.Duration);
179
+ leg.startTime = this.jsonDateToTimestamp(jsonLeg.Departure.Time);
180
+ leg.endTime = this.jsonDateToTimestamp(jsonLeg.Arrival.Time);
181
+ leg.coords = [];
182
+
183
+ if (leg.mode === Constants.ROUTING_MODE.UNKNOWN) {
184
+ // eslint-disable-next-line
185
+ continue itineraryLoop;
186
+ }
187
+
188
+ if (leg.mode === Constants.ROUTING_MODE.WALK
189
+ || leg.mode === Constants.ROUTING_MODE.BIKE
190
+ || leg.mode === Constants.ROUTING_MODE.CAR) {
191
+
192
+ leg.from = {
193
+ name: jsonLeg.Departure.Site.Name,
194
+ coords: this.jsonToCoordinates(jsonLeg.Departure.Site.Position)
195
+ };
196
+ leg.to = {
197
+ name: jsonLeg.Arrival.Site.Name,
198
+ coords: this.jsonToCoordinates(jsonLeg.Arrival.Site.Position)
199
+ };
200
+
201
+ leg.steps = [];
202
+ for (const jsonPathLink of jsonLeg.pathLinks.PathLink) {
203
+ const step = new Step();
204
+ let stepCoords;
205
+ if (jsonPathLink.Geometry) {
206
+ stepCoords = this.parseWKTGeometry(jsonPathLink.Geometry);
207
+ } else {
208
+ stepCoords = [leg.from.coords, leg.to.coords];
209
+ }
210
+ step.coords = stepCoords[0];
211
+ step._idCoordsInLeg = leg.coords.length;
212
+ stepCoords.forEach((coords, idx) => {
213
+ if (
214
+ idx !== 0
215
+ || leg.coords.length === 0
216
+ || !leg.coords[leg.coords.length - 1].equalsTo(coords)
217
+ ) {
218
+ leg.coords.push(coords);
219
+ }
220
+ });
221
+
222
+
223
+ step.name = jsonPathLink.Departure.Site.Name;
224
+ step.levelChange = null;
225
+
226
+ step.distance = jsonPathLink.Distance;
227
+
228
+ leg.steps.push(step);
229
+ }
230
+
231
+ } else if (Constants.PUBLIC_TRANSPORT.includes(leg.mode)) {
232
+
233
+ leg.from = {
234
+ name: jsonLeg.Departure.StopPlace.Name,
235
+ coords: this.jsonToCoordinates(jsonLeg.Departure.StopPlace.Position)
236
+ };
237
+ leg.to = {
238
+ name: jsonLeg.Arrival.StopPlace.Name,
239
+ coords: this.jsonToCoordinates(jsonLeg.Arrival.StopPlace.Position)
240
+ };
241
+
242
+ let transportName = jsonLeg.Line.Number;
243
+ if (leg.mode === Constants.ROUTING_MODE.TRAM && transportName.toLowerCase().includes('tram')) {
244
+ // In order to remove the "TRAM " prefix.
245
+ transportName = transportName.substr(5);
246
+ }
247
+
248
+ leg.transportInfo = {
249
+ name: transportName,
250
+ routeColor: jsonLeg.Line.Color,
251
+ routeTextColor: jsonLeg.Line.TextColor,
252
+ directionName: jsonLeg.Destination
253
+ };
254
+
255
+ for (const jsonStep of jsonLeg.steps.Step) {
256
+ const stepCoords = this.parseWKTGeometry(jsonStep.Geometry);
257
+ stepCoords.forEach((coords, idx) => {
258
+ if (
259
+ idx !== 0
260
+ || leg.coords.length === 0
261
+ || !leg.coords[leg.coords.length - 1].equalsTo(coords)
262
+ ) {
263
+ leg.coords.push(coords);
264
+ }
265
+ });
266
+ }
267
+
268
+ const legStep = new Step();
269
+ legStep.coords = leg.coords[0];
270
+ legStep._idCoordsInLeg = 0;
271
+ legStep.name = jsonLeg.Line.Name;
272
+ legStep.levelChange = null;
273
+ legStep.distance = jsonLeg.Distance;
274
+ leg.steps = [legStep];
275
+ } else {
276
+ Logger.warn(`[CitywayParser] Unknown leg mode: ${jsonLeg.TransportMode}`);
277
+ }
278
+
279
+ leg.distance = leg.coords.reduce((acc, coords, idx, arr) => {
280
+ if (idx === 0) {
281
+ return acc;
282
+ }
283
+ return acc + arr[idx - 1].distanceTo(coords);
284
+ }, 0);
285
+
286
+ itinerary.legs.push(leg);
287
+
288
+ }
289
+
290
+ routerResponse.itineraries.push(itinerary);
291
+
292
+ itinerary.distance = itinerary.coords.reduce((acc, coords, idx, arr) => {
293
+ if (idx === 0) {
294
+ return acc;
295
+ }
296
+ return acc + arr[idx - 1].distanceTo(coords);
297
+ }, 0);
298
+
299
+ // All legs have to be parsed before computing steps metadata
300
+ generateStepsMetadata(itinerary);
301
+ }
302
+
303
+ routerResponse.from = routerResponse.itineraries[0].from;
304
+ routerResponse.to = routerResponse.itineraries[routerResponse.itineraries.length - 1].to;
305
+
306
+ return routerResponse;
307
+ }
308
+
309
+ /**
310
+ * @param {object} json
311
+ * @returns {Coordinates}
312
+ */
313
+ jsonToCoordinates(json) {
314
+ return new Coordinates(json.Lat, json.Long);
315
+ }
316
+
317
+ /**
318
+ * @param {string} jsonDate
319
+ * @returns {number}
320
+ */
321
+ jsonDateToTimestamp(jsonDate) {
322
+ const [dateStr, timeStr] = jsonDate.split(' ');
323
+ const [dayStr, monthStr, yearStr] = dateStr.split('/');
324
+ const [hoursStr, minutesStr, secondsStr] = timeStr.split(':');
325
+
326
+ return dateWithTimeZone(
327
+ Number(yearStr),
328
+ Number(monthStr) - 1,
329
+ Number(dayStr),
330
+ Number(hoursStr),
331
+ Number(minutesStr),
332
+ Number(secondsStr)
333
+ ).getTime();
334
+ }
335
+
336
+ /**
337
+ * @param {string} wktGeometry
338
+ * @returns {Coordinates[]}
339
+ */
340
+ parseWKTGeometry(wktGeometry) {
341
+ const tmpCoordsStr = wktGeometry.match(/LINESTRING \((.*)\)/i);
342
+ const tmpCoordsPt = wktGeometry.match(/POINT \((.*)\)/i);
343
+ if (!tmpCoordsStr && !tmpCoordsPt) {
344
+ return null;
345
+ }
346
+
347
+ if (tmpCoordsPt) {
348
+ const [lng, lat] = tmpCoordsPt[1].split(' ');
349
+ return [new Coordinates(Number(lat), Number(lng))];
350
+ }
351
+
352
+ return tmpCoordsStr[1].split(',').map(str => {
353
+ const sp = str.trim().split(' ');
354
+ return new Coordinates(Number(sp[1]), Number(sp[0]));
355
+ });
356
+ }
357
+
358
+ /**
359
+ * @param {string} iso8601Duration
360
+ * @see https://stackoverflow.com/a/29153059/2239938
361
+ */
362
+ parseDuration(iso8601Duration) {
363
+ const iso8601DurationRegex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
364
+
365
+ var matches = iso8601Duration.match(iso8601DurationRegex);
366
+
367
+ // const sign = typeof matches[1] === 'undefined' ? '+' : '-',
368
+ const years = typeof matches[2] === 'undefined' ? 0 : Number(matches[2]);
369
+ const months = typeof matches[3] === 'undefined' ? 0 : Number(matches[3]);
370
+ const weeks = typeof matches[4] === 'undefined' ? 0 : Number(matches[4]);
371
+ const days = typeof matches[5] === 'undefined' ? 0 : Number(matches[5]);
372
+ const hours = typeof matches[6] === 'undefined' ? 0 : Number(matches[6]);
373
+ const minutes = typeof matches[7] === 'undefined' ? 0 : Number(matches[7]);
374
+ const seconds = typeof matches[8] === 'undefined' ? 0 : Number(matches[8]);
375
+
376
+ return seconds
377
+ + minutes * 60
378
+ + hours * 3600
379
+ + days * 86400
380
+ + weeks * (86400 * 7)
381
+ + months * (86400 * 30)
382
+ + years * (86400 * 365.25);
383
+ }
384
+ }
385
+
386
+ export default new CitywayRemoteRouter();