@wemap/osm 2.7.6 → 2.7.8
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/assets/itinerary-montpellier-outdoor-errored.json +1 -0
- package/assets/itinerary-montpellier-outdoor-without-steps.json +1 -0
- package/assets/network-conveying-backward.osm +74 -0
- package/assets/network-elevator.osm +48 -0
- package/assets/network-simple.osm +27 -0
- package/assets/network-with-modifiers.osm +39 -0
- package/assets/simple.osm +31 -0
- package/package.json +5 -5
- package/src/model/OsmElement.js +5 -2
- package/src/model/OsmElement.spec.js +11 -0
- package/src/model/OsmModel.js +11 -9
- package/src/model/OsmModel.spec.js +57 -0
- package/src/model/OsmNode.js +5 -13
- package/src/model/OsmNode.spec.js +14 -0
- package/src/model/OsmParser.js +18 -6
- package/src/model/OsmParser.spec.js +49 -0
- package/src/model/OsmWay.js +4 -8
- package/src/network/OsmNetwork.js +7 -8
- package/src/network/OsmNetwork.spec.js +103 -0
- package/src/network/OsmRouter.js +13 -15
- package/src/network/OsmRouter.spec.js +34 -19
- package/src/osrm/OsrmUtils.js +61 -61
- package/src/osrm/OsrmUtils.spec.js +184 -97
- /package/assets/{itinerary-osrm.json → itinerary-montpellier-outdoor.json} +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
import OsmParser from '../model/OsmParser';
|
|
7
|
+
import OsmNetwork from './OsmNetwork';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const loadFile = fileName => {
|
|
11
|
+
const filePath = path.resolve(__dirname, '../../assets/' + fileName);
|
|
12
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe('OsmNetwork - simple', () => {
|
|
16
|
+
|
|
17
|
+
let osmModel, osmNetwork;
|
|
18
|
+
const osmXmlString = loadFile('network-simple.osm');
|
|
19
|
+
|
|
20
|
+
it('Network creation', () => {
|
|
21
|
+
|
|
22
|
+
osmModel = OsmParser.parseOsmXmlString(osmXmlString);
|
|
23
|
+
osmNetwork = OsmNetwork.fromOsmModel(osmModel);
|
|
24
|
+
|
|
25
|
+
expect(osmNetwork.nodes.length).equals(3);
|
|
26
|
+
expect(osmNetwork.edges.length).equals(2);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
it('Network creation - waySelectionFilter', () => {
|
|
31
|
+
|
|
32
|
+
const selectionNetwork = OsmNetwork.fromOsmModel(osmModel, () => true);
|
|
33
|
+
|
|
34
|
+
expect(selectionNetwork.nodes.length).equals(4);
|
|
35
|
+
expect(selectionNetwork.edges.length).equals(3);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('Network creation - elevator', () => {
|
|
39
|
+
|
|
40
|
+
const elevatorModel = OsmParser.parseOsmXmlString(loadFile('network-elevator.osm'));
|
|
41
|
+
const elevatorNetwork = OsmNetwork.fromOsmModel(elevatorModel);
|
|
42
|
+
|
|
43
|
+
expect(elevatorNetwork.nodes.length).equals(7);
|
|
44
|
+
expect(elevatorNetwork.edges.length).equals(6);
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
const osmModel2 = OsmParser.parseOsmXmlString(`
|
|
48
|
+
<osm>
|
|
49
|
+
<node id='1' lat='43' lon='4'>
|
|
50
|
+
<tag k='highway' v='elevator' />
|
|
51
|
+
<tag k='level' v='0;1' />
|
|
52
|
+
</node>
|
|
53
|
+
<node id='2' lat='43' lon='3' />
|
|
54
|
+
<way id='3'>
|
|
55
|
+
<nd ref='1' />
|
|
56
|
+
<nd ref='2' />
|
|
57
|
+
<tag k='highway' v='footway' />
|
|
58
|
+
<tag k='level' v='0;1' />
|
|
59
|
+
</way>
|
|
60
|
+
</osm>
|
|
61
|
+
`);
|
|
62
|
+
expect(() => OsmNetwork.fromOsmModel(osmModel2)).throw(Error);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
it('getNodeById', () => {
|
|
67
|
+
let node = osmNetwork.getNodeById(1);
|
|
68
|
+
expect(node).not.null;
|
|
69
|
+
expect(node.data.tags.name).equals('p1');
|
|
70
|
+
|
|
71
|
+
node = osmNetwork.getNodeById(5);
|
|
72
|
+
expect(node).undefined;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
it('getNodeByName', () => {
|
|
77
|
+
let node = osmNetwork.getNodeByName('p1');
|
|
78
|
+
expect(node).not.undefined;
|
|
79
|
+
|
|
80
|
+
node = osmNetwork.getNodeByName('p5');
|
|
81
|
+
expect(node).undefined;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('getEdgesById', () => {
|
|
85
|
+
let edges = osmNetwork.getEdgesById(101);
|
|
86
|
+
expect(edges.length).equals(2);
|
|
87
|
+
expect(edges[0].data.tags.name).equals('w1');
|
|
88
|
+
expect(edges[0].node1.data.tags.name).equals('p1');
|
|
89
|
+
expect(edges[0].node2.data.tags.name).equals('p2');
|
|
90
|
+
expect(edges[1].data.tags.name).equals('w1');
|
|
91
|
+
expect(edges[1].node1.data.tags.name).equals('p2');
|
|
92
|
+
expect(edges[1].node2.data.tags.name).equals('p3');
|
|
93
|
+
|
|
94
|
+
edges = osmNetwork.getEdgesById(3);
|
|
95
|
+
expect(edges.length).equals(0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('toDetailedString', () => {
|
|
99
|
+
expect(() => osmNetwork.toDetailedString()).not.throw(Error);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
|
package/src/network/OsmRouter.js
CHANGED
|
@@ -20,28 +20,26 @@ class OsmRouter extends GraphRouter {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const edgeWeightFn = edge => {
|
|
23
|
-
if (edge.data
|
|
23
|
+
if (edge.data.tags.highway === 'elevator') {
|
|
24
24
|
return 30;
|
|
25
25
|
}
|
|
26
26
|
return Utils.getDurationFromLength(edge.length);
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const acceptOneWayFn = (edge, reversed) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
const {
|
|
31
|
+
oneway, highway, conveying
|
|
32
|
+
} = edge.data.tags;
|
|
33
|
+
if (reversed && (oneway === 'yes' || oneway === 'true' || oneway === '1')) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (conveying && highway) {
|
|
37
|
+
if (conveying === 'forward' && reversed) {
|
|
38
|
+
return false;
|
|
39
|
+
} if (conveying === 'backward' && !reversed) {
|
|
40
|
+
return false;
|
|
41
|
+
} if (conveying === 'yes' && reversed) {
|
|
35
42
|
return false;
|
|
36
|
-
}
|
|
37
|
-
if (conveying && highway) {
|
|
38
|
-
if (conveying === 'forward' && reversed) {
|
|
39
|
-
return false;
|
|
40
|
-
} if (conveying === 'backward' && !reversed) {
|
|
41
|
-
return false;
|
|
42
|
-
} if (conveying === 'yes' && reversed) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
return true;
|
|
@@ -12,9 +12,16 @@ import OsmParser from '../model/OsmParser';
|
|
|
12
12
|
import OsmRouter from './OsmRouter';
|
|
13
13
|
import OsmNetwork from './OsmNetwork';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const itineraryStart = new WGS84(43.6092754, 3.8842306, null, new Level(2));
|
|
16
|
+
const itineraryEnd = new WGS84(43.6092602, 3.8842669, null, new Level(1));
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const loadNetwork = fileName => {
|
|
20
|
+
const filePath = path.resolve(__dirname, '../../assets/' + fileName);
|
|
21
|
+
const osmXmlString = fs.readFileSync(filePath, 'utf8');
|
|
22
|
+
const osmModel = OsmParser.parseOsmXmlString(osmXmlString);
|
|
23
|
+
return OsmNetwork.fromOsmModel(osmModel);
|
|
24
|
+
};
|
|
18
25
|
|
|
19
26
|
const verifyNodesOrder = (nodes, names) => {
|
|
20
27
|
expect(nodes.length).equals(names.length);
|
|
@@ -32,14 +39,9 @@ const generateNodeNames = (start, end) =>
|
|
|
32
39
|
|
|
33
40
|
describe('OsmRouter - Multi-level itinerary', () => {
|
|
34
41
|
|
|
35
|
-
const
|
|
36
|
-
const osmXmlString = fs.readFileSync(filePath, 'utf8');
|
|
37
|
-
|
|
38
|
-
const osmModel = OsmParser.parseOsmXmlString(osmXmlString);
|
|
39
|
-
const networkModel = OsmNetwork.fromOsmModel(osmModel);
|
|
42
|
+
const networkModel = loadNetwork('bureaux-wemap-montpellier-network.osm');
|
|
40
43
|
const router = new OsmRouter(networkModel);
|
|
41
44
|
|
|
42
|
-
|
|
43
45
|
const itinerary = router.getShortestPath(itineraryStart, itineraryEnd);
|
|
44
46
|
|
|
45
47
|
const p = [];
|
|
@@ -93,11 +95,7 @@ describe('OsmRouter - Multi-level itinerary', () => {
|
|
|
93
95
|
|
|
94
96
|
describe('OsmRouter - One Way itinerary', () => {
|
|
95
97
|
|
|
96
|
-
const
|
|
97
|
-
const osmXmlString = fs.readFileSync(filePath, 'utf8');
|
|
98
|
-
|
|
99
|
-
const osmModel = OsmParser.parseOsmXmlString(osmXmlString);
|
|
100
|
-
const networkModel = OsmNetwork.fromOsmModel(osmModel);
|
|
98
|
+
const networkModel = loadNetwork('one-way.osm');
|
|
101
99
|
const router = new OsmRouter(networkModel);
|
|
102
100
|
|
|
103
101
|
it('do not use oneway', () => {
|
|
@@ -118,11 +116,7 @@ describe('OsmRouter - One Way itinerary', () => {
|
|
|
118
116
|
|
|
119
117
|
describe('OsmRouter - Conveying', () => {
|
|
120
118
|
|
|
121
|
-
const
|
|
122
|
-
const osmXmlString = fs.readFileSync(filePath, 'utf8');
|
|
123
|
-
|
|
124
|
-
const osmModel = OsmParser.parseOsmXmlString(osmXmlString);
|
|
125
|
-
const networkModel = OsmNetwork.fromOsmModel(osmModel);
|
|
119
|
+
const networkModel = loadNetwork('gare-de-lyon-extract.osm');
|
|
126
120
|
const router = new OsmRouter(networkModel);
|
|
127
121
|
|
|
128
122
|
it('do not use oneway conveying', () => {
|
|
@@ -141,3 +135,24 @@ describe('OsmRouter - Conveying', () => {
|
|
|
141
135
|
});
|
|
142
136
|
|
|
143
137
|
|
|
138
|
+
describe('OsmRouter - Conveying - backward', () => {
|
|
139
|
+
|
|
140
|
+
const networkModel = loadNetwork('network-conveying-backward.osm');
|
|
141
|
+
const router = new OsmRouter(networkModel);
|
|
142
|
+
|
|
143
|
+
it('do not use oneway conveying', () => {
|
|
144
|
+
|
|
145
|
+
const start = networkModel.getNodeByName('p6');
|
|
146
|
+
const end = networkModel.getNodeByName('p13');
|
|
147
|
+
|
|
148
|
+
const itinerary = router.getShortestPath(start, end);
|
|
149
|
+
expect(itinerary).is.not.undefined;
|
|
150
|
+
verifyNodesOrder(itinerary.nodes, ['p6', 'p7', 'p10', 'p11', 'p12', 'p13']);
|
|
151
|
+
|
|
152
|
+
const itineraryOtherWay = router.getShortestPath(end, start);
|
|
153
|
+
expect(itineraryOtherWay).is.not.undefined;
|
|
154
|
+
verifyNodesOrder(itineraryOtherWay.nodes, ['p13', 'p12', 'p9', 'p8', 'p6']);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
|
package/src/osrm/OsrmUtils.js
CHANGED
|
@@ -8,31 +8,10 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
rad2deg, Utils as MathUtils, diffAngle, deg2rad
|
|
10
10
|
} from '@wemap/maths';
|
|
11
|
-
import Logger from '@wemap/logger';
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class OsrmUtils {
|
|
15
14
|
|
|
16
|
-
static wgs84ToJson(wgs84) {
|
|
17
|
-
const output = [wgs84.lng, wgs84.lat];
|
|
18
|
-
if (wgs84.level) {
|
|
19
|
-
output.push(wgs84.level.toString());
|
|
20
|
-
}
|
|
21
|
-
return output;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static jsonToWgs84(json) {
|
|
25
|
-
const output = new WGS84(json[1], json[0]);
|
|
26
|
-
if (json.length > 2) {
|
|
27
|
-
output.level = Level.fromString(json[2]);
|
|
28
|
-
}
|
|
29
|
-
return output;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
static nodesToJsonCoords(nodes) {
|
|
33
|
-
return nodes.map(node => OsrmUtils.wgs84ToJson(node.coords));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
15
|
static itineraryToOsrmJson(itinerary) {
|
|
37
16
|
|
|
38
17
|
const itinerarySteps = itinerary.steps;
|
|
@@ -117,42 +96,6 @@ class OsrmUtils {
|
|
|
117
96
|
};
|
|
118
97
|
}
|
|
119
98
|
|
|
120
|
-
static noRouteFoundJson(message) {
|
|
121
|
-
return {
|
|
122
|
-
'code': 'NoRoute',
|
|
123
|
-
message
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
static getModifierFromAngle(_angle) {
|
|
129
|
-
|
|
130
|
-
const angle = MathUtils.positiveMod(rad2deg(_angle), 360);
|
|
131
|
-
|
|
132
|
-
if (angle > 0 && angle < 60) {
|
|
133
|
-
return 'sharp right';
|
|
134
|
-
}
|
|
135
|
-
if (angle >= 60 && angle < 140) {
|
|
136
|
-
return 'right';
|
|
137
|
-
}
|
|
138
|
-
if (angle >= 140 && angle < 160) {
|
|
139
|
-
return 'slight right';
|
|
140
|
-
}
|
|
141
|
-
if (angle >= 160 && angle <= 200) {
|
|
142
|
-
return 'straight';
|
|
143
|
-
}
|
|
144
|
-
if (angle > 200 && angle <= 220) {
|
|
145
|
-
return 'slight left';
|
|
146
|
-
}
|
|
147
|
-
if (angle > 220 && angle <= 300) {
|
|
148
|
-
return 'left';
|
|
149
|
-
}
|
|
150
|
-
if (angle > 300 && angle < 360) {
|
|
151
|
-
return 'sharp left';
|
|
152
|
-
}
|
|
153
|
-
return 'u turn';
|
|
154
|
-
}
|
|
155
|
-
|
|
156
99
|
/**
|
|
157
100
|
* Generate Itinerary from OSRM JSON, start and end.
|
|
158
101
|
* @param {Object} json JSON file provided by OSRM.
|
|
@@ -168,7 +111,6 @@ class OsrmUtils {
|
|
|
168
111
|
const {
|
|
169
112
|
legs, geometry
|
|
170
113
|
} = json.routes[0];
|
|
171
|
-
const { steps } = legs[0];
|
|
172
114
|
|
|
173
115
|
/**
|
|
174
116
|
* Create nodes and edges from geometry
|
|
@@ -188,7 +130,8 @@ class OsrmUtils {
|
|
|
188
130
|
previousNode = node;
|
|
189
131
|
}
|
|
190
132
|
|
|
191
|
-
if (
|
|
133
|
+
if (legs && legs[0]) {
|
|
134
|
+
const { steps } = legs[0];
|
|
192
135
|
OsrmUtils.parseSteps(itinerary, steps);
|
|
193
136
|
}
|
|
194
137
|
|
|
@@ -226,8 +169,8 @@ class OsrmUtils {
|
|
|
226
169
|
const wgs84 = OsrmUtils.jsonToWgs84(coords);
|
|
227
170
|
const node = itinerary.getNodeByCoords(wgs84);
|
|
228
171
|
if (!node) {
|
|
229
|
-
|
|
230
|
-
|
|
172
|
+
throw new Error('Cannot parse these step coordinates, '
|
|
173
|
+
+ 'they are not found in main itinerary: ' + wgs84.toString());
|
|
231
174
|
}
|
|
232
175
|
step.nodes.push(node);
|
|
233
176
|
|
|
@@ -282,6 +225,63 @@ class OsrmUtils {
|
|
|
282
225
|
itinerary._steps.push(step);
|
|
283
226
|
}
|
|
284
227
|
}
|
|
228
|
+
|
|
229
|
+
static getModifierFromAngle(_angle) {
|
|
230
|
+
|
|
231
|
+
const angle = MathUtils.positiveMod(rad2deg(_angle), 360);
|
|
232
|
+
|
|
233
|
+
if (angle > 0 && angle < 60) {
|
|
234
|
+
return 'sharp right';
|
|
235
|
+
}
|
|
236
|
+
if (angle >= 60 && angle < 140) {
|
|
237
|
+
return 'right';
|
|
238
|
+
}
|
|
239
|
+
if (angle >= 140 && angle < 160) {
|
|
240
|
+
return 'slight right';
|
|
241
|
+
}
|
|
242
|
+
if (angle >= 160 && angle <= 200) {
|
|
243
|
+
return 'straight';
|
|
244
|
+
}
|
|
245
|
+
if (angle > 200 && angle <= 220) {
|
|
246
|
+
return 'slight left';
|
|
247
|
+
}
|
|
248
|
+
if (angle > 220 && angle <= 300) {
|
|
249
|
+
return 'left';
|
|
250
|
+
}
|
|
251
|
+
if (angle > 300 && angle < 360) {
|
|
252
|
+
return 'sharp left';
|
|
253
|
+
}
|
|
254
|
+
return 'u turn';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static wgs84ToJson(wgs84) {
|
|
258
|
+
const output = [wgs84.lng, wgs84.lat];
|
|
259
|
+
if (wgs84.level) {
|
|
260
|
+
output.push(wgs84.level.toString());
|
|
261
|
+
}
|
|
262
|
+
return output;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
static jsonToWgs84(json) {
|
|
266
|
+
const output = new WGS84(json[1], json[0]);
|
|
267
|
+
if (json.length > 2) {
|
|
268
|
+
output.level = Level.fromString(json[2]);
|
|
269
|
+
}
|
|
270
|
+
return output;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
static nodesToJsonCoords(nodes) {
|
|
274
|
+
return nodes.map(node => OsrmUtils.wgs84ToJson(node.coords));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
static noRouteFoundJson(message) {
|
|
279
|
+
return {
|
|
280
|
+
'code': 'NoRoute',
|
|
281
|
+
message
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
export default OsrmUtils;
|