@wemap/osm 5.2.0 → 5.4.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.
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/* eslint-disable max-depth */
|
|
2
|
+
/* eslint-disable max-statements */
|
|
3
|
+
import { Coordinates } from '@wemap/geo';
|
|
4
|
+
|
|
5
|
+
import Itinerary from '../Itinerary.js';
|
|
6
|
+
import Leg from '../Leg.js';
|
|
7
|
+
import RouterResponse from '../RouterResponse.js';
|
|
8
|
+
import Step from '../Step.js';
|
|
9
|
+
import { generateStepsMetadata } from '../Utils.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} json
|
|
13
|
+
* @returns {Coordinates}
|
|
14
|
+
*/
|
|
15
|
+
export function jsonToCoordinates(json) {
|
|
16
|
+
return new Coordinates(json.Lat, json.Long);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} jsonDate
|
|
21
|
+
* @returns {number}
|
|
22
|
+
*/
|
|
23
|
+
function jsonDateToTimestamp(jsonDate) {
|
|
24
|
+
return Date.parse(jsonDate);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} wktGeometry
|
|
29
|
+
* @returns {Coordinates[]}
|
|
30
|
+
*/
|
|
31
|
+
function parseWKTGeometry(wktGeometry) {
|
|
32
|
+
const tmpCoordsStr = wktGeometry.match(/LINESTRING \((.*)\)/i);
|
|
33
|
+
if (!tmpCoordsStr) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return tmpCoordsStr[1].split(',').map(str => {
|
|
37
|
+
const sp = str.trim().split(' ');
|
|
38
|
+
return new Coordinates(Number(sp[1]), Number(sp[0]));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} iso8601Duration
|
|
44
|
+
* @see https://stackoverflow.com/a/29153059/2239938
|
|
45
|
+
*/
|
|
46
|
+
function parseDuration(iso8601Duration) {
|
|
47
|
+
const iso8601DurationRegex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
|
|
48
|
+
|
|
49
|
+
var matches = iso8601Duration.match(iso8601DurationRegex);
|
|
50
|
+
|
|
51
|
+
// const sign = typeof matches[1] === 'undefined' ? '+' : '-',
|
|
52
|
+
const years = typeof matches[2] === 'undefined' ? 0 : Number(matches[2]);
|
|
53
|
+
const months = typeof matches[3] === 'undefined' ? 0 : Number(matches[3]);
|
|
54
|
+
const weeks = typeof matches[4] === 'undefined' ? 0 : Number(matches[4]);
|
|
55
|
+
const days = typeof matches[5] === 'undefined' ? 0 : Number(matches[5]);
|
|
56
|
+
const hours = typeof matches[6] === 'undefined' ? 0 : Number(matches[6]);
|
|
57
|
+
const minutes = typeof matches[7] === 'undefined' ? 0 : Number(matches[7]);
|
|
58
|
+
const seconds = typeof matches[8] === 'undefined' ? 0 : Number(matches[8]);
|
|
59
|
+
|
|
60
|
+
return seconds
|
|
61
|
+
+ minutes * 60
|
|
62
|
+
+ hours * 3600
|
|
63
|
+
+ days * 86400
|
|
64
|
+
+ weeks * (86400 * 7)
|
|
65
|
+
+ months * (86400 * 30)
|
|
66
|
+
+ years * (86400 * 365.25);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generate multi itineraries from Cityway JSON
|
|
71
|
+
* @param {object} json JSON file provided by Cityway.
|
|
72
|
+
* @returns {?RouterResponse}
|
|
73
|
+
* @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
|
|
74
|
+
*/
|
|
75
|
+
export function createRouterResponseFromJson(json) {
|
|
76
|
+
|
|
77
|
+
if (json.StatusCode !== 200 || !json.Data || !json.Data.length) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const routerResponse = new RouterResponse();
|
|
82
|
+
routerResponse.routerName = 'cityway';
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// For the moment, take the first of the Data array.
|
|
86
|
+
const jsonResponse = json.Data[0].response;
|
|
87
|
+
|
|
88
|
+
for (const trip of jsonResponse.trips.Trip) {
|
|
89
|
+
|
|
90
|
+
const itinerary = new Itinerary();
|
|
91
|
+
|
|
92
|
+
itinerary.duration = parseDuration(trip.Duration);
|
|
93
|
+
itinerary.startTime = jsonDateToTimestamp(trip.Departure.Time);
|
|
94
|
+
itinerary.from = jsonToCoordinates(trip.Departure.Site.Position);
|
|
95
|
+
itinerary.endTime = jsonDateToTimestamp(trip.Arrival.Time);
|
|
96
|
+
itinerary.to = jsonToCoordinates(trip.Arrival.Site.Position);
|
|
97
|
+
routerResponse.itineraries.push(itinerary);
|
|
98
|
+
|
|
99
|
+
for (const jsonSection of trip.sections.Section) {
|
|
100
|
+
|
|
101
|
+
const jsonLeg = jsonSection.Leg ? jsonSection.Leg : jsonSection.PTRide;
|
|
102
|
+
|
|
103
|
+
const leg = new Leg();
|
|
104
|
+
|
|
105
|
+
leg.mode = jsonLeg.TransportMode;
|
|
106
|
+
leg.duration = parseDuration(jsonLeg.Duration);
|
|
107
|
+
leg.startTime = jsonDateToTimestamp(jsonLeg.Departure.Time);
|
|
108
|
+
leg.endTime = jsonDateToTimestamp(jsonLeg.Arrival.Time);
|
|
109
|
+
leg.coords = [];
|
|
110
|
+
|
|
111
|
+
if (leg.mode === 'WALK') {
|
|
112
|
+
|
|
113
|
+
leg.from = {
|
|
114
|
+
name: jsonLeg.Departure.Site.Name,
|
|
115
|
+
coords: jsonToCoordinates(jsonLeg.Departure.Site.Position)
|
|
116
|
+
};
|
|
117
|
+
leg.to = {
|
|
118
|
+
name: jsonLeg.Arrival.Site.Name,
|
|
119
|
+
coords: jsonToCoordinates(jsonLeg.Arrival.Site.Position)
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
leg.steps = [];
|
|
123
|
+
for (const jsonPathLink of jsonLeg.pathLinks.PathLink) {
|
|
124
|
+
const step = new Step();
|
|
125
|
+
let stepCoords;
|
|
126
|
+
if (jsonPathLink.Geometry) {
|
|
127
|
+
stepCoords = parseWKTGeometry(jsonPathLink.Geometry);
|
|
128
|
+
} else {
|
|
129
|
+
stepCoords = [leg.from.coords, leg.to.coords];
|
|
130
|
+
}
|
|
131
|
+
step.coords = stepCoords[0];
|
|
132
|
+
step._idCoordsInLeg = leg.coords.length;
|
|
133
|
+
stepCoords.forEach((coords, idx) => {
|
|
134
|
+
if (
|
|
135
|
+
idx !== 0
|
|
136
|
+
|| leg.coords.length === 0
|
|
137
|
+
|| !leg.coords[leg.coords.length - 1].equalsTo(coords)
|
|
138
|
+
) {
|
|
139
|
+
leg.coords.push(coords);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
step.name = jsonPathLink.Departure.Site.Name;
|
|
145
|
+
step.levelChange = null;
|
|
146
|
+
|
|
147
|
+
step.distance = jsonPathLink.Distance;
|
|
148
|
+
|
|
149
|
+
leg.steps.push(step);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
} else if (leg.mode === 'BUS' || leg.mode === 'TRAM') {
|
|
153
|
+
|
|
154
|
+
leg.from = {
|
|
155
|
+
name: jsonLeg.Departure.StopPlace.Name,
|
|
156
|
+
coords: jsonToCoordinates(jsonLeg.Departure.StopPlace.Position)
|
|
157
|
+
};
|
|
158
|
+
leg.to = {
|
|
159
|
+
name: jsonLeg.Arrival.StopPlace.Name,
|
|
160
|
+
coords: jsonToCoordinates(jsonLeg.Arrival.StopPlace.Position)
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
leg.transportInfo = {
|
|
164
|
+
name: jsonLeg.Line.Number,
|
|
165
|
+
routeColor: jsonLeg.Line.Color,
|
|
166
|
+
routeTextColor: jsonLeg.Line.TextColor,
|
|
167
|
+
directionName: jsonLeg.Destination
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
for (const jsonStep of jsonLeg.steps.Step) {
|
|
171
|
+
const stepCoords = parseWKTGeometry(jsonStep.Geometry);
|
|
172
|
+
stepCoords.forEach((coords, idx) => {
|
|
173
|
+
if (
|
|
174
|
+
idx !== 0
|
|
175
|
+
|| leg.coords.length === 0
|
|
176
|
+
|| !leg.coords[leg.coords.length - 1].equalsTo(coords)
|
|
177
|
+
) {
|
|
178
|
+
leg.coords.push(coords);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const legStep = new Step();
|
|
184
|
+
legStep.coords = leg.coords[0];
|
|
185
|
+
legStep._idCoordsInLeg = 0;
|
|
186
|
+
legStep.name = jsonLeg.Line.Name;
|
|
187
|
+
legStep.levelChange = null;
|
|
188
|
+
legStep.distance = jsonLeg.Distance;
|
|
189
|
+
leg.steps = [legStep];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
leg.distance = leg.coords.reduce((acc, coords, idx, arr) => {
|
|
193
|
+
if (idx === 0) {
|
|
194
|
+
return acc;
|
|
195
|
+
}
|
|
196
|
+
return acc + arr[idx - 1].distanceTo(coords);
|
|
197
|
+
}, 0);
|
|
198
|
+
|
|
199
|
+
itinerary.legs.push(leg);
|
|
200
|
+
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
itinerary.distance = itinerary.coords.reduce((acc, coords, idx, arr) => {
|
|
204
|
+
if (idx === 0) {
|
|
205
|
+
return acc;
|
|
206
|
+
}
|
|
207
|
+
return acc + arr[idx - 1].distanceTo(coords);
|
|
208
|
+
}, 0);
|
|
209
|
+
|
|
210
|
+
// All legs have to be parsed before computing steps metadata
|
|
211
|
+
generateStepsMetadata(itinerary);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
routerResponse.from = routerResponse.itineraries[0].from;
|
|
215
|
+
routerResponse.to = routerResponse.itineraries[routerResponse.itineraries.length - 1].to;
|
|
216
|
+
|
|
217
|
+
return routerResponse;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
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 './CitywayUtils.js';
|
|
10
|
+
|
|
11
|
+
import { verifyRouterResponseData } from '../RouterResponse.type.spec.js';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const { expect } = chai;
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
describe('CitywayUtils - createRouterResponseFromJson', () => {
|
|
19
|
+
|
|
20
|
+
it('RouterResponse - 1', () => {
|
|
21
|
+
|
|
22
|
+
const filePath = path.resolve(__dirname, '../../../assets/itinerary-lehavre-cityway.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('cityway');
|
|
30
|
+
expect(routerResponse.itineraries.length).equal(3);
|
|
31
|
+
expect(routerResponse.from.equalsTo(new Coordinates(49.515093882362159, 0.093417496193663158))).true;
|
|
32
|
+
expect(routerResponse.to.equalsTo(new Coordinates(49.5067090188444, 0.16948421154178309))).true;
|
|
33
|
+
|
|
34
|
+
const itinerary1 = routerResponse.itineraries[0];
|
|
35
|
+
expect(itinerary1.distance).to.be.closeTo(6887, 1);
|
|
36
|
+
expect(itinerary1.duration).equal(2379);
|
|
37
|
+
expect(itinerary1.startTime).equal(1633446356000);
|
|
38
|
+
expect(itinerary1.endTime).equal(1633448735000);
|
|
39
|
+
expect(itinerary1.legs.length).equal(5);
|
|
40
|
+
|
|
41
|
+
const itinerary1leg1 = itinerary1.legs[0];
|
|
42
|
+
expect(itinerary1leg1.startTime).equal(1633446356000);
|
|
43
|
+
expect(itinerary1leg1.endTime).equal(1633446540000);
|
|
44
|
+
expect(itinerary1leg1.distance).to.be.closeTo(200.14, 0.1);
|
|
45
|
+
expect(itinerary1leg1.mode).equal('WALK');
|
|
46
|
+
expect(itinerary1leg1.transportInfo).is.null;
|
|
47
|
+
expect(itinerary1leg1.from.name).equal('RUE DU QUARTIER NEUF');
|
|
48
|
+
expect(itinerary1leg1.from.coords.equalsTo(new Coordinates(49.515093882362159, 0.093417496193663158))).true;
|
|
49
|
+
expect(itinerary1leg1.to.name).equal('T. Gautier');
|
|
50
|
+
expect(itinerary1leg1.to.coords.equalsTo(new Coordinates(49.5147550229, 0.0911025378))).true;
|
|
51
|
+
|
|
52
|
+
const itinerary1leg2 = itinerary1.legs[1];
|
|
53
|
+
expect(itinerary1leg2.mode).equal('BUS');
|
|
54
|
+
expect(itinerary1leg2.transportInfo).is.not.null;
|
|
55
|
+
expect(itinerary1leg2.transportInfo.name).equal('03');
|
|
56
|
+
expect(itinerary1leg2.transportInfo.routeColor).equal('3FA435');
|
|
57
|
+
expect(itinerary1leg2.transportInfo.routeTextColor).is.null;
|
|
58
|
+
expect(itinerary1leg2.transportInfo.directionName).equal('Graville');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
});
|
|
63
|
+
|