@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/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "directory": "packages/geo"
14
14
  },
15
15
  "name": "@wemap/geo",
16
- "version": "11.7.0",
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": "e06f16f4114b6e8da0c0f6bbda65c1ad1a3f45ce"
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 = (point: GeoGraphVertex<VertexData, EdgeData> | Coordinates) => {
46
- if (point instanceof GeoGraphVertex) {
47
- return point;
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
- // if (proj.nearestElement instanceof Edge)
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
- // if (this.end instanceof Coordinates) {
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