@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,143 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
|
|
3
|
+
import { Coordinates, GraphEdge, GraphItinerary, GraphNode, Level } from '@wemap/geo';
|
|
4
|
+
import { OsmElement, OsmNode, OsmWay } from '@wemap/osm';
|
|
5
|
+
|
|
6
|
+
import RemoteRouter from '../RemoteRouter.js';
|
|
7
|
+
import Itinerary from '../../model/Itinerary.js';
|
|
8
|
+
import RouterResponse from '../../model/RouterResponse.js';
|
|
9
|
+
import { generateStepsMetadata } from '../RemoteRouterUtils.js';
|
|
10
|
+
import StepsGeneration from '../../wemap/WemapStepsGeneration.js';
|
|
11
|
+
import RemoteRouterServerUnreachable from '../RemoteRouterServerUnreachable.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Singleton.
|
|
15
|
+
*/
|
|
16
|
+
class DeutscheBahnRemoteRouter extends RemoteRouter {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @override
|
|
20
|
+
*/
|
|
21
|
+
get rname() {
|
|
22
|
+
return 'deutsche-bahn';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @override
|
|
27
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
28
|
+
*/
|
|
29
|
+
async getItineraries(endpointUrl, mode, waypoints) {
|
|
30
|
+
const url = this.getURL(endpointUrl, mode, waypoints);
|
|
31
|
+
const res = await fetch(url);
|
|
32
|
+
if (res.status !== 200) {
|
|
33
|
+
throw new RemoteRouterServerUnreachable(this.rname, url);
|
|
34
|
+
}
|
|
35
|
+
const response = await res.json();
|
|
36
|
+
return this.createRouterResponseFromJson(response, waypoints[0], waypoints[1]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} endpointUrl
|
|
41
|
+
* @param {string} mode
|
|
42
|
+
* @param {Array<Coordinates>} waypoints
|
|
43
|
+
*/
|
|
44
|
+
getURL(endpointUrl, mode, waypoints) {
|
|
45
|
+
let url = endpointUrl + '/route/v1/walking/';
|
|
46
|
+
|
|
47
|
+
url += waypoints.map(waypoint => {
|
|
48
|
+
if (waypoint.level) {
|
|
49
|
+
const altitude = waypoint.level.isRange ? waypoint.level.low : waypoint.level.val;
|
|
50
|
+
|
|
51
|
+
return waypoint.longitude + ',' + waypoint.latitude + ',' + altitude;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return waypoint.longitude + ',' + waypoint.latitude;
|
|
55
|
+
}).join(';');
|
|
56
|
+
|
|
57
|
+
url += '?geometries=geojson&overview=full&steps=true';
|
|
58
|
+
|
|
59
|
+
return url;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generate multi itineraries from the DB JSON
|
|
64
|
+
* @param {object} json JSON file provided by the DB.
|
|
65
|
+
* @param {Coordinates} from itinerary start
|
|
66
|
+
* @param {Coordinates} to itinerary end
|
|
67
|
+
* @returns {?RouterResponse}
|
|
68
|
+
*/
|
|
69
|
+
createRouterResponseFromJson(json, from, to) {
|
|
70
|
+
|
|
71
|
+
const { segments: jsonSegments } = json;
|
|
72
|
+
|
|
73
|
+
if (!jsonSegments) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const routerResponse = new RouterResponse();
|
|
78
|
+
routerResponse.routerName = this.rname;
|
|
79
|
+
|
|
80
|
+
routerResponse.from = from;
|
|
81
|
+
routerResponse.to = to;
|
|
82
|
+
|
|
83
|
+
/** @type {GraphEdge<OsmElement>[]} */
|
|
84
|
+
const edges = [];
|
|
85
|
+
|
|
86
|
+
/** @type {GraphNode<OsmElement>[]} */
|
|
87
|
+
const nodes = [];
|
|
88
|
+
|
|
89
|
+
/** @type {number[]} */
|
|
90
|
+
const edgesWeights = [];
|
|
91
|
+
|
|
92
|
+
let id = 1;
|
|
93
|
+
for (const jsonSegment of jsonSegments) {
|
|
94
|
+
|
|
95
|
+
const level = new Level(jsonSegment.fromLevel, jsonSegment.toLevel);
|
|
96
|
+
const osmWay = new OsmWay(id++, null, level);
|
|
97
|
+
|
|
98
|
+
for (const jsonPoint of jsonSegment.polyline) {
|
|
99
|
+
const coord = new Coordinates(jsonPoint.lat, jsonPoint.lon, null, level);
|
|
100
|
+
|
|
101
|
+
if (nodes.length !== 0
|
|
102
|
+
&& nodes[nodes.length - 1].coords.equalsTo(coord)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const osmNode = new OsmNode(id++, coord);
|
|
107
|
+
const node = new GraphNode(osmNode.coords, osmNode);
|
|
108
|
+
|
|
109
|
+
if (nodes.length !== 0) {
|
|
110
|
+
const prevNode = nodes[nodes.length - 1];
|
|
111
|
+
const edge = new GraphEdge(prevNode, node, level, osmWay);
|
|
112
|
+
edges.push(edge);
|
|
113
|
+
edgesWeights.push(prevNode.coords.distanceTo(osmNode));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
nodes.push(node);
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** @type {GraphItinerary<OsmElement>} */
|
|
122
|
+
const graphItinerary = new GraphItinerary();
|
|
123
|
+
graphItinerary.nodes = nodes;
|
|
124
|
+
graphItinerary.edges = edges;
|
|
125
|
+
graphItinerary.edgesWeights = edgesWeights;
|
|
126
|
+
graphItinerary.start = nodes[0].coords;
|
|
127
|
+
graphItinerary.end = nodes[nodes.length - 1].coords;
|
|
128
|
+
|
|
129
|
+
const points = nodes.map(node => node.coords);
|
|
130
|
+
const itinerary = Itinerary.fromOrderedCoordinates(points, from, to);
|
|
131
|
+
itinerary.legs[0].steps = StepsGeneration.fromGraphItinerary(graphItinerary);
|
|
132
|
+
itinerary.legs[0].steps.map((step, idx) => (step._idCoordsInLeg = idx));
|
|
133
|
+
|
|
134
|
+
// All legs have to be parsed before computing steps metadata
|
|
135
|
+
generateStepsMetadata(itinerary);
|
|
136
|
+
|
|
137
|
+
routerResponse.itineraries.push(itinerary);
|
|
138
|
+
|
|
139
|
+
return routerResponse;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default new DeutscheBahnRemoteRouter();
|
|
@@ -7,29 +7,30 @@ import { fileURLToPath } from 'url';
|
|
|
7
7
|
|
|
8
8
|
import { Coordinates, Level } from '@wemap/geo';
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import DeutscheBahnRemoteRouter from './DeutscheBahnRemoteRouter.js';
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import checkRouterResponseType from '../../model/RouterResponse.type.spec.js';
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
const { expect } = chai;
|
|
16
16
|
chai.use(chaiAlmost(0.1));
|
|
17
17
|
|
|
18
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const assetsPath = path.resolve(__dirname, '../../../assets');
|
|
19
20
|
|
|
20
|
-
describe('
|
|
21
|
+
describe('DeutscheBahnRemoteRouter - createRouterResponseFromJson', () => {
|
|
21
22
|
|
|
22
23
|
it('RouterResponse - 1', () => {
|
|
23
24
|
|
|
24
|
-
const filePath = path.resolve(
|
|
25
|
+
const filePath = path.resolve(assetsPath, 'itinerary-deutsche-bahn-1.json');
|
|
25
26
|
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
26
27
|
const json = JSON.parse(fileString);
|
|
27
28
|
|
|
28
29
|
const from = new Coordinates(52.5258473, 13.3683657, null, new Level(-10));
|
|
29
30
|
const to = new Coordinates(52.52499085853664, 13.369467296949914, null, new Level(0));
|
|
30
31
|
|
|
31
|
-
const routerResponse = createRouterResponseFromJson(json, from, to);
|
|
32
|
-
|
|
32
|
+
const routerResponse = DeutscheBahnRemoteRouter.createRouterResponseFromJson(json, from, to);
|
|
33
|
+
checkRouterResponseType(routerResponse);
|
|
33
34
|
|
|
34
35
|
expect(routerResponse.routerName).equal('deutsche-bahn');
|
|
35
36
|
expect(routerResponse.itineraries.length).equal(1);
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
import { Coordinates, Utils as GeoUtils } from '@wemap/geo';
|
|
3
|
+
import Logger from '@wemap/logger';
|
|
4
|
+
|
|
5
|
+
import Itinerary from '../../model/Itinerary.js';
|
|
6
|
+
import Leg from '../../model/Leg.js';
|
|
7
|
+
import RouterResponse from '../../model/RouterResponse.js';
|
|
8
|
+
import Step from '../../model/Step.js';
|
|
9
|
+
import { generateStepsMetadata, dateWithTimeZone } from '../RemoteRouterUtils.js';
|
|
10
|
+
import Constants from '../../Constants.js';
|
|
11
|
+
import RemoteRouter from '../RemoteRouter.js';
|
|
12
|
+
import RemoteRouterServerUnreachable from '../RemoteRouterServerUnreachable.js';
|
|
13
|
+
import IdfmRemoteRouterTokenError from './IdfmRemoteRouterTokenError.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* List of all modes supported by the API
|
|
17
|
+
* http://doc.navitia.io/#physical-mode
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const routingModeCorrespondance = new Map();
|
|
21
|
+
routingModeCorrespondance.set('Air', Constants.ROUTING_MODE.AIRPLANE);
|
|
22
|
+
routingModeCorrespondance.set('Boat', Constants.ROUTING_MODE.BOAT);
|
|
23
|
+
routingModeCorrespondance.set('Bus', Constants.ROUTING_MODE.BUS);
|
|
24
|
+
routingModeCorrespondance.set('BusRapidTransit', Constants.ROUTING_MODE.BUS);
|
|
25
|
+
routingModeCorrespondance.set('Coach', Constants.ROUTING_MODE.BUS);
|
|
26
|
+
routingModeCorrespondance.set('Ferry', Constants.ROUTING_MODE.FERRY);
|
|
27
|
+
routingModeCorrespondance.set('Funicular', Constants.ROUTING_MODE.FUNICULAR);
|
|
28
|
+
routingModeCorrespondance.set('LocalTrain', Constants.ROUTING_MODE.TRAIN);
|
|
29
|
+
routingModeCorrespondance.set('LongDistanceTrain', Constants.ROUTING_MODE.TRAIN);
|
|
30
|
+
routingModeCorrespondance.set('Metro', Constants.ROUTING_MODE.METRO);
|
|
31
|
+
routingModeCorrespondance.set('Métro', Constants.ROUTING_MODE.METRO);
|
|
32
|
+
routingModeCorrespondance.set('RailShuttle', Constants.ROUTING_MODE.TRAIN);
|
|
33
|
+
routingModeCorrespondance.set('RapidTransit', Constants.ROUTING_MODE.BUS);
|
|
34
|
+
routingModeCorrespondance.set('Shuttle', Constants.ROUTING_MODE.BUS);
|
|
35
|
+
routingModeCorrespondance.set('SuspendedCableCar', Constants.ROUTING_MODE.FUNICULAR);
|
|
36
|
+
routingModeCorrespondance.set('Taxi', Constants.ROUTING_MODE.TAXI);
|
|
37
|
+
routingModeCorrespondance.set('Train', Constants.ROUTING_MODE.TRAIN);
|
|
38
|
+
routingModeCorrespondance.set('RER', Constants.ROUTING_MODE.TRAIN);
|
|
39
|
+
routingModeCorrespondance.set('Tramway', Constants.ROUTING_MODE.TRAM);
|
|
40
|
+
routingModeCorrespondance.set('walking', Constants.ROUTING_MODE.WALK);
|
|
41
|
+
routingModeCorrespondance.set('bike', Constants.ROUTING_MODE.BIKE);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* List of transports modes
|
|
45
|
+
*/
|
|
46
|
+
const TRANSPORT_IDS = [
|
|
47
|
+
'physical_mode:Air',
|
|
48
|
+
'physical_mode:Boat',
|
|
49
|
+
'physical_mode:Bus',
|
|
50
|
+
'physical_mode:BusRapidTransit',
|
|
51
|
+
'physical_mode:Coach',
|
|
52
|
+
'physical_mode:Ferry',
|
|
53
|
+
'physical_mode:Funicular',
|
|
54
|
+
'physical_mode:LocalTrain',
|
|
55
|
+
'physical_mode:LongDistanceTrain',
|
|
56
|
+
'physical_mode:Metro',
|
|
57
|
+
'physical_mode:RailShuttle',
|
|
58
|
+
'physical_mode:RapidTransit',
|
|
59
|
+
'physical_mode:Shuttle',
|
|
60
|
+
'physical_mode:SuspendedCableCar',
|
|
61
|
+
'physical_mode:Taxi',
|
|
62
|
+
'physical_mode:Train',
|
|
63
|
+
'physical_mode:Tramway'
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const clientId = '539eec73-3bb5-4327-bb5e-a52672658592';
|
|
67
|
+
const clientSecret = '899f4bb9-f1d5-45d3-9f67-530827bb6734';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get last item of a given array
|
|
71
|
+
* @param {Array} array
|
|
72
|
+
* @returns {any}
|
|
73
|
+
*/
|
|
74
|
+
function last(array) {
|
|
75
|
+
return array[array.length - 1];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Singleton.
|
|
80
|
+
*/
|
|
81
|
+
class IdfmRemoteRouter extends RemoteRouter {
|
|
82
|
+
|
|
83
|
+
isLogged = false;
|
|
84
|
+
token = null;
|
|
85
|
+
expiresAt = null;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @override
|
|
89
|
+
*/
|
|
90
|
+
get rname() {
|
|
91
|
+
return 'idfm';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @override
|
|
96
|
+
* @throws {IdfmRemoteRouterTokenError}
|
|
97
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
98
|
+
*/
|
|
99
|
+
async getItineraries(endpointUrl, mode, waypoints) {
|
|
100
|
+
if (!this.canRequestService()) {
|
|
101
|
+
await this._connect();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const url = this.getURL(endpointUrl, mode, waypoints);
|
|
105
|
+
|
|
106
|
+
const res = await fetch(url, {
|
|
107
|
+
method: 'GET',
|
|
108
|
+
headers: { Authorization: 'Bearer ' + this.token }
|
|
109
|
+
});
|
|
110
|
+
if (res.status !== 200) {
|
|
111
|
+
throw new RemoteRouterServerUnreachable(this.rname, url);
|
|
112
|
+
}
|
|
113
|
+
const response = await res.json();
|
|
114
|
+
|
|
115
|
+
return this.createRouterResponseFromJson(response);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @throws {IdfmRemoteRouterTokenError}
|
|
120
|
+
*/
|
|
121
|
+
async _connect() {
|
|
122
|
+
|
|
123
|
+
const details = {
|
|
124
|
+
'grant_type': 'client_credentials',
|
|
125
|
+
'scope': 'read-data',
|
|
126
|
+
'client_id': clientId,
|
|
127
|
+
'client_secret': clientSecret
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const data = new URLSearchParams();
|
|
131
|
+
for (const property in details) {
|
|
132
|
+
if (details.hasOwnProperty(property)) {
|
|
133
|
+
const encodedKey = encodeURIComponent(property);
|
|
134
|
+
const encodedValue = encodeURIComponent(details[property]);
|
|
135
|
+
data.append(encodedKey, encodedValue);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const headers = new Headers({
|
|
140
|
+
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const res = await fetch('https://idfm.getwemap.com/api/oauth/token', {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
body: data,
|
|
146
|
+
headers
|
|
147
|
+
});
|
|
148
|
+
if (res.status !== 200) {
|
|
149
|
+
throw new IdfmRemoteRouterTokenError();
|
|
150
|
+
}
|
|
151
|
+
const response = await res.json();
|
|
152
|
+
|
|
153
|
+
if (response.access_token) {
|
|
154
|
+
this.token = response.access_token;
|
|
155
|
+
this.isLogged = true;
|
|
156
|
+
this.expiresAt = new Date(new Date().getTime() + response.expires_in * 1000);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
canRequestService() {
|
|
161
|
+
return this.token && this.expiresAt && this.expiresAt - new Date() > 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {string} endpointUrl
|
|
166
|
+
* @param {string} mode
|
|
167
|
+
* @param {Array<Coordinates>} waypoints
|
|
168
|
+
*/
|
|
169
|
+
getURL(endpointUrl, mode, waypoints) {
|
|
170
|
+
|
|
171
|
+
if (waypoints.length > 2) {
|
|
172
|
+
Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const fromPlace = `from=${waypoints[0].longitude};${waypoints[0].latitude}`;
|
|
176
|
+
const toPlace = `to=${waypoints[1].longitude};${waypoints[1].latitude}`;
|
|
177
|
+
|
|
178
|
+
let url = new URL(endpointUrl);
|
|
179
|
+
let { search } = url;
|
|
180
|
+
search = (search ? `${search}&` : '?') + `${fromPlace}&${toPlace}`;
|
|
181
|
+
|
|
182
|
+
let query = '';
|
|
183
|
+
switch (mode) {
|
|
184
|
+
case Constants.ROUTING_MODE.WALK:
|
|
185
|
+
query = this.getWalkingQuery();
|
|
186
|
+
break;
|
|
187
|
+
case Constants.ROUTING_MODE.BIKE:
|
|
188
|
+
query = this.getBikeQuery();
|
|
189
|
+
break;
|
|
190
|
+
case Constants.ROUTING_MODE.CAR:
|
|
191
|
+
query = this.getCarQuery();
|
|
192
|
+
break;
|
|
193
|
+
default:
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
url = `${url.origin}${url.pathname}${search}${query}`;
|
|
198
|
+
|
|
199
|
+
return url;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getCarQuery() {
|
|
203
|
+
const forbiddenTransport = TRANSPORT_IDS.map((id) => `forbidden_uris[]=${id}`).join('&');
|
|
204
|
+
const allowCar = 'first_section_mode[]=walking&first_section_mode[]=car&last_section_mode[]=walking&last_section_mode[]=car';
|
|
205
|
+
|
|
206
|
+
return `&${forbiddenTransport}&${allowCar}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getWalkingQuery() {
|
|
210
|
+
const forbiddenTransport = TRANSPORT_IDS.map((id) => `forbidden_uris[]=${id}`).join('&');
|
|
211
|
+
const allowWalking = 'first_section_mode[]=walking&last_section_mode[]=walking';
|
|
212
|
+
|
|
213
|
+
return `&${forbiddenTransport}&${allowWalking}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
getBikeQuery() {
|
|
217
|
+
const forbiddenTransport = TRANSPORT_IDS.map((id) => `forbidden_uris[]=${id}`).join('&');
|
|
218
|
+
const allowBike = 'first_section_mode[]=bike&last_section_mode[]=bike';
|
|
219
|
+
|
|
220
|
+
return `&${forbiddenTransport}&${allowBike}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @param {object} json
|
|
225
|
+
* @returns {Coordinates}
|
|
226
|
+
*/
|
|
227
|
+
jsonToCoordinates(json) {
|
|
228
|
+
return new Coordinates(Number(json.lat), Number(json.lon));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
getSectionCoords(section) {
|
|
232
|
+
const from = section.from.stop_point ? this.jsonToCoordinates(section.from.stop_point.coord) : this.jsonToCoordinates(section.from.address.coord);
|
|
233
|
+
const to = section.to.stop_point ? this.jsonToCoordinates(section.to.stop_point.coord) : this.jsonToCoordinates(section.to.address.coord);
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
from,
|
|
237
|
+
to
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Since the IDFM API does not provide coords for each step, we need to compute them
|
|
243
|
+
* We trim the coordinates of the leg with the distance of each step and keep the last result as the coords of the step
|
|
244
|
+
* @param {Leg} leg
|
|
245
|
+
*/
|
|
246
|
+
findStepsCoord(leg) {
|
|
247
|
+
const { steps, coords } = leg;
|
|
248
|
+
|
|
249
|
+
const duplicatedCoords = [...coords];
|
|
250
|
+
let previousStep = steps[0];
|
|
251
|
+
let accumulatedIndex = 0;
|
|
252
|
+
|
|
253
|
+
for (const [idx, step] of steps.entries()) {
|
|
254
|
+
let newCoords;
|
|
255
|
+
|
|
256
|
+
if (idx === 0) {
|
|
257
|
+
step._idCoordsInLeg = 0;
|
|
258
|
+
newCoords = coords[0];
|
|
259
|
+
} else if (idx === steps.length - 1) {
|
|
260
|
+
step._idCoordsInLeg = coords.length - 1;
|
|
261
|
+
newCoords = last(coords);
|
|
262
|
+
} else if (duplicatedCoords.length === 1) {
|
|
263
|
+
accumulatedIndex++;
|
|
264
|
+
|
|
265
|
+
step._idCoordsInLeg = accumulatedIndex;
|
|
266
|
+
|
|
267
|
+
newCoords = duplicatedCoords[0];
|
|
268
|
+
|
|
269
|
+
coords[step._idCoordsInLeg] = newCoords;
|
|
270
|
+
} else {
|
|
271
|
+
const result = GeoUtils.trimRoute(duplicatedCoords, duplicatedCoords[0], previousStep.distance);
|
|
272
|
+
accumulatedIndex += result.length - 1;
|
|
273
|
+
|
|
274
|
+
duplicatedCoords.splice(0, result.length - 1);
|
|
275
|
+
|
|
276
|
+
step._idCoordsInLeg = accumulatedIndex;
|
|
277
|
+
|
|
278
|
+
newCoords = last(result);
|
|
279
|
+
|
|
280
|
+
coords[step._idCoordsInLeg] = newCoords;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
step.coords = newCoords;
|
|
284
|
+
|
|
285
|
+
previousStep = step;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @param {string} stringDate (e.g. 20211117T104516)
|
|
291
|
+
* @returns {number}
|
|
292
|
+
*/
|
|
293
|
+
dateStringToTimestamp(stringDate, timeZone) {
|
|
294
|
+
const yearStr = stringDate.substr(0, 4);
|
|
295
|
+
const monthStr = stringDate.substr(4, 2);
|
|
296
|
+
const dayStr = stringDate.substr(6, 2);
|
|
297
|
+
const hoursStr = stringDate.substr(9, 2);
|
|
298
|
+
const minutesStr = stringDate.substr(11, 2);
|
|
299
|
+
const secondsStr = stringDate.substr(13, 2);
|
|
300
|
+
|
|
301
|
+
return dateWithTimeZone(
|
|
302
|
+
Number(yearStr),
|
|
303
|
+
Number(monthStr) - 1,
|
|
304
|
+
Number(dayStr),
|
|
305
|
+
Number(hoursStr),
|
|
306
|
+
Number(minutesStr),
|
|
307
|
+
Number(secondsStr),
|
|
308
|
+
timeZone
|
|
309
|
+
).getTime();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Generate multi itineraries from OTP JSON
|
|
314
|
+
* @param {object} json JSON file provided by OTP.
|
|
315
|
+
* @returns {?RouterResponse}
|
|
316
|
+
*/
|
|
317
|
+
createRouterResponseFromJson(json) {
|
|
318
|
+
|
|
319
|
+
if (!json || !json.journeys) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const routerResponse = new RouterResponse();
|
|
324
|
+
routerResponse.routerName = this.rname;
|
|
325
|
+
|
|
326
|
+
routerResponse.from = this.getSectionCoords(json.journeys[0].sections[0]).from;
|
|
327
|
+
routerResponse.to = this.getSectionCoords(last(json.journeys[0].sections)).to;
|
|
328
|
+
|
|
329
|
+
const timeZone = json.context.timezone;
|
|
330
|
+
|
|
331
|
+
for (const jsonItinerary of json.journeys) {
|
|
332
|
+
|
|
333
|
+
const itinerary = new Itinerary();
|
|
334
|
+
|
|
335
|
+
itinerary.duration = jsonItinerary.duration;
|
|
336
|
+
itinerary.startTime = this.dateStringToTimestamp(jsonItinerary.departure_date_time, timeZone);
|
|
337
|
+
itinerary.endTime = this.dateStringToTimestamp(jsonItinerary.arrival_date_time, timeZone);
|
|
338
|
+
itinerary.from = routerResponse.from;
|
|
339
|
+
itinerary.to = routerResponse.to;
|
|
340
|
+
itinerary.distance = 0;
|
|
341
|
+
|
|
342
|
+
routerResponse.itineraries.push(itinerary);
|
|
343
|
+
|
|
344
|
+
for (const jsonSection of jsonItinerary.sections) {
|
|
345
|
+
|
|
346
|
+
if (jsonSection.type === 'waiting' || jsonSection.type === 'transfer') {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const leg = new Leg();
|
|
351
|
+
let existingCoords = [];
|
|
352
|
+
const { from, to } = this.getSectionCoords(jsonSection);
|
|
353
|
+
|
|
354
|
+
leg.distance = 0;
|
|
355
|
+
leg.mode = routingModeCorrespondance.get(jsonSection.mode);
|
|
356
|
+
leg.duration = jsonSection.duration;
|
|
357
|
+
leg.startTime = this.dateStringToTimestamp(jsonSection.departure_date_time, timeZone);
|
|
358
|
+
leg.endTime = this.dateStringToTimestamp(jsonSection.arrival_date_time, timeZone);
|
|
359
|
+
|
|
360
|
+
leg.from = {
|
|
361
|
+
name: jsonSection.from.name,
|
|
362
|
+
coords: from
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
leg.to = {
|
|
366
|
+
name: jsonSection.to.name,
|
|
367
|
+
coords: to
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// A section can have multiple same coordinates, we need to remove them
|
|
371
|
+
leg.coords = jsonSection.geojson.coordinates.reduce((acc, [lon, lat]) => {
|
|
372
|
+
if (!existingCoords.includes(`${lon}-${lat}`)) {
|
|
373
|
+
existingCoords = existingCoords.concat(`${lon}-${lat}`);
|
|
374
|
+
acc.push(new Coordinates(lat, lon));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return acc;
|
|
378
|
+
}, []);
|
|
379
|
+
|
|
380
|
+
leg.steps = [];
|
|
381
|
+
|
|
382
|
+
if (jsonSection.path) {
|
|
383
|
+
for (const jsonPathLink of jsonSection.path) {
|
|
384
|
+
const step = new Step();
|
|
385
|
+
|
|
386
|
+
step.levelChange = null;
|
|
387
|
+
|
|
388
|
+
step.name = jsonPathLink.name;
|
|
389
|
+
step.distance = jsonPathLink.length;
|
|
390
|
+
|
|
391
|
+
leg.distance += step.distance;
|
|
392
|
+
leg.steps.push(step);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
this.findStepsCoord(leg);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (jsonSection.type === 'public_transport') {
|
|
399
|
+
leg.transportInfo = {
|
|
400
|
+
name: jsonSection.display_informations.code,
|
|
401
|
+
routeColor: jsonSection.display_informations.color,
|
|
402
|
+
routeTextColor: jsonSection.display_informations.text_color,
|
|
403
|
+
directionName: jsonSection.display_informations.direction
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
leg.mode = routingModeCorrespondance.get(jsonSection.display_informations.physical_mode);
|
|
407
|
+
|
|
408
|
+
const legStep = new Step();
|
|
409
|
+
legStep.coords = leg.coords[0];
|
|
410
|
+
legStep._idCoordsInLeg = 0;
|
|
411
|
+
legStep.name = leg.transportInfo.directionName;
|
|
412
|
+
legStep.levelChange = null;
|
|
413
|
+
legStep.distance = jsonSection.geojson.properties[0].length;
|
|
414
|
+
|
|
415
|
+
leg.steps = [legStep];
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
itinerary.distance += leg.distance;
|
|
419
|
+
|
|
420
|
+
itinerary.legs.push(leg);
|
|
421
|
+
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// All legs have to be parsed before computing steps metadata
|
|
425
|
+
generateStepsMetadata(itinerary);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return routerResponse;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export default new IdfmRemoteRouter();
|
|
@@ -6,25 +6,26 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
|
|
7
7
|
import { Coordinates } from '@wemap/geo';
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import IdfmRemoteRouter from './IdfmRemoteRouter.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('IdfmRouter - createRouterResponseFromJson', () => {
|
|
19
20
|
|
|
20
21
|
it('RouterResponse - 1', () => {
|
|
21
22
|
|
|
22
|
-
const filePath = path.resolve(
|
|
23
|
+
const filePath = path.resolve(assetsPath, 'itinerary-paris-idfm.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 = IdfmRemoteRouter.createRouterResponseFromJson(json);
|
|
28
|
+
checkRouterResponseType(routerResponse);
|
|
28
29
|
|
|
29
30
|
expect(routerResponse.routerName).equal('idfm');
|
|
30
31
|
expect(routerResponse.itineraries.length).equal(5);
|