@wemap/geo 0.1.4 → 0.2.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/index.js CHANGED
@@ -1,19 +1,28 @@
1
- import Attitude from './src/rotations/Attitude';
2
1
  import Constants from './src/Constants';
2
+
3
+ import Attitude from './src/rotations/Attitude';
4
+
5
+ import Level from './src/coordinates/Level';
6
+ import WGS84 from './src/coordinates/WGS84';
7
+ import WGS84UserPosition from './src/coordinates/WGS84UserPosition';
8
+
3
9
  import Edge from './src/graph/Edge';
10
+ import GraphRouter from './src/graph/GraphRouter';
4
11
  import Itinerary from './src/graph/Itinerary';
5
- import MapMatching from './src/graph/MapMatching';
6
12
  import Network from './src/graph/Network';
7
- import WGS84 from './src/coordinates/WGS84';
8
- import WGS84UserPosition from './src/coordinates/WGS84UserPosition';
13
+ import Node from './src/graph/Node';
14
+ import MapMatching from './src/graph/MapMatching';
9
15
 
10
16
  export {
11
17
  Attitude,
12
18
  Constants,
13
19
  Edge,
20
+ GraphRouter,
14
21
  Itinerary,
22
+ Level,
15
23
  MapMatching,
16
24
  Network,
25
+ Node,
17
26
  WGS84,
18
27
  WGS84UserPosition
19
28
  };
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "directory": "packages/geo"
13
13
  },
14
14
  "name": "@wemap/geo",
15
- "version": "0.1.4",
15
+ "version": "0.2.0",
16
16
  "bugs": {
17
17
  "url": "https://github.com/wemap/wemap-utils-js/issues"
18
18
  },
@@ -26,5 +26,8 @@
26
26
  "wemap"
27
27
  ],
28
28
  "license": "ISC",
29
- "gitHead": "991a53f06eed6adb872fad2cbd70a4edf7ee9cf9"
29
+ "dependencies": {
30
+ "lodash.isnumber": "^3.0.3"
31
+ },
32
+ "gitHead": "6673bc4d0f4b6ff404534e0642122c340fe58483"
30
33
  }
@@ -0,0 +1,108 @@
1
+ import Logger from '@wemap/logger';
2
+
3
+ /**
4
+ * A Level is the representation of a building floor number
5
+ * A level can be a simple number (val) or a range (low, up)
6
+ * To know if a level is a range or a number, isRange can be used
7
+ */
8
+ class Level {
9
+
10
+ constructor() {
11
+ this.val = null;
12
+ this.isRange = false;
13
+ }
14
+
15
+ clone() {
16
+ const output = new Level();
17
+ output.isRange = this.isRange;
18
+ output.val = this.val;
19
+ output.up = this.up;
20
+ output.low = this.low;
21
+ return output;
22
+ }
23
+
24
+ /**
25
+ * Create a level from a string
26
+ * @param {*} str level in str format (eg. 1, -2, 1;2, -2;3, 2;-1, 0.5;1 ...)
27
+ */
28
+ static fromString(str) {
29
+ const level = new Level();
30
+ const num = Number(str);
31
+ if (!isNaN(num)) {
32
+ level.val = num;
33
+ level.isRange = false;
34
+ return level;
35
+ }
36
+
37
+ const splited = str.split(';');
38
+ if (splited.length === 2) {
39
+ const num1 = Number(splited[0]);
40
+ const num2 = Number(splited[1]);
41
+ if (!isNaN(num1) && !isNaN(num2)) {
42
+ level.up = Math.max(num1, num2);
43
+ level.low = Math.min(num1, num2);
44
+ level.isRange = true;
45
+ return level;
46
+ }
47
+ }
48
+
49
+ Logger.warn('Cannot parse following level: ' + str);
50
+ return null;
51
+ }
52
+
53
+ /**
54
+ * Is the given number inside the current level
55
+ * @param {Number} val the given number
56
+ */
57
+ isInside(val) {
58
+ return this.isRange ? val >= this.low && val <= this.up : val === this.val;
59
+ }
60
+
61
+ /**
62
+ * Retrieve the intersection of two levels
63
+ * @param {Level} other The other level
64
+ */
65
+ intersect(other) {
66
+
67
+ if (!other) {
68
+ return null;
69
+ }
70
+
71
+ if (this.isRange && !other.isRange) {
72
+ if (this.isInside(other.val)) {
73
+ return other;
74
+ }
75
+ return null;
76
+ }
77
+ if (!this.isRange && other.isRange) {
78
+ if (other.isInside(this.val)) {
79
+ return this;
80
+ }
81
+ return null;
82
+ }
83
+ if (this.isRange && other.isRange) {
84
+ const level = new Level();
85
+ level.low = Math.max(this.low, other.low);
86
+ level.up = Math.min(this.up, other.up);
87
+ level.isRange = true;
88
+ return level;
89
+ }
90
+ return this.val === other.val ? this : null;
91
+ }
92
+
93
+ multiplyBy(factor) {
94
+ if (this.isRange) {
95
+ this.low *= factor;
96
+ this.up *= factor;
97
+ } else {
98
+ this.val *= factor;
99
+ }
100
+ }
101
+
102
+ toString() {
103
+ return this.isRange ? this.low + ';' + this.up : this.val;
104
+ }
105
+
106
+ }
107
+
108
+ export default Level;
@@ -2,14 +2,30 @@ import {
2
2
  Utils, Vector3, Quaternion
3
3
  } from '@wemap/maths';
4
4
  import Constants from '../Constants';
5
-
6
-
5
+ import isNumber from 'lodash/isnumber';
6
+
7
+ /**
8
+ * JSON output formats:
9
+ * - FORMAT_LAT_LNG_ELE (default)
10
+ * - FORMAT_LNG_LAT_STRLEVEL
11
+ */
12
+ const FORMAT_LAT_LNG_ELE = 0;
13
+ const FORMAT_LNG_LAT_STRLEVEL = 1;
14
+
15
+ /**
16
+ * A WGS84 position using at least latitude (lat) and longitude (lng).
17
+ * Optionnal fields are: altitude (alt) and level.
18
+ *
19
+ * Basic geo methods are directly accessibles from here:
20
+ * distanceTo, bearingTo, toEcef...
21
+ */
7
22
  class WGS84 {
8
23
 
9
24
  constructor(lat, lng, alt) {
10
25
  this._lat = lat;
11
26
  this._lng = lng;
12
27
  this._alt = alt;
28
+ this._level = null;
13
29
  this._ecef = null;
14
30
  }
15
31
 
@@ -25,6 +41,10 @@ class WGS84 {
25
41
  return this._alt;
26
42
  }
27
43
 
44
+ get level() {
45
+ return this._level;
46
+ }
47
+
28
48
  set lat(lat) {
29
49
  this._ecef = null;
30
50
  this._lat = lat;
@@ -40,8 +60,16 @@ class WGS84 {
40
60
  this._alt = alt;
41
61
  }
42
62
 
63
+ set level(level) {
64
+ this._level = level;
65
+ }
66
+
43
67
  clone() {
44
- return new WGS84(this.lat, this.lng, this.alt);
68
+ const output = new WGS84(this.lat, this.lng, this.alt);
69
+ if (this.level) {
70
+ output.level = this.level.clone();
71
+ }
72
+ return output;
45
73
  }
46
74
 
47
75
  equalsTo(other) {
@@ -62,6 +90,17 @@ class WGS84 {
62
90
  return str;
63
91
  }
64
92
 
93
+ toJson(format = FORMAT_LAT_LNG_ELE) {
94
+ switch (format) {
95
+ case FORMAT_LNG_LAT_STRLEVEL:
96
+ return [this.lng, this.lat, this.level ? this.level.toString() : null];
97
+
98
+ case FORMAT_LAT_LNG_ELE:
99
+ default:
100
+ return [this.lat, this.lng, this.ele];
101
+ }
102
+ }
103
+
65
104
  destinationPoint(distance, bearing, elevation = 0) {
66
105
  const newPoint = this.clone();
67
106
  newPoint.move(distance, bearing, elevation);
@@ -201,8 +240,8 @@ class WGS84 {
201
240
  // Adapted to ECEF
202
241
  getProjection(edge) {
203
242
 
204
- const p1 = edge.node1;
205
- const p2 = edge.node2;
243
+ const p1 = edge.node1.coords;
244
+ const p2 = edge.node2.coords;
206
245
 
207
246
  const a = Vector3.normalize(p1.ecef);
208
247
  const b = Vector3.normalize(p2.ecef);
@@ -219,7 +258,7 @@ class WGS84 {
219
258
  // So if p1.alt and p2.alt are defined we take the middle elevation between p1 and p2.
220
259
  // Otherwise we remove alt from projection because the residual has no sense.
221
260
  let alt;
222
- if (typeof p1.alt !== 'undefined' && typeof p2.alt !== 'undefined') {
261
+ if (isNumber(p1.alt) && isNumber(p2.alt)) {
223
262
  alt = (p1.alt + p2.alt) / 2;
224
263
  }
225
264
  const projection = new WGS84(poseWGS84.lat, poseWGS84.lng, alt);
@@ -290,6 +329,15 @@ class WGS84 {
290
329
 
291
330
  return sampledRoute;
292
331
  }
332
+
333
+
334
+ static get FORMAT_LNG_LAT_STRLEVEL() {
335
+ return FORMAT_LNG_LAT_STRLEVEL;
336
+ }
337
+
338
+ static get FORMAT_LAT_LNG_ELE() {
339
+ return FORMAT_LAT_LNG_ELE;
340
+ }
293
341
  }
294
342
 
295
343
  export default WGS84;
@@ -2,6 +2,9 @@ import WGS84 from './WGS84';
2
2
 
3
3
  const MESSAGE_TYPE = 'WGS84UserPosition';
4
4
 
5
+ /**
6
+ * A WGS84 User Position is a WGS84 position with specific data related to user (bearing, provider, time, accuracy)
7
+ */
5
8
  class WGS84UserPosition extends WGS84 {
6
9
 
7
10
  constructor(lat, lng, alt = null, time = null,
package/src/graph/Edge.js CHANGED
@@ -1,55 +1,56 @@
1
- import WGS84 from '../coordinates/WGS84';
1
+ import Node from './Node';
2
2
 
3
+ /**
4
+ * An Edge is a segment composed of two Node
5
+ * An edge is mostly issued from an OsmWay (data attribute), but this is not
6
+ * always the case. For example, edges created by mapmatching.
7
+ */
3
8
  class Edge {
4
9
 
5
- constructor(node1, node2) {
10
+ constructor(node1, node2, data) {
6
11
 
7
- if (!(node1 instanceof WGS84)) {
8
- throw new TypeError('node1 is not an instance of WGS84');
12
+ if (!(node1 instanceof Node)) {
13
+ throw new TypeError('node1 is not an instance of Node');
9
14
  }
10
15
 
11
- if (!(node2 instanceof WGS84)) {
12
- throw new TypeError('node2 is not an instance of WGS84');
16
+ if (!(node2 instanceof Node)) {
17
+ throw new TypeError('node2 is not an instance of Node');
13
18
  }
14
19
 
15
20
  this.node1 = node1;
16
21
  this.node2 = node2;
22
+ this.data = data;
23
+ this.level = null;
24
+
17
25
  this.computedSizeAndBearing = false;
18
26
  }
19
27
 
20
- getBearing() {
28
+ /**
29
+ * Get edge bearing from node1 to node2
30
+ */
31
+ get bearing() {
21
32
  if (!this.computedSizeAndBearing) {
22
33
  this.computeSizeAndBearing();
23
34
  }
24
- return this.bearing;
35
+ return this._bearing;
25
36
  }
26
37
 
27
- getLength() {
38
+ /**
39
+ * get edge length
40
+ */
41
+ get length() {
28
42
  if (!this.computedSizeAndBearing) {
29
43
  this.computeSizeAndBearing();
30
44
  }
31
- return this.length;
45
+ return this._length;
32
46
  }
33
47
 
34
48
  computeSizeAndBearing() {
35
- this.length = this.node1.distanceTo(this.node2);
36
- this.bearing = this.node1.bearingTo(this.node2);
49
+ this._length = this.node1.distanceTo(this.node2);
50
+ this._bearing = this.node1.bearingTo(this.node2);
37
51
  this.computedSizedAndBearing = true;
38
52
  }
39
53
 
40
- static pointsToEdges(points, lngLatFormat = true) {
41
- let lastPoint = null;
42
- const edgesNetwork = [];
43
- for (let i = 0; i < points.length; i++) {
44
- const currentPoint = new WGS84(points[i][lngLatFormat ? 1 : 0], points[i][lngLatFormat ? 0 : 1]);
45
- if (lastPoint) {
46
- edgesNetwork.push(new Edge(lastPoint, currentPoint));
47
- }
48
- lastPoint = currentPoint;
49
- }
50
- return edgesNetwork;
51
- }
52
-
53
54
  equalsTo(other) {
54
55
 
55
56
  if (!(other instanceof Edge)) {
@@ -58,6 +59,10 @@ class Edge {
58
59
 
59
60
  return other.node1.equalsTo(this.node1) && other.node2.equalsTo(this.node2);
60
61
  }
62
+
63
+ clone(node1, node2) {
64
+ return new Edge(node1, node2, this.data);
65
+ }
61
66
  }
62
67
 
63
68
  export default Edge;
@@ -0,0 +1,242 @@
1
+ /* eslint-disable max-statements */
2
+ /* eslint-disable complexity */
3
+
4
+ import { WGS84 } from '@wemap/geo';
5
+
6
+ import Edge from './Edge';
7
+ import Itinerary from './Itinerary';
8
+ import Node from './Node';
9
+ import MapMatching from './MapMatching';
10
+
11
+ class GraphRouter {
12
+
13
+ constructor(network) {
14
+ this.network = network;
15
+ this.mapMatching = new MapMatching(network);
16
+ this.mapMatching.maxDistance = 50;
17
+ }
18
+
19
+ getShortestPath(_start, _end, acceptEdgeFn = () => true) {
20
+ let start;
21
+ let startNodeCreated;
22
+
23
+ if (_start instanceof Node) {
24
+ start = _start;
25
+ } else if (_start instanceof WGS84) {
26
+ const node = this.network.getNodeByCoords(_start);
27
+ if (node) {
28
+ start = node;
29
+ } else {
30
+ const proj = this.mapMatching.getProjection(_start, true, false, false, acceptEdgeFn);
31
+ if (proj && proj.nearestElement instanceof Node) {
32
+ start = proj.nearestElement;
33
+ } else if (proj && proj.nearestElement instanceof Edge) {
34
+ startNodeCreated = this.createNodeInsideEdge(
35
+ proj.nearestElement,
36
+ proj.projection
37
+ );
38
+ start = startNodeCreated;
39
+ }
40
+ }
41
+ }
42
+
43
+ if (!start) {
44
+ throw new Error('Cannot provide path because no starting point has been found');
45
+ }
46
+
47
+ let end;
48
+ let endNodeCreated;
49
+
50
+ if (_end instanceof Node) {
51
+ end = _end;
52
+ } else if (_end instanceof WGS84) {
53
+ const node = this.network.getNodeByCoords(_end);
54
+ if (node) {
55
+ end = node;
56
+ } else {
57
+ const proj = this.mapMatching.getProjection(_end, true, false, false, acceptEdgeFn);
58
+ if (proj && proj.nearestElement instanceof Node) {
59
+ end = proj.nearestElement;
60
+ } else if (proj && proj.nearestElement instanceof Edge) {
61
+ endNodeCreated = this.createNodeInsideEdge(
62
+ proj.nearestElement,
63
+ proj.projection
64
+ );
65
+ end = endNodeCreated;
66
+ }
67
+ }
68
+ }
69
+
70
+ if (!end) {
71
+ if (startNodeCreated) {
72
+ this.removeNodeFromPreviouslyCreatedEdge(startNodeCreated);
73
+ }
74
+ throw new Error('Cannot provide path because no end point has been found');
75
+ }
76
+
77
+ const path = this.getShortestPathInternal(start, end, acceptEdgeFn);
78
+
79
+ if (endNodeCreated) {
80
+ this.removeNodeFromPreviouslyCreatedEdge(endNodeCreated);
81
+ }
82
+
83
+ if (startNodeCreated) {
84
+ this.removeNodeFromPreviouslyCreatedEdge(startNodeCreated);
85
+ }
86
+
87
+ let route;
88
+ if (path && path.length !== 0) {
89
+ route = Itinerary.fromNetworkNodes(path, _start, _end);
90
+ }
91
+
92
+ return route;
93
+ }
94
+
95
+ createNodeInsideEdge(edge, point) {
96
+ const a = edge.node1;
97
+ const b = edge.node2;
98
+
99
+ const m = new Node(point);
100
+ m.coords.level = edge.level;
101
+
102
+ const u = new Edge(a, m);
103
+ u.level = edge.level;
104
+ u.data = edge.data;
105
+
106
+ const v = new Edge(b, m);
107
+ v.level = edge.level;
108
+ v.data = edge.data;
109
+
110
+ m.edges.push(u, v);
111
+ a.edges.push(u);
112
+ b.edges.push(v);
113
+ a.edges = a.edges.filter(_edge => _edge !== edge);
114
+ b.edges = b.edges.filter(_edge => _edge !== edge);
115
+
116
+ this.network.nodes.push(m);
117
+ this.network.edges.push(u, v);
118
+
119
+ this.network.edges = this.network.edges.filter(
120
+ _edge => _edge !== edge
121
+ );
122
+
123
+ return m;
124
+ }
125
+
126
+ removeNodeFromPreviouslyCreatedEdge(_node) {
127
+ const u = _node.edges[0];
128
+ const v = _node.edges[1];
129
+
130
+ u.node1.edges = u.node1.edges.filter(edge => edge !== u);
131
+ v.node1.edges = v.node1.edges.filter(edge => edge !== v);
132
+
133
+ const oldEdge = new Edge(u.node1, v.node1);
134
+ oldEdge.data = u.data;
135
+ this.network.edges.push(oldEdge);
136
+ u.node1.edges.push(oldEdge);
137
+ v.node1.edges.push(oldEdge);
138
+
139
+ this.network.nodes = this.network.nodes.filter(node => node !== _node);
140
+ this.network.edges = this.network.edges.filter(
141
+ edge => edge !== u && edge !== v
142
+ );
143
+ }
144
+
145
+ getShortestPathInternal(start, end, acceptEdgeFn) {
146
+ const distanceMap = {},
147
+ checking = {},
148
+ vertexList = {},
149
+ vertexNodes = {},
150
+ parentVertices = {},
151
+ path = [];
152
+ let vertexId = 0;
153
+
154
+ // Initially, we assume each vertex is unreachable
155
+ this.network.nodes.forEach(vertex => {
156
+
157
+ // Generate Unique Router Id
158
+ vertex.uniqueRouterId = vertexId;
159
+ vertexNodes[vertexId] = vertex;
160
+
161
+ distanceMap[vertexId] = Infinity;
162
+ checking[vertexId] = null;
163
+ vertexList[vertexId] = true;
164
+
165
+ vertexId++;
166
+ });
167
+
168
+ // The cost from the starting vertex to the starting vertex is 0
169
+ distanceMap[start.uniqueRouterId] = 0;
170
+
171
+ // check each vertex
172
+ while (Object.keys(vertexList).length > 0) {
173
+ const current = Number(
174
+ Object.keys(vertexList).reduce((_checking, vertex) => {
175
+ return distanceMap[_checking] > distanceMap[vertex]
176
+ ? vertex
177
+ : _checking;
178
+ }, Object.keys(vertexList)[0])
179
+ );
180
+
181
+ // all the vertices accessible from current vertex
182
+ this.network.edges
183
+ .filter(edge => {
184
+
185
+ if (!acceptEdgeFn(edge)) {
186
+ return false;
187
+ }
188
+
189
+ const from = edge.node1.uniqueRouterId,
190
+ to = edge.node2.uniqueRouterId;
191
+ // are these vertices joined?
192
+ return from === current || to === current;
193
+ })
194
+ // for each vertex we can reach
195
+ .forEach(edge => {
196
+ let to, from;
197
+ // determine the direction of travel
198
+ if (edge.node1.uniqueRouterId === current) {
199
+ to = edge.node2.uniqueRouterId;
200
+ from = edge.node1.uniqueRouterId;
201
+ } else {
202
+ to = edge.node1.uniqueRouterId;
203
+ from = edge.node2.uniqueRouterId;
204
+ }
205
+ // distance is how far we travelled to reach the
206
+ // current vertex, plus cost of travel the next(to)
207
+ const distance = distanceMap[current] + edge.length;
208
+
209
+ // if we have found a cheaper path
210
+ // update the hash of costs
211
+ // and record which vertex we came from
212
+ if (distanceMap[to] > distance) {
213
+ distanceMap[to] = distance;
214
+ checking[to] = current;
215
+ parentVertices[to] = from;
216
+ }
217
+ });
218
+
219
+ // remove vertex so we don't revisit it
220
+ delete vertexList[current];
221
+ }
222
+
223
+ // now we have the most efficient paths for all vertices
224
+ // build the path for the user specified vertex(end)
225
+ let endId = end.uniqueRouterId;
226
+ while (parentVertices[endId]) {
227
+ path.unshift(vertexNodes[endId]);
228
+ endId = parentVertices[endId];
229
+ }
230
+ if (path.length !== 0) {
231
+ path.unshift(start);
232
+ }
233
+
234
+ // Remove Unique Router Id
235
+ this.network.nodes.forEach(vertex => {
236
+ delete vertex.uniqueRouterId;
237
+ });
238
+
239
+ return path;
240
+ }
241
+ }
242
+ export default GraphRouter;
@@ -1,36 +1,133 @@
1
- import Edge from './Edge';
1
+
2
+ import { WGS84 } from '@wemap/geo';
3
+
2
4
  import Network from './Network';
5
+ import Edge from './Edge';
6
+ import Node from './Node';
3
7
 
4
- // An itinerary is an ordered list of edges where edge(n-1).p2 = edge.(n).p1
8
+ /**
9
+ * Main attributes are:
10
+ * nodes: the ordered list of Node
11
+ * edges: the ordered list of Edge
12
+ * start: the start point (WGS84)
13
+ * end: the end point (WGS84)
14
+ * length: the route length
15
+ */
5
16
  class Itinerary extends Network {
6
17
 
7
- constructor(edges = []) {
8
- super(edges);
18
+ constructor() {
19
+ super();
20
+ this.start = null;
21
+ this.end = null;
22
+ this.length = -1;
9
23
  }
10
24
 
11
- static fromPoints(points, lngLatFormat = true) {
12
- return new Itinerary(Edge.pointsToEdges(points, lngLatFormat));
25
+ /**
26
+ * Get route duration
27
+ * @param {Number} speed in km/h
28
+ */
29
+ getDuration(speed = 4) {
30
+ return (speed * 1000 / 3600) * this.length;
13
31
  }
14
32
 
15
- get firstEdge() {
16
- if (this.edges.length < 1) {
17
- throw new TypeError('first edge of itinerary does not exist');
33
+ /**
34
+ * Get edge at given distance
35
+ * @param {Number} distance
36
+ * @returns {Edge} the edge or null if not exists
37
+ */
38
+ getEdgeAt(distance) {
39
+
40
+ if (distance === 0 && this.edges.length > 0) {
41
+ return this.edges[0];
42
+ }
43
+
44
+ let sum = 0;
45
+ for (let i = 0; i <= this.edges.length; i++) {
46
+ sum += this.edges[i].length;
47
+ if (distance <= sum) {
48
+ return this.edges[i];
49
+ }
18
50
  }
19
- return this.edges[0];
51
+ return null;
20
52
  }
21
53
 
22
- get secondEdge() {
23
- if (this.edges.length < 2) {
24
- throw new TypeError('second edge of itinerary does not exist');
54
+
55
+ static fromNetworkNodes(nodes, start, end) {
56
+
57
+ /**
58
+ * Itinerary part of network is cloned and ordered
59
+ */
60
+
61
+ const itinerary = new Itinerary();
62
+ itinerary.length = 0;
63
+ itinerary.start = start;
64
+ itinerary.end = end;
65
+
66
+ let nextItineraryNode;
67
+
68
+ for (let i = 0; i < nodes.length; i++) {
69
+
70
+ const networkNode = nodes[i];
71
+ const itineraryNode = nextItineraryNode ? nextItineraryNode : networkNode.clone();
72
+
73
+ itinerary.nodes.push(itineraryNode);
74
+
75
+ if (i === nodes.length - 1) {
76
+ break;
77
+ }
78
+ const nextNetworkNode = nodes[i + 1];
79
+ nextItineraryNode = nextNetworkNode.clone();
80
+
81
+ const networkEdge = Network.getEdgeByNodes(networkNode.edges, networkNode, nextNetworkNode);
82
+ const itineraryEdge = networkEdge.clone(itineraryNode, nextItineraryNode);
83
+ itineraryNode.edges.push(itineraryEdge);
84
+ nextItineraryNode.edges.push(itineraryEdge);
85
+
86
+ itinerary.edges.push(itineraryEdge);
87
+ itinerary.length += itineraryEdge.length;
25
88
  }
26
- return this.edges[1];
89
+
90
+ return itinerary;
27
91
  }
28
92
 
29
- get distance() {
30
- let dist = 0;
31
- this.edges.forEach(edge => (dist += edge.getLength()));
32
- return dist;
93
+ /**
94
+ * Convert lng/lat points to Itinerary
95
+ * @param {2DArray} points 2D points array of lng/lat (or lat/lng see lngLatFormat)
96
+ * @param {Boolean} lngLatFormat true: lng/lat (default), false: lat/lng
97
+ */
98
+ static fromOrderedPointsArray(points, start, end, lngLatFormat) {
99
+ const route = new Itinerary();
100
+ route.length = 0;
101
+ route.start = new WGS84(start[lngLatFormat ? 1 : 0],
102
+ start[lngLatFormat ? 0 : 1]);
103
+ route.end = new WGS84(end[lngLatFormat ? 1 : 0],
104
+ end[lngLatFormat ? 0 : 1]);
105
+
106
+ let lastPoint = null;
107
+
108
+ for (let i = 0; i < points.length; i++) {
109
+
110
+ const currentPoint = new Node(
111
+ new WGS84(points[i][lngLatFormat ? 1 : 0],
112
+ points[i][lngLatFormat ? 0 : 1])
113
+ );
114
+
115
+ if (lastPoint) {
116
+ const edge = new Edge(lastPoint, currentPoint);
117
+
118
+ route.edges.push(edge);
119
+ route.length += edge.length;
120
+
121
+ currentPoint.edges.push(edge);
122
+ lastPoint.edges.push(edge);
123
+ }
124
+
125
+ route.nodes.push(currentPoint);
126
+ lastPoint = currentPoint;
127
+ }
128
+
129
+ return route;
33
130
  }
34
- }
35
131
 
132
+ }
36
133
  export default Itinerary;
@@ -1,10 +1,10 @@
1
- /* eslint complexity: ["error", 22]*/
2
- /* eslint max-statements: ["error", 45]*/
1
+ /* eslint-disable max-statements */
2
+ /* eslint-disable complexity */
3
+ import isNumber from 'lodash/isnumber';
3
4
 
4
- import { Utils as MathUtils } from '@wemap/maths';
5
- import Network from './Network';
5
+ import { rad2deg } from '@wemap/maths';
6
6
 
7
- const rad2deg = MathUtils.rad2deg;
7
+ import Network from './Network';
8
8
 
9
9
  class MapMatching {
10
10
 
@@ -12,7 +12,7 @@ class MapMatching {
12
12
 
13
13
  /**
14
14
  * Constructor of Map-matching
15
- * @param {Network} network List of Edges
15
+ * @param {Network} network
16
16
  */
17
17
  constructor(network) {
18
18
  if (network) {
@@ -35,7 +35,7 @@ class MapMatching {
35
35
  this.network = network;
36
36
  }
37
37
 
38
- getProjection(location, useDistance = true, useBearing = true) {
38
+ getProjection(location, useDistance = true, useBearing = true, useMultiLevelSegments = true, acceptEdgeFn = () => true) {
39
39
 
40
40
  if (!this.network) {
41
41
  return null;
@@ -56,29 +56,42 @@ class MapMatching {
56
56
  for (let i = 0; i < this.network.edges.length; i++) {
57
57
  const edge = this.network.edges[i];
58
58
 
59
+ if (!acceptEdgeFn(edge)) {
60
+ continue;
61
+ }
62
+
63
+ if (!useMultiLevelSegments && edge.level && edge.level.isRange) {
64
+ continue;
65
+ }
66
+
67
+ if (location.level && !location.level.intersect(edge.level)
68
+ || !location.level && edge.level) {
69
+ continue;
70
+ }
71
+
59
72
  if (useBearing) {
60
- const edgeBearing = rad2deg(edge.getBearing());
73
+ const edgeBearing = rad2deg(edge.bearing);
61
74
  const diffAngle = MapMatching.diffAngle90(edgeBearing, location.bearing);
62
75
  if (diffAngle > this._maxAngleBearing) {
63
76
  continue;
64
77
  }
65
78
  } else {
66
79
  // Check node 1
67
- const distNode1 = location.distanceTo(edge.node1);
80
+ const distNode1 = location.distanceTo(edge.node1.coords);
68
81
  if (distNode1 < projection.distanceFromNearestElement
69
82
  && (!useDistance || distNode1 <= this._maxDistance)) {
70
83
  projection.distanceFromNearestElement = distNode1;
71
84
  projection.nearestElement = edge.node1;
72
- projection.projection = edge.node1.clone();
85
+ projection.projection = edge.node1.coords.clone();
73
86
  }
74
87
 
75
88
  // Check node 2
76
- const distNode2 = location.distanceTo(edge.node2);
89
+ const distNode2 = location.distanceTo(edge.node2.coords);
77
90
  if (distNode2 < projection.distanceFromNearestElement
78
91
  && (!useDistance || distNode2 <= this._maxDistance)) {
79
92
  projection.distanceFromNearestElement = distNode2;
80
93
  projection.nearestElement = edge.node2;
81
- projection.projection = edge.node2.clone();
94
+ projection.projection = edge.node2.coords.clone();
82
95
  }
83
96
  }
84
97
 
@@ -97,7 +110,9 @@ class MapMatching {
97
110
 
98
111
  if (!projection.projection) {
99
112
  return null;
100
- } else if (typeof projection.projection.alt === 'undefined') {
113
+ }
114
+
115
+ if (!isNumber(projection.projection.alt)) {
101
116
  projection.projection.alt = location.alt;
102
117
  }
103
118
 
@@ -1,27 +1,52 @@
1
1
  import chai from 'chai';
2
2
  import chaiAlmost from 'chai-almost';
3
3
 
4
+ import {
5
+ WGS84, WGS84UserPosition
6
+ } from '@wemap/geo';
7
+
4
8
  import Edge from './Edge';
5
- import MapMatching from './MapMatching';
9
+ import Node from './Node';
6
10
  import Network from './Network';
7
- import WGS84 from '../coordinates/WGS84';
8
- import WGS84UserPosition from '../coordinates/WGS84UserPosition';
11
+ import MapMatching from './MapMatching';
9
12
 
10
13
  const expect = chai.expect;
11
14
  chai.use(chaiAlmost());
12
15
 
16
+
17
+ function edgeFromTwoNodes(node1, node2) {
18
+ if (!(node1 instanceof Node)) {
19
+ throw new TypeError('node1 is not an instance of Node');
20
+ }
21
+
22
+ if (!(node2 instanceof Node)) {
23
+ throw new TypeError('node2 is not an instance of Node');
24
+ }
25
+ const edge = new Edge(node1, node2);
26
+ node1.edges.push(edge);
27
+ node2.edges.push(edge);
28
+ return edge;
29
+ }
30
+
31
+ function createNetworkFromEdgesAndNodes(edges, nodes) {
32
+ const network = new Network();
33
+ network.edges = edges;
34
+ network.nodes = nodes;
35
+ return network;
36
+ }
37
+
13
38
  describe('matching', () => {
14
39
  it('Should return the good value', () => {
15
40
 
16
- const p1 = new WGS84(43.6091883, 3.8841242);
17
- const p2 = new WGS84(43.6091709, 3.8842382);
18
- const p3 = new WGS84(43.6091288, 3.884226);
19
- const p4 = new WGS84(43.6091461, 3.884112);
20
- const e1 = new Edge(p1, p2);
21
- const e2 = new Edge(p2, p3);
22
- const e3 = new Edge(p3, p4);
23
- const e4 = new Edge(p4, p1);
24
- const network = new Network([e1, e2, e3, e4]);
41
+ const n1 = new Node(new WGS84(43.6091883, 3.8841242));
42
+ const n2 = new Node(new WGS84(43.6091709, 3.8842382));
43
+ const n3 = new Node(new WGS84(43.6091288, 3.884226));
44
+ const n4 = new Node(new WGS84(43.6091461, 3.884112));
45
+ const e1 = edgeFromTwoNodes(n1, n2);
46
+ const e2 = edgeFromTwoNodes(n2, n3);
47
+ const e3 = edgeFromTwoNodes(n3, n4);
48
+ const e4 = edgeFromTwoNodes(n4, n1);
49
+ const network = createNetworkFromEdgesAndNodes([e1, e2, e3, e4], [n1, n2, n3, n4]);
25
50
 
26
51
  const mapMatching = new MapMatching(network);
27
52
  mapMatching.maxAngleBearing = 30;
@@ -1,33 +1,26 @@
1
- import Edge from './Edge';
2
-
1
+ /**
2
+ * A typical network with nodes (Node) and edges (Edge)
3
+ */
3
4
  class Network {
4
5
 
5
- constructor(edges = []) {
6
- this.addEdges(edges);
6
+ constructor() {
7
+ this.nodes = [];
8
+ this.edges = [];
7
9
  }
8
10
 
9
- addEdges(edges) {
10
- Network.checkEdgesArray(edges);
11
- if (!this.edges) {
12
- this.edges = [];
13
- }
14
- this.edges = this.edges.concat(edges);
11
+ getNodeByCoords(coords) {
12
+ return this.nodes.find(node => node.coords.equalsTo(coords));
15
13
  }
16
14
 
17
- get length() {
18
- return this.edges.length;
15
+ getEdgeByNodes(node1, node2) {
16
+ return Network.getEdgeByNodes(this.edges, node1, node2);
19
17
  }
20
18
 
21
- static checkEdgesArray(edges) {
22
- if (!Array.isArray(edges)) {
23
- throw new TypeError('Network creation: edges param is not an Array');
24
- }
25
-
26
- edges.forEach(edge => {
27
- if (!(edge instanceof Edge)) {
28
- throw new TypeError('Network creation: ' + edge + ' is not an Edge');
29
- }
30
- });
19
+ static getEdgeByNodes(edges, node1, node2) {
20
+ return edges.find(edge =>
21
+ node1 === edge.node1 && node2 === edge.node2
22
+ || node2 === edge.node1 && node1 === edge.node2
23
+ );
31
24
  }
32
25
  }
33
26
 
@@ -0,0 +1,26 @@
1
+ class Node {
2
+
3
+ constructor(coords, data) {
4
+ this.coords = coords;
5
+ this.data = data;
6
+ this.edges = [];
7
+ }
8
+
9
+ distanceTo(other) {
10
+ return this.coords.distanceTo(other.coords);
11
+ }
12
+
13
+ bearingTo(other) {
14
+ return this.coords.bearingTo(other.coords);
15
+ }
16
+
17
+ equalsTo(other) {
18
+ return this.coords.equalsTo(other.coords);
19
+ }
20
+
21
+ clone() {
22
+ return new Node(this.coords, this.data);
23
+ }
24
+ }
25
+
26
+ export default Node;