@wemap/routers 12.8.10 → 12.9.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/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "directory": "packages/routers"
13
13
  },
14
14
  "name": "@wemap/routers",
15
- "version": "12.8.10",
15
+ "version": "12.9.0",
16
16
  "bugs": {
17
17
  "url": "https://github.com/wemap/wemap-modules-js/issues"
18
18
  },
@@ -52,5 +52,5 @@
52
52
  },
53
53
  "./helpers/*": "./helpers/*"
54
54
  },
55
- "gitHead": "a9ac02840374bc97ddf8322f4f35d737ff49a379"
55
+ "gitHead": "3a52e658a5933592bcc44488f936c150638532b2"
56
56
  }
@@ -4,7 +4,7 @@ import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
 
7
- import { Coordinates } from '@wemap/geo';
7
+ import { Coordinates, Utils as GeoUtils } from '@wemap/geo';
8
8
  import { OsmParser } from '@wemap/osm';
9
9
 
10
10
  import ItineraryInfoManager, { ItineraryInfo } from './ItineraryInfoManager.js';
@@ -28,8 +28,8 @@ const load = (fileName: string) => {
28
28
  const graph = OsmGraphUtils.createGraphFromOsmModel(model);
29
29
  const osmRouter = new GraphRouter(graph);
30
30
 
31
- const from = model.getNodeByName('start')?.coords as Coordinates;
32
- const to = model.getNodeByName('end')?.coords as Coordinates;
31
+ const from = model.getNodeByName('origin')?.coords as Coordinates;
32
+ const to = model.getNodeByName('destination')?.coords as Coordinates;
33
33
 
34
34
  const itinerary = Itinerary.fromGraphRoute(osmRouter.calculateShortestPath(from, to).route());
35
35
  const iim = new ItineraryInfoManager();
@@ -47,7 +47,7 @@ describe('ItineraryInfoManager', () => {
47
47
 
48
48
  const { from, to, iim, model, itinerary } = load('itinerary-info-two-points.osm');
49
49
 
50
- // Projection on start
50
+ // Projection on origin
51
51
  const position1 = model.getNodeByName('position 1')?.coords as Coordinates;
52
52
  resItineraryInfo = iim.getInfo(position1);
53
53
  expect(resItineraryInfo).is.not.null;
@@ -56,7 +56,7 @@ describe('ItineraryInfoManager', () => {
56
56
  expect(itineraryInfo.projection.coords.equals(from)).is.true;
57
57
  expect(itineraryInfo.previousStep).is.null;
58
58
  expect(itineraryInfo.nextStep?.number).equals(1);
59
- expect(itineraryInfo.remainingDistance).almost.equals(itinerary.distance);
59
+ expect(itineraryInfo.remainingDistance).closeTo(8.29, 0.1);
60
60
  expect(itineraryInfo.remainingPercentage).almost.equals(1);
61
61
  expect(itineraryInfo.traveledDistance).almost.equals(0);
62
62
  expect(itineraryInfo.traveledPercentage).almost.equals(0);
@@ -70,13 +70,13 @@ describe('ItineraryInfoManager', () => {
70
70
  expect(itineraryInfo.projection.nearestElement).instanceOf(Edge);
71
71
  expect(itineraryInfo.previousStep?.number).equals(1);
72
72
  expect(itineraryInfo.nextStep).is.null;
73
- expect(itineraryInfo.remainingDistance).closeTo(itinerary.distance / 2, 0.1);
74
- expect(itineraryInfo.remainingPercentage).almost.equals(0.5);
75
- expect(itineraryInfo.traveledDistance).closeTo(itinerary.distance / 2, 0.1);
76
- expect(itineraryInfo.traveledPercentage).almost.equals(0.5);
73
+ expect(itineraryInfo.remainingDistance).closeTo(4.3, 0.1);
74
+ expect(itineraryInfo.remainingPercentage).closeTo(0.59, 0.01);
75
+ expect(itineraryInfo.traveledDistance).closeTo(3, 0.1);
76
+ expect(itineraryInfo.traveledPercentage).closeTo(0.41, 0.01);
77
77
  expect(itineraryInfo.leg).almost.equals(itinerary.legs[0]);
78
78
 
79
- // Projection on end
79
+ // Projection on destination
80
80
  const position3 = model.getNodeByName('position 3')?.coords as Coordinates;
81
81
  resItineraryInfo = iim.getInfo(position3);
82
82
  expect(resItineraryInfo).is.not.null;
@@ -85,19 +85,21 @@ describe('ItineraryInfoManager', () => {
85
85
  expect(itineraryInfo.projection.coords.equals(to)).is.true;
86
86
  expect(itineraryInfo.previousStep?.number).equals(1);
87
87
  expect(itineraryInfo.nextStep).is.null;
88
- expect(itineraryInfo.remainingDistance).almost.equals(0);
89
- expect(itineraryInfo.remainingPercentage).almost.equals(0);
90
- expect(itineraryInfo.traveledDistance).almost.equals(itinerary.distance);
91
- expect(itineraryInfo.traveledPercentage).almost.equals(1);
88
+ expect(itineraryInfo.remainingDistance).closeTo(1.65, 0.1);
89
+ expect(itineraryInfo.remainingPercentage).closeTo(0.21, 0.01);
90
+ expect(itineraryInfo.traveledDistance).closeTo(itinerary.distance, 0.1);
91
+ expect(itineraryInfo.traveledPercentage).closeTo(0.78, 0.01);
92
92
  expect(itineraryInfo.leg).almost.equals(itinerary.legs[0]);
93
93
 
94
94
  });
95
95
 
96
96
  it('Itinerary 2 points - from & to projection', () => {
97
97
 
98
- const { iim, model, itinerary } = load('itinerary-info-two-points-proj.osm');
98
+ const { iim, model, itinerary, to } = load('itinerary-info-two-points-proj.osm');
99
+ const porigin = model.getNodeByName('porigin')?.coords as Coordinates;
100
+ const pdest = model.getNodeByName('pdest')?.coords as Coordinates;
99
101
 
100
- // Projection on start
102
+ // Projection on origin
101
103
  const position1 = model.getNodeByName('position 1')?.coords as Coordinates;
102
104
  resItineraryInfo = iim.getInfo(position1);
103
105
  expect(resItineraryInfo).is.not.null;
@@ -105,7 +107,7 @@ describe('ItineraryInfoManager', () => {
105
107
  expect(itineraryInfo.projection.nearestElement).instanceOf(Vertex);
106
108
  expect(itineraryInfo.previousStep).is.null;
107
109
  expect(itineraryInfo.nextStep?.number).equals(1);
108
- expect(itineraryInfo.remainingDistance).almost.equals(itinerary.distance);
110
+ expect(itineraryInfo.remainingDistance).almost.equals(GeoUtils.calcDistance([position1, porigin, pdest]));
109
111
  expect(itineraryInfo.remainingPercentage).almost.equals(1);
110
112
  expect(itineraryInfo.traveledDistance).almost.equals(0);
111
113
  expect(itineraryInfo.traveledPercentage).almost.equals(0);
@@ -113,15 +115,16 @@ describe('ItineraryInfoManager', () => {
113
115
 
114
116
  // Projection on middle
115
117
  const position2 = model.getNodeByName('position 2')?.coords as Coordinates;
118
+ const p2 = model.getNodeByName('p2')?.coords as Coordinates;
116
119
  resItineraryInfo = iim.getInfo(position2);
117
120
  expect(resItineraryInfo).is.not.null;
118
121
  itineraryInfo = resItineraryInfo as ItineraryInfo;
119
122
  expect(itineraryInfo.projection.nearestElement).instanceOf(Edge);
120
123
  expect(itineraryInfo.previousStep?.number).equals(1);
121
124
  expect(itineraryInfo.nextStep?.number).equals(2);
122
- expect(itineraryInfo.remainingDistance).closeTo(3.4, 0.1);
125
+ expect(itineraryInfo.remainingDistance).closeTo(GeoUtils.calcDistance([position2, p2, pdest]), 0.1);
123
126
  expect(itineraryInfo.remainingPercentage).closeTo(3.4 / 4.78, 0.1);
124
- expect(itineraryInfo.traveledDistance).closeTo(1.38, 0.1);
127
+ expect(itineraryInfo.traveledDistance).closeTo(GeoUtils.calcDistance([porigin, p2]), 0.1);
125
128
  expect(itineraryInfo.traveledPercentage).closeTo(1.38 / 4.78, 0.1);
126
129
  expect(itineraryInfo.leg).almost.equals(itinerary.legs[0]);
127
130
 
@@ -133,10 +136,10 @@ describe('ItineraryInfoManager', () => {
133
136
  expect(itineraryInfo.projection.nearestElement).instanceOf(Vertex);
134
137
  expect(itineraryInfo.previousStep?.number).equals(1);
135
138
  expect(itineraryInfo.nextStep?.number).equals(2);
136
- expect(itineraryInfo.remainingDistance).almost.equals(0);
137
- expect(itineraryInfo.remainingPercentage).almost.equals(0);
138
- expect(itineraryInfo.traveledDistance).almost.equals(itinerary.distance);
139
- expect(itineraryInfo.traveledPercentage).almost.equals(1);
139
+ expect(itineraryInfo.remainingDistance).almost.equals(GeoUtils.calcDistance([position3, pdest]));
140
+ expect(itineraryInfo.remainingPercentage).closeTo(0.25, 0.01);
141
+ expect(itineraryInfo.traveledDistance).almost.equals(GeoUtils.calcDistance([porigin, pdest]));
142
+ expect(itineraryInfo.traveledPercentage).closeTo(0.75, 0.01);
140
143
  expect(itineraryInfo.leg).almost.equals(itinerary.legs[0]);
141
144
 
142
145
  });
@@ -162,8 +165,8 @@ describe('ItineraryInfoManager', () => {
162
165
  expect(itineraryInfo.projection.nearestElement).instanceOf(Vertex);
163
166
  expect(itineraryInfo.previousStep?.number).equals(14);
164
167
  expect(itineraryInfo.nextStep?.number).equals(15);
165
- expect(itineraryInfo.remainingDistance).almost.equals(56.095);
166
- expect(itineraryInfo.remainingPercentage).almost.equals(0.0155);
168
+ expect(itineraryInfo.remainingDistance).closeTo(56.9, 0.1);
169
+ expect(itineraryInfo.remainingPercentage).closeTo(0.0155, 0.01);
167
170
  expect(itineraryInfo.leg).almost.equals(itinerary.legs[2]);
168
171
 
169
172
  position = new Coordinates(45.1641691, 5.7413891);
@@ -173,8 +176,8 @@ describe('ItineraryInfoManager', () => {
173
176
  expect(itineraryInfo.projection.nearestElement).instanceOf(Edge);
174
177
  expect(itineraryInfo.nextStep?.number).equals(15);
175
178
  expect(itineraryInfo.previousStep?.number).equals(14);
176
- expect(itineraryInfo.remainingDistance).almost.equals(65.671);
177
- expect(itineraryInfo.remainingPercentage).almost.equals(0.018);
179
+ expect(itineraryInfo.remainingDistance).closeTo(66.4, 0.1);
180
+ expect(itineraryInfo.remainingPercentage).closeTo(0.018, 0.01);
178
181
  expect(itineraryInfo.leg).almost.equals(itinerary.legs[2]);
179
182
  });
180
183
  });
@@ -29,6 +29,7 @@ class ItineraryInfoManager {
29
29
  _coordsPreviousStep: (Step | null)[] = [];
30
30
  _coordsDistanceTraveled: number[] = [];
31
31
  _coordsLeg: Leg[] = [];
32
+ _itineraryDistanceWithoutProjections = 0;
32
33
 
33
34
  constructor(itinerary: Itinerary | null = null) {
34
35
  this.itinerary = itinerary;
@@ -53,6 +54,11 @@ class ItineraryInfoManager {
53
54
  this._coordsPreviousStep = new Array(itinerary.coords.length);
54
55
  this._coordsDistanceTraveled = new Array(itinerary.coords.length);
55
56
  this._coordsLeg = new Array(itinerary.coords.length);
57
+ const originProjectionDistance = this._itinerary.origin.distanceTo(this._itinerary.coords[0]);
58
+ const destinationProjectionDistance = this._itinerary.destination.distanceTo(this._itinerary.coords[this._itinerary.coords.length - 1]);
59
+ this._itineraryDistanceWithoutProjections = itinerary.distance
60
+ - originProjectionDistance
61
+ - destinationProjectionDistance;
56
62
 
57
63
  let stepId = 0;
58
64
  let previousStep: Step | null = null;
@@ -98,17 +104,31 @@ class ItineraryInfoManager {
98
104
  throw new Error('ItineraryInfoManager: could not find projection in itinerary (Node)');
99
105
  }
100
106
 
101
- const traveledDistance = this._coordsDistanceTraveled[idx];
102
- const remainingDistance = this._itinerary.distance - traveledDistance;
107
+ const traveledDistanceOnItinerary = this._coordsDistanceTraveled[idx];
108
+
109
+ // Traveled distance takes into account:
110
+ // - projection of the origin
111
+ const traveledDistance = traveledDistanceOnItinerary;
112
+
113
+ // Remaining distance takes into account:
114
+ // - projection of the current position
115
+ // - distance remaining on the itinerary
116
+ const remainingDistanceOnItinerary = this._itineraryDistanceWithoutProjections - traveledDistanceOnItinerary;
117
+ const remainingDistance = projection.distanceFromNearestElement + remainingDistanceOnItinerary;
118
+
119
+ const distanceForPercentage = traveledDistance + remainingDistance
120
+ const traveledPercentage = traveledDistance / distanceForPercentage;
121
+ const remainingPercentage = 1 - traveledPercentage;
122
+
103
123
  itineraryInfo = {
104
124
  nextStep: this._coordsNextStep[idx],
105
125
  previousStep: this._coordsPreviousStep[idx],
106
126
  projection: projection,
107
127
  leg: this._coordsLeg[idx],
108
- traveledDistance,
128
+ traveledDistance: traveledDistanceOnItinerary,
109
129
  remainingDistance,
110
- traveledPercentage: traveledDistance / this._itinerary.distance,
111
- remainingPercentage: remainingDistance / this._itinerary.distance
130
+ traveledPercentage,
131
+ remainingPercentage
112
132
  };
113
133
 
114
134
  } else if (projection.nearestElement instanceof Edge) {
@@ -127,9 +147,23 @@ class ItineraryInfoManager {
127
147
  idx--;
128
148
  }
129
149
 
130
- const traveledDistance = this._coordsDistanceTraveled[idx]
150
+ const traveledDistanceOnItinerary = this._coordsDistanceTraveled[idx]
131
151
  + projection.coords.distanceTo(firstNode);
132
- const remainingDistance = this._itinerary.distance - traveledDistance;
152
+
153
+ // Traveled distance takes into account:
154
+ // - distance traveled on the itinerary
155
+ const traveledDistance = traveledDistanceOnItinerary;
156
+
157
+ // Remaining distance takes into account:
158
+ // - projection of the current position
159
+ // - distance remaining on the itinerary
160
+ const remainingDistanceOnItinerary = this._itineraryDistanceWithoutProjections - traveledDistanceOnItinerary;
161
+ const remainingDistance = projection.distanceFromNearestElement + remainingDistanceOnItinerary
162
+
163
+ const distanceForPercentage = traveledDistance + remainingDistance
164
+ const traveledPercentage = traveledDistance / distanceForPercentage;
165
+ const remainingPercentage = 1 - traveledPercentage;
166
+
133
167
  itineraryInfo = {
134
168
  nextStep: this._coordsNextStep[idx + 1],
135
169
  previousStep: this._coordsPreviousStep[idx + 1],
@@ -137,8 +171,8 @@ class ItineraryInfoManager {
137
171
  leg: this._coordsLeg[idx + 1],
138
172
  traveledDistance,
139
173
  remainingDistance,
140
- traveledPercentage: traveledDistance / this._itinerary.distance,
141
- remainingPercentage: remainingDistance / this._itinerary.distance
174
+ traveledPercentage,
175
+ remainingPercentage
142
176
  };
143
177
 
144
178
  }
@@ -26,7 +26,7 @@ export class WemapMultiRoutingError extends RoutingError {
26
26
  }
27
27
 
28
28
  static notFound(mapName: string, details?: string) {
29
- return new WemapMultiRoutingError(StatusCode.NOT_FOUND, mapName, `Cannot found an itinerary in map ${mapName}. Details: ${details}`)
29
+ return new WemapMultiRoutingError(StatusCode.NOT_FOUND, mapName, `Cannot found an itinerary in map ${mapName}. Details: ${details || 'No details'}`)
30
30
  }
31
31
 
32
32
  }
@@ -39,7 +39,7 @@ export class RemoteRoutingError extends RoutingError {
39
39
  }
40
40
 
41
41
  static notFound(routerName: string, details?: string) {
42
- return new RemoteRoutingError(StatusCode.NOT_FOUND, routerName, `Cannot found an itinerary with ${routerName}. Details: ${details}`)
42
+ return new RemoteRoutingError(StatusCode.NOT_FOUND, routerName, `Cannot found an itinerary with ${routerName}. Details: ${details || 'No details'}`)
43
43
  }
44
44
 
45
45
  static missingApiKey(routerName: string, details?: string) {
@@ -37,7 +37,7 @@ class GraphRoute extends Graph {
37
37
  return new GraphRoute(start, end, graph.vertices, graph.edges, edgesWeights);
38
38
  }
39
39
 
40
- get hasRoute() { return Boolean(this.edges.length) }
40
+ get hasRoute() { return Boolean(this.vertices.length) }
41
41
  }
42
42
 
43
43
  export default GraphRoute;
@@ -61,9 +61,17 @@ export default class Itinerary {
61
61
  this.origin = origin;
62
62
  this.destination = destination;
63
63
  this.legs = legs;
64
- this.duration = typeof duration === 'number'
65
- ? duration
66
- : this.legs.reduce((dur, leg) => dur + leg.duration, 0);
64
+ if (typeof duration === 'number') {
65
+ this.duration = duration;
66
+ } else {
67
+ const firstCoords = this.legs[0].coords[0];
68
+ const lastLeg = this.legs[this.legs.length - 1];
69
+ const lastCoords = lastLeg.coords[lastLeg.coords.length - 1];
70
+ // Projection duration is calculated using walking mode
71
+ const originProjectionDuration = getDurationFromLength(this.origin.distanceTo(firstCoords));
72
+ const destinationProjectionDuration = getDurationFromLength(this.destination.distanceTo(lastCoords));
73
+ this.duration = this.legs.reduce((dur, leg) => dur + leg.duration, originProjectionDuration + destinationProjectionDuration);
74
+ }
67
75
  this.startTime = typeof startTime === 'number' ? startTime : null;
68
76
  this.endTime = typeof endTime === 'number' ? endTime : null;
69
77
 
@@ -131,7 +139,7 @@ export default class Itinerary {
131
139
 
132
140
  get distance() {
133
141
  if (this._distance === null) {
134
- this._distance = GeoUtils.calcDistance(this.coords);
142
+ this._distance = GeoUtils.calcDistance([this.origin, ...this.coords, this.destination]);
135
143
  }
136
144
  return this._distance;
137
145
 
@@ -144,19 +152,10 @@ export default class Itinerary {
144
152
  }
145
153
 
146
154
  static fromItineraries(...itineraries: Itinerary[]) {
147
- let duration = 0;
148
- const legs: Leg[] = [];
149
-
150
- itineraries.forEach(_itinerary => {
151
- duration += _itinerary.duration;
152
- legs.push(..._itinerary.legs);
153
- });
154
-
155
155
  return new Itinerary({
156
156
  origin: itineraries[0].origin,
157
157
  destination: itineraries[itineraries.length - 1].destination,
158
- duration,
159
- legs
158
+ legs: itineraries.map(itinerary => itinerary.legs).flat(),
160
159
  });
161
160
  }
162
161
 
@@ -257,7 +256,6 @@ export default class Itinerary {
257
256
  return new Itinerary({
258
257
  origin: graphRoute.start,
259
258
  destination: graphRoute.end,
260
- duration: leg.duration,
261
259
  legs: [leg]
262
260
  });
263
261
  }
@@ -31,7 +31,7 @@ describe('CitywayRemoteRouter - parseResponse', () => {
31
31
  const itinerary1 = itineraries[0];
32
32
  expect(itinerary1.origin.equals(new Coordinates(49.515093882362159, 0.093417496193663158))).true;
33
33
  expect(itinerary1.destination.equals(new Coordinates(49.5067090188444, 0.16948421154178309))).true;
34
- expect(itinerary1.distance).to.be.closeTo(6887, 1);
34
+ expect(itinerary1.distance).to.be.closeTo(6919, 1);
35
35
  expect(itinerary1.duration).equal(2379);
36
36
  expect(itinerary1.transitMode).equal('BUS');
37
37
  // Do not work because of the input time format
@@ -36,8 +36,8 @@ describe('DeutscheBahnRemoteRouter - parseResponse', () => {
36
36
  expect(itinerary1.origin.equals(origin)).true;
37
37
  expect(itinerary1.destination.equals(destination)).true;
38
38
  expect(itinerary1.coords.length).equal(77);
39
- expect(itinerary1.distance).closeTo(129.6, 0.1);
40
- expect(itinerary1.duration).almost.equal(93.305);
39
+ expect(itinerary1.distance).closeTo(130, 1);
40
+ expect(itinerary1.duration).closeTo(94, 1);
41
41
  expect(itinerary1.transitMode).equal('WALK');
42
42
 
43
43
  const itinerary1leg1 = itinerary1.legs[0];
@@ -31,7 +31,7 @@ describe('GeoveloRouter - parseResponse', () => {
31
31
  const itinerary1 = itineraries[0];
32
32
  expect(itinerary1.origin.equals(new Coordinates(43.596949, 3.877772))).true;
33
33
  expect(itinerary1.destination.equals(new Coordinates(43.609609, 3.914684))).true;
34
- expect(itinerary1.distance).to.be.closeTo(4029, 1);
34
+ expect(itinerary1.distance).to.be.closeTo(4031, 1);
35
35
  expect(itinerary1.duration).equal(1053);
36
36
  expect(itinerary1.transitMode).equal('BIKE');
37
37
  // Do not work because of the input time format
@@ -37,7 +37,7 @@ describe('OsrmRemoteRouter - parseResponse', () => {
37
37
  const itinerary1 = itineraries[0];
38
38
  expect(itinerary1.origin.equals(origin)).true;
39
39
  expect(itinerary1.destination.equals(destination)).true;
40
- expect(itinerary1.distance).closeTo(400, 1);
40
+ expect(itinerary1.distance).closeTo(429, 1);
41
41
  expect(itinerary1.duration).equal(287.8);
42
42
  expect(itinerary1.startTime).is.null;
43
43
  expect(itinerary1.endTime).is.null;
@@ -196,7 +196,8 @@ export default class CustomGraphMap {
196
196
 
197
197
  getRouteInsideMap(start: Coordinates, end: Coordinates, options: GraphRouterOptions) {
198
198
  // Call the Wemap router to get the shortest path
199
- return this.router.calculateShortestPath(start, end, options).route();
199
+ const route = this.router.calculateShortestPath(start, end, options).route();
200
+ return route.hasRoute ? route : null;
200
201
  }
201
202
 
202
203
  getTripInsideMap(waypoints: Coordinates[], options: GraphRouterOptions) {