@wemap/geo 11.7.0 → 11.8.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/dist/index.js +128 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +128 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/graph/GeoGraphRouter.ts +232 -7
- package/src/graph/NoRouteFoundError.ts +4 -5
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"directory": "packages/geo"
|
|
14
14
|
},
|
|
15
15
|
"name": "@wemap/geo",
|
|
16
|
-
"version": "11.
|
|
16
|
+
"version": "11.8.1",
|
|
17
17
|
"bugs": {
|
|
18
18
|
"url": "https://github.com/wemap/wemap-modules-js/issues"
|
|
19
19
|
},
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
},
|
|
46
46
|
"./tests/*": "./tests/*"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "7d7d2a3e1988e60a28121b557b6aa1eab1308f13"
|
|
49
49
|
}
|
|
@@ -42,17 +42,23 @@ class GeoGraphRouter<VertexData = unknown, EdgeData = unknown> {
|
|
|
42
42
|
|
|
43
43
|
const createdVertices: GeoGraphVertex<VertexData, EdgeData>[] = [];
|
|
44
44
|
|
|
45
|
-
const retrieveOrCreateNearestVertex = (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
const retrieveOrCreateNearestVertex = (
|
|
46
|
+
point: Coordinates | GeoGraphVertex<VertexData, EdgeData>
|
|
47
|
+
): GeoGraphVertex<VertexData, EdgeData> => {
|
|
48
|
+
|
|
49
|
+
if (point instanceof GeoGraphVertex) { return point; }
|
|
49
50
|
|
|
50
51
|
const closeVertex = this._graph.getVertexByCoords(point);
|
|
52
|
+
// Case 1. Vertex is close enough to an existing one (some centimeters)
|
|
51
53
|
if (closeVertex) {
|
|
52
54
|
return closeVertex;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
const proj = this._mapMatching.getProjection(point, true, false, false, acceptEdgeFn);
|
|
57
|
+
const proj = this._mapMatching.getProjection(point, true, false, false, options.acceptEdgeFn);
|
|
58
|
+
|
|
59
|
+
// Case 2. We cannot find a projection of the coordinates on the graph.
|
|
60
|
+
// This can happens if it's a multi level graph and the input coordinates have a level which is not covered by the graph.
|
|
61
|
+
// It can also happens because the input coordinates are too far from the graph.
|
|
56
62
|
if (!proj) {
|
|
57
63
|
let message = `Point ${point.toString()} is too far from the graph `
|
|
58
64
|
+ `> ${this._mapMatching.maxDistance.toFixed(0)} meters.`;
|
|
@@ -62,10 +68,13 @@ class GeoGraphRouter<VertexData = unknown, EdgeData = unknown> {
|
|
|
62
68
|
}
|
|
63
69
|
throw new NoRouteFoundError(start, end, message);
|
|
64
70
|
}
|
|
71
|
+
|
|
72
|
+
// Case 3. Nearest element is a vertex, so it is not necessary to create a new vertex
|
|
65
73
|
if (proj.nearestElement instanceof GeoGraphVertex) {
|
|
66
74
|
return proj.nearestElement;
|
|
67
75
|
}
|
|
68
|
-
|
|
76
|
+
|
|
77
|
+
// Case 4. Nearest element is an edge, so a new vertex is created
|
|
69
78
|
const vertexCreated = this.createVertexInsideEdge(
|
|
70
79
|
proj.nearestElement,
|
|
71
80
|
proj.coords
|
|
@@ -83,7 +92,6 @@ class GeoGraphRouter<VertexData = unknown, EdgeData = unknown> {
|
|
|
83
92
|
const startVertex = retrieveOrCreateNearestVertex(start);
|
|
84
93
|
const endVertex = retrieveOrCreateNearestVertex(end);
|
|
85
94
|
|
|
86
|
-
|
|
87
95
|
const startCoords = start instanceof GeoGraphVertex ? start.coords : start;
|
|
88
96
|
const endCoords = end instanceof GeoGraphVertex ? end.coords : end;
|
|
89
97
|
|
|
@@ -116,6 +124,116 @@ class GeoGraphRouter<VertexData = unknown, EdgeData = unknown> {
|
|
|
116
124
|
|
|
117
125
|
}
|
|
118
126
|
|
|
127
|
+
|
|
128
|
+
getShortestPaths(
|
|
129
|
+
start: GeoGraphVertex<VertexData, EdgeData> | Coordinates,
|
|
130
|
+
ends: (GeoGraphVertex<VertexData, EdgeData> | Coordinates)[],
|
|
131
|
+
options: GeoGraphRouterOptions<VertexData, EdgeData> = GeoGraphRouter.DEFAULT_OPTIONS
|
|
132
|
+
) {
|
|
133
|
+
|
|
134
|
+
const { acceptEdgeFn, weightEdgeFn, projectionMaxDistance } = options;
|
|
135
|
+
this._mapMatching.maxDistance = projectionMaxDistance;
|
|
136
|
+
|
|
137
|
+
const createdVertices: GeoGraphVertex<VertexData, EdgeData>[] = [];
|
|
138
|
+
|
|
139
|
+
const retrieveOrCreateNearestVertex = (
|
|
140
|
+
point: Coordinates | GeoGraphVertex<VertexData, EdgeData>,
|
|
141
|
+
options: GeoGraphRouterOptions<VertexData, EdgeData> = GeoGraphRouter.DEFAULT_OPTIONS
|
|
142
|
+
): GeoGraphVertex<VertexData, EdgeData> | null => {
|
|
143
|
+
|
|
144
|
+
if (point instanceof GeoGraphVertex) { return point; }
|
|
145
|
+
|
|
146
|
+
const closeVertex = this._graph.getVertexByCoords(point);
|
|
147
|
+
// Case 1. Vertex is close enough to an existing one (some centimeters)
|
|
148
|
+
if (closeVertex) {
|
|
149
|
+
return closeVertex;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const proj = this._mapMatching.getProjection(point, true, false, false, options.acceptEdgeFn);
|
|
153
|
+
|
|
154
|
+
// Case 2. We cannot find a projection of the coordinates on the graph.
|
|
155
|
+
// This can happens if it's a multi level graph and the input coordinates have a level which is not covered by the graph.
|
|
156
|
+
// It can also happens because the input coordinates are too far from the graph.
|
|
157
|
+
if (!proj) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Case 3. Nearest element is a vertex, so it is not necessary to create a new vertex
|
|
162
|
+
if (proj.nearestElement instanceof GeoGraphVertex) {
|
|
163
|
+
return proj.nearestElement;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Case 4. Nearest element is an edge, so a new vertex is created
|
|
167
|
+
const vertexCreated = this.createVertexInsideEdge(
|
|
168
|
+
proj.nearestElement,
|
|
169
|
+
proj.coords
|
|
170
|
+
);
|
|
171
|
+
createdVertices.push(vertexCreated);
|
|
172
|
+
return vertexCreated;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
const removeCreatedVertices = () => {
|
|
177
|
+
while (createdVertices.length) {
|
|
178
|
+
this.removeVertexFromPreviouslyCreatedEdge(createdVertices.pop() as GeoGraphVertex<VertexData, EdgeData>);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const startVertex = retrieveOrCreateNearestVertex(start);
|
|
183
|
+
|
|
184
|
+
const endsStruct: {
|
|
185
|
+
input: GeoGraphVertex<VertexData, EdgeData> | Coordinates,
|
|
186
|
+
vertex: GeoGraphVertex<VertexData, EdgeData> | null,
|
|
187
|
+
itinerary: GeoGraphItinerary<VertexData, EdgeData> | null
|
|
188
|
+
}[] = ends.map(e => ({
|
|
189
|
+
input: e,
|
|
190
|
+
vertex: retrieveOrCreateNearestVertex(e),
|
|
191
|
+
itinerary: null
|
|
192
|
+
}))
|
|
193
|
+
|
|
194
|
+
const startCoords = start instanceof GeoGraphVertex ? start.coords : start;
|
|
195
|
+
if (!startVertex) {
|
|
196
|
+
let message = `Point ${startCoords.toString()} is too far from the graph `
|
|
197
|
+
+ `> ${this._mapMatching.maxDistance.toFixed(0)} meters.`;
|
|
198
|
+
if (startCoords.level !== null) {
|
|
199
|
+
message += ' If it is a multi-level map, please verify if you have'
|
|
200
|
+
+ ` a graph at level ${Level.toString(startCoords.level)}.`;
|
|
201
|
+
}
|
|
202
|
+
throw new NoRouteFoundError(start, ends, message);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const endsStructFiltered = endsStruct.filter(v => v.vertex != null) as
|
|
206
|
+
{
|
|
207
|
+
input: GeoGraphVertex<VertexData, EdgeData> | Coordinates,
|
|
208
|
+
vertex: GeoGraphVertex<VertexData, EdgeData>,
|
|
209
|
+
itinerary: GeoGraphItinerary<VertexData, EdgeData> | null
|
|
210
|
+
}[];
|
|
211
|
+
|
|
212
|
+
const graphItineraries = this.getShortestPathsBetweenGeoGraphVertices(
|
|
213
|
+
startVertex,
|
|
214
|
+
endsStructFiltered.map(v => v.vertex),
|
|
215
|
+
weightEdgeFn,
|
|
216
|
+
acceptEdgeFn
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// In the case of start or end are not a vertex, graphItinerary.start/end are first vertex or
|
|
220
|
+
// last vertex. We have to override their values.
|
|
221
|
+
graphItineraries.forEach((itinerary, idx) => {
|
|
222
|
+
itinerary.start = startCoords;
|
|
223
|
+
const endStructFiltered = endsStructFiltered[idx];
|
|
224
|
+
endStructFiltered.itinerary = itinerary;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
removeCreatedVertices();
|
|
228
|
+
|
|
229
|
+
endsStructFiltered.forEach(e => {
|
|
230
|
+
if (e.itinerary !== null) {
|
|
231
|
+
e.itinerary.end = (e.input instanceof GeoGraphVertex) ? e.input.coords : e.input;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
return endsStructFiltered.map(e => e.itinerary);
|
|
235
|
+
}
|
|
236
|
+
|
|
119
237
|
createVertexInsideEdge(edge: GeoGraphEdge<VertexData, EdgeData>, point: Coordinates) {
|
|
120
238
|
const a = edge.vertex1;
|
|
121
239
|
const b = edge.vertex2;
|
|
@@ -261,5 +379,112 @@ class GeoGraphRouter<VertexData = unknown, EdgeData = unknown> {
|
|
|
261
379
|
// This clone the itinerary and temporary vertices
|
|
262
380
|
return GeoGraphItinerary.fromGraphVertices(start.coords, end.coords, path, edgesWeights);
|
|
263
381
|
}
|
|
382
|
+
|
|
383
|
+
getShortestPathsBetweenGeoGraphVertices(
|
|
384
|
+
start: GeoGraphVertex<VertexData, EdgeData>,
|
|
385
|
+
ends: GeoGraphVertex<VertexData, EdgeData>[],
|
|
386
|
+
weightFn: (edge: GeoGraphEdge<VertexData, EdgeData>) => number,
|
|
387
|
+
acceptEdgeFn?: (edge: GeoGraphEdge<VertexData, EdgeData>) => boolean
|
|
388
|
+
) {
|
|
389
|
+
|
|
390
|
+
const distanceMap: { [key: number]: number } = {},
|
|
391
|
+
checking: { [key: number]: null | number } = {},
|
|
392
|
+
vertexList: { [key: number]: boolean } = {},
|
|
393
|
+
vertices: { [key: number]: GeoGraphVertex<VertexData, EdgeData> } = {},
|
|
394
|
+
parentVertices: { [key: number]: number } = {};
|
|
395
|
+
|
|
396
|
+
// Initially, we assume each vertex is unreachable
|
|
397
|
+
this._graph.vertices.forEach(vertex => {
|
|
398
|
+
|
|
399
|
+
// Generate Unique Router Id
|
|
400
|
+
vertices[vertex.id] = vertex;
|
|
401
|
+
distanceMap[vertex.id] = Infinity;
|
|
402
|
+
checking[vertex.id] = null;
|
|
403
|
+
vertexList[vertex.id] = true;
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// The cost from the starting vertex to the starting vertex is 0
|
|
407
|
+
distanceMap[start.id] = 0;
|
|
408
|
+
|
|
409
|
+
// check each vertex
|
|
410
|
+
while (Object.keys(vertexList).length > 0) {
|
|
411
|
+
const keys = Object.keys(vertexList).map(Number);
|
|
412
|
+
const current = Number(
|
|
413
|
+
keys.reduce((_checking, vertexId) => {
|
|
414
|
+
return distanceMap[_checking] > distanceMap[vertexId]
|
|
415
|
+
? vertexId
|
|
416
|
+
: _checking;
|
|
417
|
+
}, keys[0])
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
// all the vertices accessible from current vertex
|
|
421
|
+
this._graph.edges
|
|
422
|
+
.filter(edge => {
|
|
423
|
+
|
|
424
|
+
if (acceptEdgeFn && !acceptEdgeFn(edge) || this.disabledEdges.has(edge)) {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const from = edge.vertex1.id,
|
|
429
|
+
to = edge.vertex2.id;
|
|
430
|
+
// are these vertices joined?
|
|
431
|
+
return from === current || to === current;
|
|
432
|
+
})
|
|
433
|
+
// for each vertex we can reach
|
|
434
|
+
.forEach(edge => {
|
|
435
|
+
let to, from, reversed = false;
|
|
436
|
+
// determine the direction of travel
|
|
437
|
+
if (edge.vertex1.id === current) {
|
|
438
|
+
to = edge.vertex2.id;
|
|
439
|
+
from = edge.vertex1.id;
|
|
440
|
+
} else {
|
|
441
|
+
to = edge.vertex1.id;
|
|
442
|
+
from = edge.vertex2.id;
|
|
443
|
+
reversed = true;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (edge.isOneway && reversed) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// distance is how far we travelled to reach the
|
|
451
|
+
// current vertex, plus cost of travel the next(to)
|
|
452
|
+
const distance = distanceMap[current] + weightFn(edge);
|
|
453
|
+
|
|
454
|
+
// if we have found a cheaper path
|
|
455
|
+
// update the hash of costs
|
|
456
|
+
// and record which vertex we came from
|
|
457
|
+
if (distanceMap[to] > distance) {
|
|
458
|
+
distanceMap[to] = distance;
|
|
459
|
+
checking[to] = current;
|
|
460
|
+
parentVertices[to] = from;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// remove vertex so we don't revisit it
|
|
465
|
+
delete vertexList[current];
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
return ends.map(end => {
|
|
470
|
+
const edgesWeights = [];
|
|
471
|
+
const path: GeoGraphVertex<VertexData, EdgeData>[] = [];
|
|
472
|
+
|
|
473
|
+
// now we have the most efficient paths for all vertices
|
|
474
|
+
// build the path for the user specified vertex(end)
|
|
475
|
+
let endId = end.id;
|
|
476
|
+
while (typeof parentVertices[endId] !== 'undefined') {
|
|
477
|
+
path.unshift(vertices[endId]);
|
|
478
|
+
edgesWeights.unshift(distanceMap[endId] - distanceMap[parentVertices[endId]]);
|
|
479
|
+
endId = parentVertices[endId];
|
|
480
|
+
}
|
|
481
|
+
if (path.length !== 0) {
|
|
482
|
+
path.unshift(start);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// This clone the itinerary and temporary vertices
|
|
486
|
+
return GeoGraphItinerary.fromGraphVertices(start.coords, end.coords, path, edgesWeights);
|
|
487
|
+
});
|
|
488
|
+
}
|
|
264
489
|
}
|
|
265
490
|
export default GeoGraphRouter;
|
|
@@ -5,7 +5,7 @@ class NoRouteFoundError extends Error {
|
|
|
5
5
|
|
|
6
6
|
constructor(
|
|
7
7
|
public start: GeoGraphVertex | Coordinates,
|
|
8
|
-
public end: GeoGraphVertex | Coordinates,
|
|
8
|
+
public end: GeoGraphVertex | Coordinates | (Coordinates | GeoGraphVertex)[],
|
|
9
9
|
public details: string | null = null
|
|
10
10
|
) { super(); }
|
|
11
11
|
|
|
@@ -13,8 +13,6 @@ class NoRouteFoundError extends Error {
|
|
|
13
13
|
if (this.start instanceof GeoGraphVertex) {
|
|
14
14
|
return `GeoGraphVertex ${this.start.coords.toString()}`;
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
// if (this.start instanceof Coordinates) {
|
|
18
16
|
return this.start.toString();
|
|
19
17
|
}
|
|
20
18
|
|
|
@@ -22,8 +20,9 @@ class NoRouteFoundError extends Error {
|
|
|
22
20
|
if (this.end instanceof GeoGraphVertex) {
|
|
23
21
|
return `GeoGraphVertex ${this.end.coords.toString()}`;
|
|
24
22
|
}
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
if (Array.isArray(this.end)) {
|
|
24
|
+
return this.end.map(p => p instanceof GeoGraphVertex ? p.coords.toString() : p.toString()).join(',');
|
|
25
|
+
}
|
|
27
26
|
return this.end.toString();
|
|
28
27
|
}
|
|
29
28
|
|