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