@wemap/geo 0.2.1 → 0.3.1
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 +5 -4
- package/src/coordinates/Level.js +150 -36
- package/src/coordinates/Level.spec.js +164 -0
- package/src/coordinates/WGS84.js +17 -35
- package/src/coordinates/WGS84UserPosition.js +13 -6
- package/src/graph/Edge.js +8 -3
- package/src/graph/GraphRouter.js +1 -5
- package/src/graph/GraphRouter.spec.js +72 -0
- package/src/graph/Itinerary.js +294 -22
- package/src/graph/Itinerary.spec.js +168 -0
- package/src/graph/MapMatching.js +10 -1
- package/src/graph/MapMatching.spec.js +113 -57
- package/src/graph/Step.js +57 -0
- package/tests/CommonTest.js +57 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
|
|
3
|
+
import GraphRouter from './GraphRouter';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
network, nodes, itineraryStart, itineraryEnd
|
|
7
|
+
} from '../../tests/CommonTest';
|
|
8
|
+
|
|
9
|
+
describe('GraphRouter', () => {
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const router = new GraphRouter(network);
|
|
13
|
+
let itinerary;
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
it('network did not change', () => {
|
|
17
|
+
const nodesBefore = network.nodes.slice(0);
|
|
18
|
+
const edgesBefore = network.edges.slice(0);
|
|
19
|
+
|
|
20
|
+
itinerary = router.getShortestPath(itineraryStart, itineraryEnd);
|
|
21
|
+
|
|
22
|
+
const isEdgeInArray = (edgeArray, edge) => edgeArray.find(_edge =>
|
|
23
|
+
edge.node1 === _edge.node1
|
|
24
|
+
&& edge.node2 === _edge.node2
|
|
25
|
+
&& edge.level === _edge.level
|
|
26
|
+
&& edge.data === _edge.data
|
|
27
|
+
);
|
|
28
|
+
const areEdgeArrayEquals = (edgeArray1, edgeArray2) => {
|
|
29
|
+
if (edgeArray1.length !== edgeArray2.length) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
for (let j = 0; j < edgeArray1.length; j++) {
|
|
33
|
+
if (!isEdgeInArray(edgeArray2, edgeArray1[j])) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (let j = 0; j < edgeArray2.length; j++) {
|
|
38
|
+
if (!isEdgeInArray(edgeArray1, edgeArray2[j])) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
expect(network.nodes.length).equals(nodesBefore.length);
|
|
46
|
+
for (let i = 0; i < nodesBefore.length; i++) {
|
|
47
|
+
const node = network.nodes[i];
|
|
48
|
+
const nodeBefore = nodesBefore[i];
|
|
49
|
+
expect(nodeBefore).equals(node);
|
|
50
|
+
expect(areEdgeArrayEquals(node.edges, nodeBefore.edges)).true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
expect(areEdgeArrayEquals(network.edges, edgesBefore)).true;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('router returns shortest path', () => {
|
|
57
|
+
|
|
58
|
+
expect(itinerary.nodes.length).equal(11);
|
|
59
|
+
|
|
60
|
+
for (let i = 1; i <= 10; i++) {
|
|
61
|
+
expect(itinerary.nodes[i].data).equal(nodes[i + 6].data);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('no route found', () => {
|
|
67
|
+
const itineraryWithoutStairs = router.getShortestPath(itineraryStart, itineraryEnd,
|
|
68
|
+
edge => !edge.data || !edge.data.hasOwnProperty('stairs') || !edge.data.stairs
|
|
69
|
+
);
|
|
70
|
+
expect(itineraryWithoutStairs).is.undefined;
|
|
71
|
+
});
|
|
72
|
+
});
|
package/src/graph/Itinerary.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
2
|
import { WGS84 } from '@wemap/geo';
|
|
3
3
|
|
|
4
4
|
import Network from './Network';
|
|
5
5
|
import Edge from './Edge';
|
|
6
6
|
import Node from './Node';
|
|
7
|
+
import Step from './Step';
|
|
8
|
+
import {
|
|
9
|
+
diffAngle, deg2rad
|
|
10
|
+
} from '@wemap/maths';
|
|
11
|
+
import MapMatching from './MapMatching';
|
|
12
|
+
|
|
13
|
+
const SKIP_STEP_ANGLE_MAX = deg2rad(20);
|
|
7
14
|
|
|
8
15
|
/**
|
|
9
16
|
* Main attributes are:
|
|
@@ -19,15 +26,63 @@ class Itinerary extends Network {
|
|
|
19
26
|
super();
|
|
20
27
|
this.start = null;
|
|
21
28
|
this.end = null;
|
|
22
|
-
this.
|
|
29
|
+
this._steps = null;
|
|
30
|
+
this._length = 0;
|
|
31
|
+
|
|
32
|
+
// This array is for computational time gain only
|
|
33
|
+
// It helps to know if this.edges is in the same direction than the current itinerary
|
|
34
|
+
// true = opposite direction
|
|
35
|
+
// false = same direction
|
|
36
|
+
// if this.edgeDirectionReversed[i] is true, this.edges[i].node2 is before
|
|
37
|
+
// this.edges[i].node1 following the current itinerary
|
|
38
|
+
this._edgesDirectionReversed = [];
|
|
39
|
+
|
|
40
|
+
// This array is for computational time gain only
|
|
41
|
+
// It helps to find next and previous steps by storing next steps indexes for each node
|
|
42
|
+
// The size of this array is the same than this.nodes
|
|
43
|
+
this._nextStepsIndexes = null;
|
|
44
|
+
|
|
45
|
+
this.mapMatching = new MapMatching(this);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get route length
|
|
50
|
+
*/
|
|
51
|
+
get length() {
|
|
52
|
+
if (!this._length) {
|
|
53
|
+
this._length = this.edges.reduce((acc, edge) => acc + edge.length, 0);
|
|
54
|
+
}
|
|
55
|
+
return this._length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get route duration with default speed
|
|
60
|
+
*/
|
|
61
|
+
get duration() {
|
|
62
|
+
return this.getDuration();
|
|
23
63
|
}
|
|
24
64
|
|
|
25
65
|
/**
|
|
26
66
|
* Get route duration
|
|
27
67
|
* @param {Number} speed in km/h
|
|
28
68
|
*/
|
|
29
|
-
getDuration(speed
|
|
30
|
-
return (
|
|
69
|
+
getDuration(speed) {
|
|
70
|
+
return Itinerary.getDurationFromLength(this.length, speed);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get route duration
|
|
75
|
+
* @param {Number} speed in km/h
|
|
76
|
+
*/
|
|
77
|
+
static getDurationFromLength(length, speed = 4) {
|
|
78
|
+
return length / (speed * 1000 / 3600);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get steps() {
|
|
82
|
+
if (!this._steps) {
|
|
83
|
+
this.generateSteps();
|
|
84
|
+
}
|
|
85
|
+
return this._steps;
|
|
31
86
|
}
|
|
32
87
|
|
|
33
88
|
/**
|
|
@@ -51,6 +106,147 @@ class Itinerary extends Network {
|
|
|
51
106
|
return null;
|
|
52
107
|
}
|
|
53
108
|
|
|
109
|
+
getNextNode(graphElement) {
|
|
110
|
+
if (graphElement instanceof Edge) {
|
|
111
|
+
const indexOfEdge = this.edges.indexOf(graphElement);
|
|
112
|
+
if (indexOfEdge !== -1) {
|
|
113
|
+
return this.nodes[indexOfEdge + 1];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (graphElement instanceof Node) {
|
|
118
|
+
const indexOfNode = this.nodes.indexOf(graphElement);
|
|
119
|
+
if (indexOfNode !== -1 && indexOfNode !== this.nodes.length - 1) {
|
|
120
|
+
return this.nodes[indexOfNode + 1];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getNextEdge(graphElement) {
|
|
127
|
+
if (graphElement instanceof Edge) {
|
|
128
|
+
const indexOfEdge = this.edges.indexOf(graphElement);
|
|
129
|
+
if (indexOfEdge !== -1 && indexOfEdge !== this.edges.length - 1) {
|
|
130
|
+
return this.edges[indexOfEdge + 1];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (graphElement instanceof Node) {
|
|
135
|
+
const indexOfNode = this.nodes.indexOf(graphElement);
|
|
136
|
+
if (indexOfNode !== -1 && indexOfNode !== this.edges.length) {
|
|
137
|
+
return this.edges[indexOfNode];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getNextStep(graphElement) {
|
|
144
|
+
|
|
145
|
+
if (graphElement instanceof Edge) {
|
|
146
|
+
const indexOfEdge = this.edges.indexOf(graphElement);
|
|
147
|
+
if (indexOfEdge !== -1) {
|
|
148
|
+
return this.getNextStepFromEdgeId(indexOfEdge);
|
|
149
|
+
}
|
|
150
|
+
} else if (graphElement instanceof Node) {
|
|
151
|
+
const indexOfNode = this.nodes.indexOf(graphElement);
|
|
152
|
+
if (indexOfNode !== -1) {
|
|
153
|
+
return this.getNextStepFromNodeId(indexOfNode);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
getNextStepFromEdgeId(edgeId) {
|
|
160
|
+
return this.steps[this._nextStepsIndexes[edgeId + 1]];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getNextStepFromNodeId(nodeId) {
|
|
164
|
+
return this.steps[this._nextStepsIndexes[nodeId]];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
getPreviousStep(graphElement) {
|
|
168
|
+
|
|
169
|
+
if (graphElement instanceof Edge) {
|
|
170
|
+
const indexOfEdge = this.edges.indexOf(graphElement);
|
|
171
|
+
if (indexOfEdge !== -1) {
|
|
172
|
+
return this.getPreviousStepFromEdgeId(indexOfEdge);
|
|
173
|
+
}
|
|
174
|
+
} else if (graphElement instanceof Node) {
|
|
175
|
+
const indexOfNode = this.nodes.indexOf(graphElement);
|
|
176
|
+
if (indexOfNode !== -1) {
|
|
177
|
+
return this.getPreviousStepFromNodeId(indexOfNode);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
getPreviousStepFromEdgeId(edgeId) {
|
|
184
|
+
const previousStepId = this._nextStepsIndexes[edgeId + 1] - 1;
|
|
185
|
+
if (previousStepId !== -1) {
|
|
186
|
+
return this.steps[previousStepId];
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getPreviousStepFromNodeId(nodeId) {
|
|
192
|
+
const previousStepId = this._nextStepsIndexes[nodeId] - 1;
|
|
193
|
+
if (previousStepId !== -1) {
|
|
194
|
+
return this.steps[previousStepId];
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
getInfo(position) {
|
|
200
|
+
|
|
201
|
+
if (!(position) instanceof WGS84) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const projection = this.mapMatching.getProjection(position);
|
|
206
|
+
if (!projection) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const totalDistance = this.length;
|
|
211
|
+
let traveledDistance = 0;
|
|
212
|
+
let nextStep, previousStep;
|
|
213
|
+
|
|
214
|
+
if (projection.nearestElement instanceof Node) {
|
|
215
|
+
|
|
216
|
+
let currentNodeIndex = 0;
|
|
217
|
+
while (this.nodes[currentNodeIndex] !== projection.nearestElement) {
|
|
218
|
+
traveledDistance += this.edges[currentNodeIndex].length;
|
|
219
|
+
currentNodeIndex++;
|
|
220
|
+
}
|
|
221
|
+
nextStep = this.getNextStepFromNodeId(currentNodeIndex);
|
|
222
|
+
previousStep = this.getPreviousStepFromNodeId(currentNodeIndex);
|
|
223
|
+
|
|
224
|
+
} else if (projection.nearestElement instanceof Edge) {
|
|
225
|
+
|
|
226
|
+
let currentEdgeIndex = 0;
|
|
227
|
+
while (this.edges[currentEdgeIndex] !== projection.nearestElement) {
|
|
228
|
+
traveledDistance += this.edges[currentEdgeIndex].length;
|
|
229
|
+
currentEdgeIndex++;
|
|
230
|
+
}
|
|
231
|
+
traveledDistance += this.nodes[currentEdgeIndex].coords
|
|
232
|
+
.distanceTo(projection.projection);
|
|
233
|
+
nextStep = this.getNextStepFromEdgeId(currentEdgeIndex);
|
|
234
|
+
previousStep = this.getPreviousStepFromEdgeId(currentEdgeIndex);
|
|
235
|
+
|
|
236
|
+
} else {
|
|
237
|
+
throw new Error('No projection found');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
nextStep,
|
|
242
|
+
previousStep,
|
|
243
|
+
projection,
|
|
244
|
+
traveledDistance,
|
|
245
|
+
traveledPercentage: traveledDistance / totalDistance,
|
|
246
|
+
remainingDistance: totalDistance - traveledDistance,
|
|
247
|
+
remainingPercentage: 1 - traveledDistance / totalDistance
|
|
248
|
+
};
|
|
249
|
+
}
|
|
54
250
|
|
|
55
251
|
static fromNetworkNodes(nodes, start, end) {
|
|
56
252
|
|
|
@@ -59,32 +255,26 @@ class Itinerary extends Network {
|
|
|
59
255
|
*/
|
|
60
256
|
|
|
61
257
|
const itinerary = new Itinerary();
|
|
62
|
-
itinerary.
|
|
258
|
+
itinerary._length = 0;
|
|
63
259
|
itinerary.start = start;
|
|
64
260
|
itinerary.end = end;
|
|
65
261
|
|
|
66
|
-
let nextItineraryNode;
|
|
67
|
-
|
|
68
262
|
for (let i = 0; i < nodes.length; i++) {
|
|
69
263
|
|
|
70
264
|
const networkNode = nodes[i];
|
|
71
|
-
const itineraryNode = nextItineraryNode ? nextItineraryNode : networkNode.clone();
|
|
72
265
|
|
|
73
|
-
itinerary.nodes.push(
|
|
266
|
+
itinerary.nodes.push(networkNode);
|
|
74
267
|
|
|
75
268
|
if (i === nodes.length - 1) {
|
|
76
269
|
break;
|
|
77
270
|
}
|
|
78
|
-
const nextNetworkNode = nodes[i + 1];
|
|
79
|
-
nextItineraryNode = nextNetworkNode.clone();
|
|
80
271
|
|
|
272
|
+
const nextNetworkNode = nodes[i + 1];
|
|
81
273
|
const networkEdge = Network.getEdgeByNodes(networkNode.edges, networkNode, nextNetworkNode);
|
|
82
|
-
|
|
83
|
-
itineraryNode.edges.push(itineraryEdge);
|
|
84
|
-
nextItineraryNode.edges.push(itineraryEdge);
|
|
274
|
+
itinerary._edgesDirectionReversed.push(networkNode !== networkEdge.node1);
|
|
85
275
|
|
|
86
|
-
itinerary.edges.push(
|
|
87
|
-
itinerary.
|
|
276
|
+
itinerary.edges.push(networkEdge);
|
|
277
|
+
itinerary._length += networkEdge.length;
|
|
88
278
|
}
|
|
89
279
|
|
|
90
280
|
return itinerary;
|
|
@@ -97,7 +287,7 @@ class Itinerary extends Network {
|
|
|
97
287
|
*/
|
|
98
288
|
static fromOrderedPointsArray(points, start, end, lngLatFormat) {
|
|
99
289
|
const route = new Itinerary();
|
|
100
|
-
route.
|
|
290
|
+
route._length = 0;
|
|
101
291
|
route.start = new WGS84(start[lngLatFormat ? 1 : 0],
|
|
102
292
|
start[lngLatFormat ? 0 : 1]);
|
|
103
293
|
route.end = new WGS84(end[lngLatFormat ? 1 : 0],
|
|
@@ -114,12 +304,9 @@ class Itinerary extends Network {
|
|
|
114
304
|
|
|
115
305
|
if (lastPoint) {
|
|
116
306
|
const edge = new Edge(lastPoint, currentPoint);
|
|
117
|
-
|
|
307
|
+
route._edgesDirectionReversed.push(false);
|
|
118
308
|
route.edges.push(edge);
|
|
119
|
-
route.
|
|
120
|
-
|
|
121
|
-
currentPoint.edges.push(edge);
|
|
122
|
-
lastPoint.edges.push(edge);
|
|
309
|
+
route._length += edge.length;
|
|
123
310
|
}
|
|
124
311
|
|
|
125
312
|
route.nodes.push(currentPoint);
|
|
@@ -129,5 +316,90 @@ class Itinerary extends Network {
|
|
|
129
316
|
return route;
|
|
130
317
|
}
|
|
131
318
|
|
|
319
|
+
|
|
320
|
+
generateSteps() {
|
|
321
|
+
|
|
322
|
+
this._steps = [];
|
|
323
|
+
this._nextStepsIndexes = [];
|
|
324
|
+
|
|
325
|
+
let currentStep, previousStep;
|
|
326
|
+
let previousBearing = this.start.bearingTo(this.nodes[0].coords);
|
|
327
|
+
|
|
328
|
+
for (let i = 0; i < this.nodes.length - 1; i++) {
|
|
329
|
+
|
|
330
|
+
const node = this.nodes[i];
|
|
331
|
+
const nextNode = this.nodes[i + 1];
|
|
332
|
+
const edge = this.edges[i];
|
|
333
|
+
|
|
334
|
+
const { length } = edge;
|
|
335
|
+
const bearing = edge.bearing + (edge.node1 !== node ? Math.PI : 0);
|
|
336
|
+
const angle = diffAngle(previousBearing, bearing + Math.PI);
|
|
337
|
+
|
|
338
|
+
// If it is not the first iteration
|
|
339
|
+
if (currentStep) {
|
|
340
|
+
currentStep.nodes.push(node);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!currentStep || Math.abs(diffAngle(Math.PI, angle)) >= SKIP_STEP_ANGLE_MAX) {
|
|
344
|
+
|
|
345
|
+
previousStep = currentStep;
|
|
346
|
+
|
|
347
|
+
currentStep = new Step();
|
|
348
|
+
currentStep.number = this._steps.length + 1;
|
|
349
|
+
currentStep.angle = angle;
|
|
350
|
+
currentStep.previousBearing = previousBearing;
|
|
351
|
+
currentStep.nextBearing = bearing;
|
|
352
|
+
currentStep.nodes.push(node);
|
|
353
|
+
this._steps.push(currentStep);
|
|
354
|
+
|
|
355
|
+
if (!previousStep) {
|
|
356
|
+
currentStep.firstStep = true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const indexOfCurrentStep = this._steps.length - 1;
|
|
360
|
+
const numberOfNodesToFill = previousStep ? previousStep.nodes.length - 1 : 1;
|
|
361
|
+
for (let j = 0; j < numberOfNodesToFill; j++) {
|
|
362
|
+
this._nextStepsIndexes.push(indexOfCurrentStep);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
currentStep.edges.push(edge);
|
|
367
|
+
currentStep._length += length;
|
|
368
|
+
|
|
369
|
+
if (i === this.nodes.length - 2) {
|
|
370
|
+
currentStep.nodes.push(nextNode);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
previousBearing = bearing;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const lastNode = this.nodes[this.nodes.length - 1];
|
|
377
|
+
if (lastNode.coords !== this.end) {
|
|
378
|
+
|
|
379
|
+
const lastStep = new Step();
|
|
380
|
+
lastStep.number = this._steps.length + 1;
|
|
381
|
+
lastStep._length = 0;
|
|
382
|
+
lastStep.nextBearing = lastNode.coords.bearingTo(this.end);
|
|
383
|
+
lastStep.previousBearing = previousBearing;
|
|
384
|
+
lastStep.lastStep = true;
|
|
385
|
+
lastStep.angle = diffAngle(lastStep.previousBearing, lastStep.nextBearing + Math.PI);
|
|
386
|
+
lastStep.nodes.push(lastNode);
|
|
387
|
+
this._steps.push(lastStep);
|
|
388
|
+
|
|
389
|
+
if (!currentStep) {
|
|
390
|
+
lastStep.firstStep = true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const indexOfCurrentStep = this._steps.length - 1;
|
|
394
|
+
const numberOfNodesToFill = currentStep ? currentStep.nodes.length - 1 : 1;
|
|
395
|
+
for (let j = 0; j < numberOfNodesToFill; j++) {
|
|
396
|
+
this._nextStepsIndexes.push(indexOfCurrentStep);
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
currentStep.lastStep = true;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
}
|
|
403
|
+
|
|
132
404
|
}
|
|
133
405
|
export default Itinerary;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
|
|
4
|
+
import GraphRouter from './GraphRouter';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
network, nodes, itineraryStart, itineraryEnd, edges
|
|
8
|
+
} from '../../tests/CommonTest';
|
|
9
|
+
import Level from '../coordinates/Level';
|
|
10
|
+
import WGS84 from '../coordinates/WGS84';
|
|
11
|
+
|
|
12
|
+
describe('Itinerary', () => {
|
|
13
|
+
|
|
14
|
+
const router = new GraphRouter(network);
|
|
15
|
+
const itinerary = router.getShortestPath(itineraryStart, itineraryEnd);
|
|
16
|
+
|
|
17
|
+
it('is readable', () => {
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < itinerary.nodes.length; i++) {
|
|
20
|
+
const node = itinerary.nodes[i];
|
|
21
|
+
|
|
22
|
+
if (i !== itinerary.nodes.length - 1) {
|
|
23
|
+
|
|
24
|
+
const reversed = itinerary._edgesDirectionReversed[i];
|
|
25
|
+
const firstNode = reversed ? itinerary.edges[i].node2 : itinerary.edges[i].node1;
|
|
26
|
+
const secondNode = reversed ? itinerary.edges[i].node1 : itinerary.edges[i].node2;
|
|
27
|
+
|
|
28
|
+
expect(firstNode).equal(node);
|
|
29
|
+
expect(secondNode).equal(itinerary.nodes[i + 1]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('verify getNextEdge() and getNextNode()', () => {
|
|
36
|
+
|
|
37
|
+
let currentNode, currentEdge;
|
|
38
|
+
|
|
39
|
+
// Iterate on nodes
|
|
40
|
+
currentNode = itinerary.nodes[0];
|
|
41
|
+
for (let i = 0; i < itinerary.nodes.length - 1; i++) {
|
|
42
|
+
currentEdge = itinerary.getNextEdge(currentNode);
|
|
43
|
+
expect(itinerary.edges.indexOf(currentEdge)).equal(i);
|
|
44
|
+
|
|
45
|
+
currentNode = itinerary.getNextNode(currentNode);
|
|
46
|
+
expect(itinerary.nodes.indexOf(currentNode)).equal(i + 1);
|
|
47
|
+
}
|
|
48
|
+
expect(itinerary.getNextNode(currentNode)).is.null;
|
|
49
|
+
expect(itinerary.getNextEdge(currentNode)).is.null;
|
|
50
|
+
|
|
51
|
+
// Iterate on edges
|
|
52
|
+
currentEdge = itinerary.edges[0];
|
|
53
|
+
for (let i = 0; i < itinerary.edges.length - 1; i++) {
|
|
54
|
+
currentNode = itinerary.getNextNode(currentEdge);
|
|
55
|
+
expect(itinerary.nodes.indexOf(currentNode)).equal(i + 1);
|
|
56
|
+
|
|
57
|
+
currentEdge = itinerary.getNextEdge(currentEdge);
|
|
58
|
+
expect(itinerary.edges.indexOf(currentEdge)).equal(i + 1);
|
|
59
|
+
}
|
|
60
|
+
const lastNode = itinerary.getNextNode(currentEdge);
|
|
61
|
+
expect(lastNode).is.not.null;
|
|
62
|
+
expect(itinerary.getNextNode(lastNode)).is.null;
|
|
63
|
+
expect(itinerary.getNextEdge(currentEdge)).is.null;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('verify steps', () => {
|
|
67
|
+
|
|
68
|
+
const steps = itinerary.steps;
|
|
69
|
+
expect(steps.length).equal(10);
|
|
70
|
+
|
|
71
|
+
expect(steps[0].nodes[1]).equal(nodes[7]);
|
|
72
|
+
expect(steps[0].nodes[2]).equal(nodes[8]);
|
|
73
|
+
|
|
74
|
+
for (let i = 2; i <= 9; i++) {
|
|
75
|
+
expect(steps[i - 1].nodes[0]).equal(nodes[i + 6]);
|
|
76
|
+
expect(steps[i - 1].nodes[1]).equal(nodes[i + 7]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
expect(steps[9].nodes[0]).equal(nodes[16]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
it('verify next and previous steps', () => {
|
|
84
|
+
|
|
85
|
+
const {
|
|
86
|
+
steps, _nextStepsIndexes
|
|
87
|
+
} = itinerary;
|
|
88
|
+
const itineraryNodes = itinerary.nodes;
|
|
89
|
+
const itineraryEdges = itinerary.edges;
|
|
90
|
+
|
|
91
|
+
expect(_nextStepsIndexes.length).equal(itinerary.nodes.length);
|
|
92
|
+
|
|
93
|
+
expect(_nextStepsIndexes[0]).equal(0);
|
|
94
|
+
expect(_nextStepsIndexes[1]).equal(1);
|
|
95
|
+
for (let i = 2; i <= 10; i++) {
|
|
96
|
+
expect(_nextStepsIndexes[i]).equal(i - 1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
expect(itinerary.getNextStep(itineraryNodes[0])).equal(steps[0]);
|
|
100
|
+
expect(itinerary.getNextStep(itineraryNodes[1])).equal(steps[1]);
|
|
101
|
+
for (let i = 2; i <= 10; i++) {
|
|
102
|
+
expect(itinerary.getNextStep(itineraryNodes[i])).equal(steps[i - 1]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
expect(itinerary.getNextStep(itineraryEdges[0])).equal(steps[1]);
|
|
106
|
+
for (let i = 1; i <= 9; i++) {
|
|
107
|
+
expect(itinerary.getNextStep(itineraryEdges[i])).equal(steps[i]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
expect(itinerary.getPreviousStep(itineraryNodes[0])).is.null;
|
|
112
|
+
expect(itinerary.getPreviousStep(itineraryNodes[1])).equal(steps[0]);
|
|
113
|
+
for (let i = 3; i <= 10; i++) {
|
|
114
|
+
expect(itinerary.getPreviousStep(itineraryNodes[i])).equal(steps[i - 2]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
expect(itinerary.getPreviousStep(itineraryEdges[0])).equal(steps[0]);
|
|
118
|
+
for (let i = 2; i <= 9; i++) {
|
|
119
|
+
expect(itinerary.getPreviousStep(itineraryEdges[i])).equal(steps[i - 1]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('info', () => {
|
|
125
|
+
let itineraryInfo;
|
|
126
|
+
let currentPosition;
|
|
127
|
+
|
|
128
|
+
currentPosition = new WGS84(43.6092811, 3.8842406, null, new Level(2));
|
|
129
|
+
itineraryInfo = itinerary.getInfo(currentPosition);
|
|
130
|
+
expect(itineraryInfo.projection).is.not.null;
|
|
131
|
+
expect(itineraryInfo.projection).is.not.undefined;
|
|
132
|
+
expect(itineraryInfo.projection.projection).instanceOf(WGS84);
|
|
133
|
+
expect(itineraryInfo.projection.nearestElement).equal(edges[7]);
|
|
134
|
+
expect(itineraryInfo.remainingDistance).be.closeTo(30.35, 0.01);
|
|
135
|
+
expect(itineraryInfo.traveledDistance).be.closeTo(0.79, 0.01);
|
|
136
|
+
expect(itineraryInfo.remainingPercentage).be.closeTo(0.97, 0.01);
|
|
137
|
+
expect(itineraryInfo.traveledPercentage).be.closeTo(0.03, 0.01);
|
|
138
|
+
expect(itineraryInfo.previousStep).equal(itinerary.steps[0]);
|
|
139
|
+
expect(itineraryInfo.nextStep).equal(itinerary.steps[1]);
|
|
140
|
+
|
|
141
|
+
currentPosition = new WGS84(43.6092811, 3.8842406, null, null);
|
|
142
|
+
itineraryInfo = itinerary.getInfo(currentPosition);
|
|
143
|
+
expect(itineraryInfo).is.null;
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
currentPosition = new WGS84(43.6093476, 3.8841978, null, new Level(2));
|
|
147
|
+
itineraryInfo = itinerary.getInfo(currentPosition);
|
|
148
|
+
expect(itineraryInfo.projection.nearestElement).equal(edges[11]);
|
|
149
|
+
expect(itineraryInfo.remainingDistance).be.closeTo(17.37, 0.01);
|
|
150
|
+
expect(itineraryInfo.traveledDistance).be.closeTo(13.76, 0.01);
|
|
151
|
+
expect(itineraryInfo.remainingPercentage).be.closeTo(0.56, 0.01);
|
|
152
|
+
expect(itineraryInfo.traveledPercentage).be.closeTo(0.44, 0.01);
|
|
153
|
+
expect(itineraryInfo.previousStep).equal(itinerary.steps[4]);
|
|
154
|
+
expect(itineraryInfo.nextStep).equal(itinerary.steps[5]);
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
currentPosition = new WGS84(43.6093182, 3.8842847, null, new Level(1));
|
|
158
|
+
itineraryInfo = itinerary.getInfo(currentPosition);
|
|
159
|
+
expect(itineraryInfo.projection.nearestElement).equal(nodes[15]);
|
|
160
|
+
expect(itineraryInfo.remainingDistance).be.closeTo(5.02, 0.01);
|
|
161
|
+
expect(itineraryInfo.traveledDistance).be.closeTo(26.11, 0.01);
|
|
162
|
+
expect(itineraryInfo.remainingPercentage).be.closeTo(0.16, 0.01);
|
|
163
|
+
expect(itineraryInfo.traveledPercentage).be.closeTo(0.84, 0.01);
|
|
164
|
+
expect(itineraryInfo.previousStep).equal(itinerary.steps[7]);
|
|
165
|
+
expect(itineraryInfo.nextStep).equal(itinerary.steps[8]);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
});
|
package/src/graph/MapMatching.js
CHANGED
|
@@ -35,7 +35,7 @@ class MapMatching {
|
|
|
35
35
|
this.network = network;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
getProjection(location, useDistance =
|
|
38
|
+
getProjection(location, useDistance = false, useBearing = false, useMultiLevelSegments = true, acceptEdgeFn = () => true) {
|
|
39
39
|
|
|
40
40
|
if (!this.network) {
|
|
41
41
|
return null;
|
|
@@ -49,6 +49,12 @@ class MapMatching {
|
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
const updateProjectionLevelFromEdge = (_edge, _projection) => {
|
|
53
|
+
if (_edge.level) {
|
|
54
|
+
_projection.level = _edge.level.clone();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
52
58
|
const projection = {};
|
|
53
59
|
projection.origin = location;
|
|
54
60
|
projection.distanceFromNearestElement = Number.MAX_VALUE;
|
|
@@ -83,6 +89,7 @@ class MapMatching {
|
|
|
83
89
|
projection.distanceFromNearestElement = distNode1;
|
|
84
90
|
projection.nearestElement = edge.node1;
|
|
85
91
|
projection.projection = edge.node1.coords.clone();
|
|
92
|
+
updateProjectionLevelFromEdge(edge, projection.projection);
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
// Check node 2
|
|
@@ -92,12 +99,14 @@ class MapMatching {
|
|
|
92
99
|
projection.distanceFromNearestElement = distNode2;
|
|
93
100
|
projection.nearestElement = edge.node2;
|
|
94
101
|
projection.projection = edge.node2.coords.clone();
|
|
102
|
+
updateProjectionLevelFromEdge(edge, projection.projection);
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
// Check edge
|
|
99
107
|
const segmentProjection = location.getProjection(edge);
|
|
100
108
|
if (segmentProjection) {
|
|
109
|
+
updateProjectionLevelFromEdge(edge, segmentProjection);
|
|
101
110
|
const distEdge = location.distanceTo(segmentProjection);
|
|
102
111
|
if (distEdge < projection.distanceFromNearestElement
|
|
103
112
|
&& (!useDistance || distEdge <= this._maxDistance)) {
|