@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
+