@wemap/routers 6.2.3 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/biocbon-bergere-rdc-network.osm +163 -0
- package/assets/gare-de-lest-network-pp-bounds.osm +1615 -0
- package/dist/wemap-routers.es.js +3011 -0
- package/dist/wemap-routers.es.js.map +1 -0
- package/index.js +13 -5
- package/package.json +9 -6
- package/src/Constants.js +1 -0
- package/src/ItineraryInfoManager.spec.js +2 -2
- package/src/Utils.js +0 -77
- package/src/model/Itinerary.js +41 -5
- package/src/model/Itinerary.spec.js +91 -0
- package/src/model/Itinerary.type.spec.js +3 -78
- package/src/model/Leg.js +89 -19
- package/src/model/Leg.spec.js +110 -0
- package/src/model/Leg.type.spec.js +48 -0
- package/src/model/LevelChange.js +14 -24
- package/src/model/LevelChange.spec.js +78 -0
- package/src/model/LevelChange.type.spec.js +26 -0
- package/src/model/RouterResponse.js +70 -1
- package/src/model/RouterResponse.spec.js +85 -0
- package/src/model/RouterResponse.type.spec.js +7 -4
- package/src/model/Step.js +45 -6
- package/src/model/Step.spec.js +100 -0
- package/src/model/Step.type.spec.js +52 -0
- package/src/remote/RemoteRouter.js +31 -0
- package/src/remote/RemoteRouterManager.js +84 -0
- package/src/remote/RemoteRouterOptions.js +25 -0
- package/src/remote/RemoteRouterServerUnreachable.js +10 -0
- package/src/remote/RemoteRouterUtils.js +78 -0
- package/src/remote/RoutingModeCorrespondanceNotFound.js +18 -0
- package/src/remote/cityway/CitywayRemoteRouter.js +386 -0
- package/src/{cityway/CitywayUtils.spec.js → remote/cityway/CitywayRemoteRouter.spec.js} +19 -18
- package/src/remote/deutsche-bahn/DeutscheBahnRemoteRouter.js +143 -0
- package/src/{deutsche-bahn/DeutscheBahnRouterUtils.spec.js → remote/deutsche-bahn/DeutscheBahnRemoteRouter.spec.js} +7 -6
- package/src/remote/idfm/IdfmRemoteRouter.js +432 -0
- package/src/{idfm/IdfmUtils.spec.js → remote/idfm/IdfmRemoteRouter.spec.js} +7 -6
- package/src/remote/idfm/IdfmRemoteRouterTokenError.js +6 -0
- package/src/remote/osrm/OsrmRemoteRouter.js +331 -0
- package/src/{osrm/OsrmUtils.spec.js → remote/osrm/OsrmRemoteRouter.spec.js} +9 -15
- package/src/remote/otp/OtpRemoteRouter.js +222 -0
- package/src/{otp/OtpUtils.spec.js → remote/otp/OtpRemoteRouter.spec.js} +10 -9
- package/src/remote/wemap-meta/WemapMetaRemoteRouter.js +57 -0
- package/src/remote/wemap-meta/WemapMetaRemoteRouter.spec.js +22 -0
- package/src/remote/wemap-meta/WemapMetaRemoteRouterOptions.js +36 -0
- package/src/remote/wemap-meta/WemapMetaRemoteRouterPayload.js +44 -0
- package/src/wemap/WemapRouter.js +6 -0
- package/src/wemap/WemapRouterUtils.js +10 -4
- package/src/wemap/WemapStepsGeneration.js +36 -9
- package/src/wemap-meta/IOMap.js +191 -0
- package/src/wemap-meta/WemapMetaRouter.js +314 -0
- package/src/wemap-meta/WemapMetaRouter.spec.js +119 -0
- package/src/wemap-meta/WemapMetaRouterOptions.js +20 -0
- package/src/cityway/CitywayUtils.js +0 -309
- package/src/deutsche-bahn/DeutscheBahnRouterUtils.js +0 -91
- package/src/idfm/IdfmUtils.js +0 -256
- package/src/osrm/OsrmUtils.js +0 -269
- package/src/otp/OtpUtils.js +0 -150
|
@@ -0,0 +1,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();
|
|
@@ -6,25 +6,26 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
|
|
7
7
|
import { Coordinates } from '@wemap/geo';
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import CitywayRemoteRouter from './CitywayRemoteRouter.js';
|
|
10
10
|
|
|
11
|
-
import
|
|
11
|
+
import checkRouterResponseType from '../../model/RouterResponse.type.spec.js';
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
const { expect } = chai;
|
|
15
15
|
|
|
16
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const assetsPath = path.resolve(__dirname, '../../../assets');
|
|
17
18
|
|
|
18
|
-
describe('
|
|
19
|
+
describe('CitywayRemoteRouter - createRouterResponseFromJson', () => {
|
|
19
20
|
|
|
20
21
|
it('RouterResponse - 1', () => {
|
|
21
22
|
|
|
22
|
-
const filePath = path.resolve(
|
|
23
|
+
const filePath = path.resolve(assetsPath, 'itinerary-lehavre-cityway-1.json');
|
|
23
24
|
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
24
25
|
const json = JSON.parse(fileString);
|
|
25
26
|
|
|
26
|
-
const routerResponse = createRouterResponseFromJson(json);
|
|
27
|
-
|
|
27
|
+
const routerResponse = CitywayRemoteRouter.createRouterResponseFromJson(json);
|
|
28
|
+
checkRouterResponseType(routerResponse);
|
|
28
29
|
|
|
29
30
|
expect(routerResponse.routerName).equal('cityway');
|
|
30
31
|
expect(routerResponse.itineraries.length).equal(3);
|
|
@@ -62,48 +63,48 @@ describe('CitywayUtils - createRouterResponseFromJson', () => {
|
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
it('RouterResponse - 2', () => {
|
|
65
|
-
const filePath = path.resolve(
|
|
66
|
+
const filePath = path.resolve(assetsPath, 'itinerary-lehavre-cityway-2.json');
|
|
66
67
|
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
67
68
|
const json = JSON.parse(fileString);
|
|
68
69
|
|
|
69
|
-
const routerResponse = createRouterResponseFromJson(json);
|
|
70
|
-
|
|
70
|
+
const routerResponse = CitywayRemoteRouter.createRouterResponseFromJson(json);
|
|
71
|
+
checkRouterResponseType(routerResponse);
|
|
71
72
|
|
|
72
73
|
expect(routerResponse.itineraries.length).equal(1);
|
|
73
74
|
expect(routerResponse.itineraries[0].mode).equal('WALK');
|
|
74
75
|
});
|
|
75
76
|
|
|
76
77
|
it('RouterResponse - 3', () => {
|
|
77
|
-
const filePath = path.resolve(
|
|
78
|
+
const filePath = path.resolve(assetsPath, 'itinerary-lehavre-cityway-3.json');
|
|
78
79
|
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
79
80
|
const json = JSON.parse(fileString);
|
|
80
81
|
|
|
81
|
-
const routerResponse = createRouterResponseFromJson(json);
|
|
82
|
-
|
|
82
|
+
const routerResponse = CitywayRemoteRouter.createRouterResponseFromJson(json);
|
|
83
|
+
checkRouterResponseType(routerResponse);
|
|
83
84
|
|
|
84
85
|
expect(routerResponse.itineraries[0].mode).equal('PT');
|
|
85
86
|
expect(routerResponse.itineraries[0].legs[1].mode).equal('FUNICULAR');
|
|
86
87
|
});
|
|
87
88
|
|
|
88
89
|
it('RouterResponse - 4', () => {
|
|
89
|
-
const filePath = path.resolve(
|
|
90
|
+
const filePath = path.resolve(assetsPath, 'itinerary-lehavre-cityway-4.json');
|
|
90
91
|
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
91
92
|
const json = JSON.parse(fileString);
|
|
92
93
|
|
|
93
|
-
const routerResponse = createRouterResponseFromJson(json);
|
|
94
|
-
|
|
94
|
+
const routerResponse = CitywayRemoteRouter.createRouterResponseFromJson(json);
|
|
95
|
+
checkRouterResponseType(routerResponse);
|
|
95
96
|
|
|
96
97
|
expect(routerResponse.itineraries[0].mode).equal('BIKE');
|
|
97
98
|
expect(routerResponse.itineraries[0].legs[0].mode).equal('BIKE');
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
it('RouterResponse - 5', () => {
|
|
101
|
-
const filePath = path.resolve(
|
|
102
|
+
const filePath = path.resolve(assetsPath, 'itinerary-lehavre-cityway-5.json');
|
|
102
103
|
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
103
104
|
const json = JSON.parse(fileString);
|
|
104
105
|
|
|
105
|
-
const routerResponse = createRouterResponseFromJson(json);
|
|
106
|
-
|
|
106
|
+
const routerResponse = CitywayRemoteRouter.createRouterResponseFromJson(json);
|
|
107
|
+
checkRouterResponseType(routerResponse);
|
|
107
108
|
|
|
108
109
|
expect(routerResponse.itineraries[1].mode).equal('PT');
|
|
109
110
|
expect(routerResponse.itineraries[1].legs[1].mode).equal('TRAM');
|