@wemap/routers 6.0.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/assets/bureaux-wemap-montpellier-network.osm +162 -0
- package/assets/gare-de-lyon-extract.osm +174 -0
- package/assets/itinerary-deutsche-bahn-1.json +368 -0
- package/assets/itinerary-grenoble-otp-1.json +1536 -0
- package/assets/itinerary-grenoble-otp-2.json +1092 -0
- package/assets/itinerary-lehavre-cityway-1.json +6799 -0
- package/assets/itinerary-lehavre-cityway-2.json +2133 -0
- package/assets/itinerary-lehavre-cityway-3.json +12577 -0
- package/assets/itinerary-lehavre-cityway-4.json +1451 -0
- package/assets/itinerary-lehavre-cityway-5.json +5925 -0
- package/assets/itinerary-montpellier-osrm-3.json +1 -0
- package/assets/itinerary-montpellier-outdoor-without-steps.json +110 -0
- package/assets/itinerary-montpellier-outdoor.json +513 -0
- package/assets/itinerary-with-duplicate-nodes.json +110 -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/one-way.osm +46 -0
- package/dist/wemap-osm.es.js +2012 -0
- package/dist/wemap-osm.es.js.map +1 -0
- package/index.js +24 -0
- package/package.json +35 -0
- package/src/ItineraryInfoManager.js +148 -0
- package/src/ItineraryInfoManager.spec.js +54 -0
- package/src/Utils.js +64 -0
- package/src/cityway/CitywayUtils.js +240 -0
- package/src/cityway/CitywayUtils.spec.js +109 -0
- package/src/deutsche-bahn/DeutscheBahnRouterUtils.js +91 -0
- package/src/deutsche-bahn/DeutscheBahnRouterUtils.spec.js +54 -0
- package/src/model/Itinerary.js +197 -0
- package/src/model/Itinerary.type.spec.js +105 -0
- package/src/model/ItineraryInfo.js +34 -0
- package/src/model/Leg.js +113 -0
- package/src/model/LevelChange.js +65 -0
- package/src/model/RouterResponse.js +19 -0
- package/src/model/RouterResponse.type.spec.js +24 -0
- package/src/model/Step.js +118 -0
- package/src/osrm/OsrmUtils.js +269 -0
- package/src/osrm/OsrmUtils.spec.js +457 -0
- package/src/otp/OtpUtils.js +150 -0
- package/src/otp/OtpUtils.spec.js +97 -0
- package/src/wemap/WemapNetworkUtils.js +195 -0
- package/src/wemap/WemapNetworkUtils.spec.js +109 -0
- package/src/wemap/WemapRouter.js +27 -0
- package/src/wemap/WemapRouter.spec.js +221 -0
- package/src/wemap/WemapRouterOptions.js +32 -0
- package/src/wemap/WemapRouterUtils.js +46 -0
- package/src/wemap/WemapStepsGeneration.js +104 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
import chai from 'chai';
|
|
3
|
+
import chaiAlmost from 'chai-almost';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
import { Coordinates } from '@wemap/geo';
|
|
9
|
+
|
|
10
|
+
import { createRouterResponseFromJson } from './OtpUtils.js';
|
|
11
|
+
|
|
12
|
+
import { verifyRouterResponseData } from '../model/RouterResponse.type.spec.js';
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const { expect } = chai;
|
|
16
|
+
chai.use(chaiAlmost(0.1));
|
|
17
|
+
|
|
18
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
|
|
20
|
+
describe('OtpUtils - createRouterResponseFromJson', () => {
|
|
21
|
+
|
|
22
|
+
it('RouterResponse - 1', () => {
|
|
23
|
+
|
|
24
|
+
const filePath = path.resolve(__dirname, '../../assets/itinerary-grenoble-otp-1.json');
|
|
25
|
+
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
26
|
+
const json = JSON.parse(fileString);
|
|
27
|
+
|
|
28
|
+
const routerResponse = createRouterResponseFromJson(json);
|
|
29
|
+
verifyRouterResponseData(routerResponse);
|
|
30
|
+
|
|
31
|
+
expect(routerResponse.routerName).equal('otp');
|
|
32
|
+
expect(routerResponse.itineraries.length).equal(2);
|
|
33
|
+
expect(routerResponse.from.equalsTo(new Coordinates(45.187291, 5.723455))).true;
|
|
34
|
+
expect(routerResponse.to.equalsTo(new Coordinates(45.163729, 5.74085))).true;
|
|
35
|
+
|
|
36
|
+
const itinerary1 = routerResponse.itineraries[0];
|
|
37
|
+
expect(itinerary1.coords.length).equal(162);
|
|
38
|
+
expect(itinerary1.duration).equal(1206);
|
|
39
|
+
expect(itinerary1.startTime).equal(1614607210000);
|
|
40
|
+
expect(itinerary1.endTime).equal(1614608416000);
|
|
41
|
+
expect(itinerary1.legs.length).equal(3);
|
|
42
|
+
|
|
43
|
+
const itinerary1leg1 = itinerary1.legs[0];
|
|
44
|
+
expect(itinerary1leg1.startTime).equal(1614607210000);
|
|
45
|
+
expect(itinerary1leg1.endTime).equal(1614607499000);
|
|
46
|
+
expect(itinerary1leg1.distance).almost.equal(260.14);
|
|
47
|
+
expect(itinerary1leg1.mode).equal('WALK');
|
|
48
|
+
expect(itinerary1leg1.transportInfo).is.null;
|
|
49
|
+
expect(itinerary1leg1.from.name).equal('Origin');
|
|
50
|
+
expect(itinerary1leg1.from.coords.equalsTo(new Coordinates(45.187291, 5.723455))).true;
|
|
51
|
+
expect(itinerary1leg1.to.name).equal('Grenoble, Victor Hugo');
|
|
52
|
+
expect(itinerary1leg1.to.coords.equalsTo(new Coordinates(45.1887, 5.72533))).true;
|
|
53
|
+
|
|
54
|
+
const itinerary1leg2 = itinerary1.legs[1];
|
|
55
|
+
expect(itinerary1leg2.mode).equal('BUS');
|
|
56
|
+
expect(itinerary1leg2.transportInfo).is.not.null;
|
|
57
|
+
expect(itinerary1leg2.transportInfo.name).equal('C4');
|
|
58
|
+
expect(itinerary1leg2.transportInfo.routeColor).equal('F3D000');
|
|
59
|
+
expect(itinerary1leg2.transportInfo.routeTextColor).equal('000000');
|
|
60
|
+
expect(itinerary1leg2.transportInfo.directionName).equal('Eybens, Le Verderet');
|
|
61
|
+
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
it('RouterResponse - 2', () => {
|
|
66
|
+
|
|
67
|
+
const filePath = path.resolve(__dirname, '../../assets/itinerary-grenoble-otp-2.json');
|
|
68
|
+
const fileString = fs.readFileSync(filePath, 'utf8');
|
|
69
|
+
const json = JSON.parse(fileString);
|
|
70
|
+
|
|
71
|
+
const routerResponse = createRouterResponseFromJson(json);
|
|
72
|
+
verifyRouterResponseData(routerResponse);
|
|
73
|
+
|
|
74
|
+
expect(routerResponse.itineraries.length).equal(2);
|
|
75
|
+
expect(routerResponse.from.equalsTo(new Coordinates(45.192514856662456, 5.731452541397871))).true;
|
|
76
|
+
expect(routerResponse.to.equalsTo(new Coordinates(45.18544, 5.73123))).true;
|
|
77
|
+
|
|
78
|
+
const itinerary1 = routerResponse.itineraries[0];
|
|
79
|
+
expect(itinerary1.legs.length).equal(5);
|
|
80
|
+
|
|
81
|
+
expect(itinerary1.legs[0].mode).equal('WALK');
|
|
82
|
+
|
|
83
|
+
expect(itinerary1.legs[1].mode).equal('TRAM');
|
|
84
|
+
expect(itinerary1.legs[1].transportInfo).is.not.null;
|
|
85
|
+
|
|
86
|
+
expect(itinerary1.legs[2].mode).equal('WALK');
|
|
87
|
+
|
|
88
|
+
expect(itinerary1.legs[3].mode).equal('TRAM');
|
|
89
|
+
expect(itinerary1.legs[3].transportInfo).is.not.null;
|
|
90
|
+
|
|
91
|
+
expect(itinerary1.legs[4].mode).equal('WALK');
|
|
92
|
+
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
});
|
|
97
|
+
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Level, GraphEdge, GraphNode, Network } from '@wemap/geo';
|
|
2
|
+
import { OsmElement, OsmModel, OsmNode, OsmWay } from '@wemap/osm';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export const HIGHWAYS_PEDESTRIANS = ['footway', 'steps', 'pedestrian', 'living_street', 'path', 'track', 'sidewalk'];
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_WAY_SELECTOR = way => {
|
|
8
|
+
return HIGHWAYS_PEDESTRIANS.includes(way.tags.highway)
|
|
9
|
+
|| way.tags.footway === 'sidewalk'
|
|
10
|
+
|| way.tags.public_transport === 'platform'
|
|
11
|
+
|| way.tags.railway === 'platform';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Network<OsmElement>} network
|
|
16
|
+
* @param {string} name
|
|
17
|
+
*/
|
|
18
|
+
export function getNodeByName(network, name) {
|
|
19
|
+
return network.nodes.find(({ builtFrom }) => builtFrom.tags.name === name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {Network<OsmElement>} network
|
|
24
|
+
* @param {string} name
|
|
25
|
+
*/
|
|
26
|
+
export function getEdgeByName(network, name) {
|
|
27
|
+
return network.edges.find(({ builtFrom }) => builtFrom.tags.name === name);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {GraphEdge<OsmElement>} edge
|
|
32
|
+
* @param {OsmWay} way
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
*/
|
|
35
|
+
function manageOneWay(edge, way) {
|
|
36
|
+
|
|
37
|
+
const { highway, oneway, conveying } = way.tags;
|
|
38
|
+
|
|
39
|
+
edge.isOneway = Boolean((oneway === 'yes' || oneway === 'true' || oneway === '1')
|
|
40
|
+
|| (conveying && highway && ['yes', 'forward', 'backward'].includes(conveying)));
|
|
41
|
+
|
|
42
|
+
if (edge.isOneway && conveying === 'backward') {
|
|
43
|
+
const tmpNode = edge.node1;
|
|
44
|
+
edge.node1 = edge.node2;
|
|
45
|
+
edge.node2 = tmpNode;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {Network} networkModel
|
|
51
|
+
* @param {GraphNode} node
|
|
52
|
+
*/
|
|
53
|
+
function createNodesAndEdgesFromElevator(networkModel, node) {
|
|
54
|
+
|
|
55
|
+
/** @type {GraphNode[]} */
|
|
56
|
+
const createdNodes = [];
|
|
57
|
+
const getOrCreateLevelNode = (level, builtFrom) => {
|
|
58
|
+
let levelNode = createdNodes.find(({ coords }) => Level.equalsTo(level, coords.level));
|
|
59
|
+
if (!levelNode) {
|
|
60
|
+
levelNode = new GraphNode(node.coords.clone(), builtFrom);
|
|
61
|
+
levelNode.coords.level = level;
|
|
62
|
+
createdNodes.push(levelNode);
|
|
63
|
+
networkModel.nodes.push(levelNode);
|
|
64
|
+
}
|
|
65
|
+
return levelNode;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Create nodes from node.edges
|
|
69
|
+
node.edges.forEach(edge => {
|
|
70
|
+
if (edge.level.isRange) {
|
|
71
|
+
throw new Error('Cannot handle this elevator edge due to ambiguity');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const levelNode = getOrCreateLevelNode(edge.level, node.builtFrom);
|
|
75
|
+
if (edge.node1 === node) {
|
|
76
|
+
edge.node1 = levelNode;
|
|
77
|
+
} else {
|
|
78
|
+
edge.node2 = levelNode;
|
|
79
|
+
}
|
|
80
|
+
levelNode.edges.push(edge);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Create edges from createdNodes
|
|
84
|
+
for (let i = 0; i < createdNodes.length; i++) {
|
|
85
|
+
for (let j = i + 1; j < createdNodes.length; j++) {
|
|
86
|
+
|
|
87
|
+
const createdNode1 = createdNodes[i];
|
|
88
|
+
const createdNode2 = createdNodes[j];
|
|
89
|
+
|
|
90
|
+
const newEdge = new GraphEdge(
|
|
91
|
+
createdNode1,
|
|
92
|
+
createdNode2,
|
|
93
|
+
new Level(createdNode1.coords.level.val, createdNode2.coords.level.val),
|
|
94
|
+
node.builtFrom
|
|
95
|
+
);
|
|
96
|
+
networkModel.edges.push(newEdge);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Remove the historical elevator node from the network
|
|
101
|
+
networkModel.nodes = networkModel.nodes.filter(_node => _node !== node);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {OsmModel} osmModel
|
|
107
|
+
* @param {function} _waySelectionFilter
|
|
108
|
+
* @returns {Network<OsmElement>}
|
|
109
|
+
*/
|
|
110
|
+
export function createNetworkFromOsmModel(
|
|
111
|
+
osmModel,
|
|
112
|
+
waySelectionFilter = DEFAULT_WAY_SELECTOR
|
|
113
|
+
) {
|
|
114
|
+
|
|
115
|
+
const networkModel = new Network();
|
|
116
|
+
|
|
117
|
+
const nodesCreated = {};
|
|
118
|
+
const elevatorNodes = [];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {OsmNode} osmNode
|
|
122
|
+
*/
|
|
123
|
+
const getOrCreateNode = osmNode => {
|
|
124
|
+
let node = nodesCreated[osmNode.id];
|
|
125
|
+
if (!node) {
|
|
126
|
+
node = new GraphNode(osmNode.coords, osmNode);
|
|
127
|
+
nodesCreated[osmNode.id] = node;
|
|
128
|
+
networkModel.nodes.push(node);
|
|
129
|
+
|
|
130
|
+
if (osmNode.tags.highway === 'elevator') {
|
|
131
|
+
elevatorNodes.push(node);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return node;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
osmModel.ways.forEach(way => {
|
|
138
|
+
if (!waySelectionFilter(way)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let firstNode = getOrCreateNode(way.nodes[0]);
|
|
143
|
+
for (let i = 1; i < way.nodes.length; i++) {
|
|
144
|
+
const secondNode = getOrCreateNode(way.nodes[i]);
|
|
145
|
+
|
|
146
|
+
const edge = new GraphEdge(firstNode, secondNode, way.level, way);
|
|
147
|
+
manageOneWay(edge, way);
|
|
148
|
+
networkModel.edges.push(edge);
|
|
149
|
+
firstNode = secondNode;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
elevatorNodes.forEach(node => {
|
|
155
|
+
// We have to clone this node for each connected edge
|
|
156
|
+
createNodesAndEdgesFromElevator(networkModel, node);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
GraphNode.generateNodesLevels(networkModel.nodes);
|
|
160
|
+
|
|
161
|
+
return networkModel;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
// /**
|
|
166
|
+
// * @param {GraphNode} node
|
|
167
|
+
// * @param {object} tags
|
|
168
|
+
// */
|
|
169
|
+
// static _applyNodePropertiesFromTags(node, tags) {
|
|
170
|
+
// node.name = tags.name || null;
|
|
171
|
+
// node.subwayEntrance = tags.railway === 'subway_entrance';
|
|
172
|
+
// if (node.subwayEntrance && tags.ref) {
|
|
173
|
+
// node.subwayEntranceRef = tags.ref;
|
|
174
|
+
// }
|
|
175
|
+
// }
|
|
176
|
+
|
|
177
|
+
// /**
|
|
178
|
+
// * @param {GraphEdge} edge
|
|
179
|
+
// * @param {object} tags
|
|
180
|
+
// */
|
|
181
|
+
// static _applyEdgePropertiesFromTags(edge, tags) {
|
|
182
|
+
// const { highway, oneway, conveying, name } = tags;
|
|
183
|
+
// edge.name = name || null;
|
|
184
|
+
// edge.isStairs = highway === 'steps';
|
|
185
|
+
// edge.isConveying = 'conveying' in tags;
|
|
186
|
+
// edge.isOneway = Boolean((oneway === 'yes' || oneway === 'true' || oneway === '1')
|
|
187
|
+
// || (conveying && highway && ['yes', 'forward', 'backward'].includes(conveying)));
|
|
188
|
+
|
|
189
|
+
// if (conveying === 'backward') {
|
|
190
|
+
// const tmpNode = edge.node1;
|
|
191
|
+
// edge.node1 = edge.node2;
|
|
192
|
+
// edge.node2 = tmpNode;
|
|
193
|
+
// }
|
|
194
|
+
|
|
195
|
+
// }
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
import chai from 'chai';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
import { Network } from '@wemap/geo';
|
|
8
|
+
import { verifyCoherence } from '@wemap/geo/tests/CommonTest.js';
|
|
9
|
+
|
|
10
|
+
import { OsmModel, OsmParser } from '@wemap/osm';
|
|
11
|
+
|
|
12
|
+
import { createNetworkFromOsmModel, getNodeByName, getEdgeByName } from './WemapNetworkUtils.js';
|
|
13
|
+
|
|
14
|
+
const { expect } = chai;
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
const loadFile = fileName => {
|
|
19
|
+
const filePath = path.resolve(__dirname, '../../assets/' + fileName);
|
|
20
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('OsmNetwork - simple', () => {
|
|
24
|
+
|
|
25
|
+
/** @type {OsmModel} */
|
|
26
|
+
let osmModel;
|
|
27
|
+
|
|
28
|
+
/** @type {Network} */
|
|
29
|
+
let network;
|
|
30
|
+
const osmXmlString = loadFile('network-simple.osm');
|
|
31
|
+
|
|
32
|
+
it('Network creation', () => {
|
|
33
|
+
|
|
34
|
+
osmModel = OsmParser.parseOsmXmlString(osmXmlString);
|
|
35
|
+
network = createNetworkFromOsmModel(osmModel);
|
|
36
|
+
|
|
37
|
+
verifyCoherence(network);
|
|
38
|
+
expect(network.nodes.length).equals(3);
|
|
39
|
+
expect(network.edges.length).equals(2);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
it('Network creation - waySelectionFilter', () => {
|
|
44
|
+
|
|
45
|
+
const selectionNetwork = createNetworkFromOsmModel(osmModel, () => true);
|
|
46
|
+
|
|
47
|
+
verifyCoherence(selectionNetwork);
|
|
48
|
+
expect(selectionNetwork.nodes.length).equals(4);
|
|
49
|
+
expect(selectionNetwork.edges.length).equals(3);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('Network creation - elevator', () => {
|
|
53
|
+
|
|
54
|
+
const elevatorModel = OsmParser.parseOsmXmlString(loadFile('network-elevator.osm'));
|
|
55
|
+
const elevatorNetwork = createNetworkFromOsmModel(elevatorModel);
|
|
56
|
+
|
|
57
|
+
verifyCoherence(elevatorNetwork);
|
|
58
|
+
expect(elevatorNetwork.nodes.length).equals(7);
|
|
59
|
+
expect(elevatorNetwork.edges.length).equals(7);
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
const osmModel2 = OsmParser.parseOsmXmlString(`
|
|
63
|
+
<osm>
|
|
64
|
+
<node id='1' lat='43' lon='4'>
|
|
65
|
+
<tag k='highway' v='elevator' />
|
|
66
|
+
<tag k='level' v='0;1' />
|
|
67
|
+
</node>
|
|
68
|
+
<node id='2' lat='43' lon='3' />
|
|
69
|
+
<way id='3'>
|
|
70
|
+
<nd ref='1' />
|
|
71
|
+
<nd ref='2' />
|
|
72
|
+
<tag k='highway' v='footway' />
|
|
73
|
+
<tag k='level' v='0;1' />
|
|
74
|
+
</way>
|
|
75
|
+
</osm>
|
|
76
|
+
`);
|
|
77
|
+
expect(() => createNetworkFromOsmModel(osmModel2)).throw(Error);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('getNodeByName', () => {
|
|
81
|
+
let node = getNodeByName(network, 'p1');
|
|
82
|
+
expect(node).not.undefined;
|
|
83
|
+
|
|
84
|
+
node = getNodeByName(network, 'p5');
|
|
85
|
+
expect(node).is.undefined;
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
describe('OsmNetwork - with stairs', () => {
|
|
91
|
+
|
|
92
|
+
/** @type {OsmModel} */
|
|
93
|
+
let osmModel;
|
|
94
|
+
|
|
95
|
+
/** @type {Network} */
|
|
96
|
+
let network;
|
|
97
|
+
const osmXmlString = loadFile('bureaux-wemap-montpellier-network.osm');
|
|
98
|
+
|
|
99
|
+
it('Network creation', () => {
|
|
100
|
+
|
|
101
|
+
osmModel = OsmParser.parseOsmXmlString(osmXmlString);
|
|
102
|
+
network = createNetworkFromOsmModel(osmModel);
|
|
103
|
+
|
|
104
|
+
verifyCoherence(network);
|
|
105
|
+
|
|
106
|
+
expect(getEdgeByName(network, 'w6').builtFrom.tags.highway).equal('steps');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Coordinates, GraphItinerary, GraphRouter, Network } from '@wemap/geo';
|
|
2
|
+
import { OsmElement } from '@wemap/osm';
|
|
3
|
+
|
|
4
|
+
import WemapRouterOptions from './WemapRouterOptions.js';
|
|
5
|
+
|
|
6
|
+
class WemapRouter extends GraphRouter {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {!Network<OsmElement>} network
|
|
10
|
+
*/
|
|
11
|
+
constructor(network) {
|
|
12
|
+
super(network);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Coordinates} start
|
|
17
|
+
* @param {Coordinates} end
|
|
18
|
+
* @param {WemapRouterOptions} options
|
|
19
|
+
* @returns {GraphItinerary<OsmElement>}
|
|
20
|
+
*/
|
|
21
|
+
getShortestPath(start, end, options = new WemapRouterOptions()) {
|
|
22
|
+
return super.getShortestPath(start, end, options);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default WemapRouter;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
import chai from 'chai';
|
|
3
|
+
import chaiAlmost from 'chai-almost';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
Level, Coordinates, GraphNode, Network, GraphItinerary
|
|
10
|
+
} from '@wemap/geo';
|
|
11
|
+
import { OsmElement, OsmParser } from '@wemap/osm';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import WemapRouter from './WemapRouter.js';
|
|
15
|
+
import WemapRouterOptions from './WemapRouterOptions.js';
|
|
16
|
+
import WemapStepsGeneration from './WemapStepsGeneration.js';
|
|
17
|
+
import { createNetworkFromOsmModel, getNodeByName } from './WemapNetworkUtils.js';
|
|
18
|
+
|
|
19
|
+
import LevelChange from '../model/LevelChange.js';
|
|
20
|
+
|
|
21
|
+
const { expect } = chai;
|
|
22
|
+
chai.use(chaiAlmost(0.01));
|
|
23
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {GraphItinerary<OsmElement>} _itinerary
|
|
28
|
+
* @returns {string[]}
|
|
29
|
+
*/
|
|
30
|
+
const getNodesNames = _itinerary => {
|
|
31
|
+
return _itinerary.nodes.map(({ builtFrom }) => builtFrom.tags.name || null);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
const itineraryStart = new Coordinates(43.6092754, 3.8842306, null, new Level(2));
|
|
36
|
+
const itineraryEnd = new Coordinates(43.6092602, 3.8842669, null, new Level(1));
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} fileName
|
|
41
|
+
* @returns {Network}
|
|
42
|
+
*/
|
|
43
|
+
const loadNetwork = fileName => {
|
|
44
|
+
const filePath = path.resolve(__dirname, '../../assets/' + fileName);
|
|
45
|
+
const osmXmlString = fs.readFileSync(filePath, 'utf8');
|
|
46
|
+
const osmModel = OsmParser.parseOsmXmlString(osmXmlString);
|
|
47
|
+
return createNetworkFromOsmModel(osmModel);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const generateNodeNames = (start, end) =>
|
|
51
|
+
new Array(end - start + 1).fill().map((_, idx) => 'p' + (idx + start));
|
|
52
|
+
|
|
53
|
+
describe('WemapRouter - Multi-level itinerary', () => {
|
|
54
|
+
|
|
55
|
+
const networkModel = loadNetwork('bureaux-wemap-montpellier-network.osm');
|
|
56
|
+
const router = new WemapRouter(networkModel);
|
|
57
|
+
|
|
58
|
+
const itinerary = router.getShortestPath(itineraryStart, itineraryEnd);
|
|
59
|
+
|
|
60
|
+
/** @type {GraphNode<OsmElement>[]} */
|
|
61
|
+
const p = [];
|
|
62
|
+
|
|
63
|
+
it('Search for nodes', () => {
|
|
64
|
+
for (let i = 1; i <= 16; i++) {
|
|
65
|
+
p[i] = getNodeByName(networkModel, 'p' + i);
|
|
66
|
+
expect(p[i]).instanceOf(GraphNode);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('Router return shortest path', () => {
|
|
71
|
+
expect(getNodesNames(itinerary)).deep.equals(
|
|
72
|
+
['w2', ...generateNodeNames(7, 16)]
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('Verify steps', () => {
|
|
77
|
+
|
|
78
|
+
const steps = WemapStepsGeneration.fromGraphItinerary(itinerary);
|
|
79
|
+
expect(steps.length).equal(8);
|
|
80
|
+
|
|
81
|
+
expect(steps[0].coords.equalsTo(itineraryStart.getSegmentProjection(p[6].coords, p[7].coords))).true;
|
|
82
|
+
expect(steps[0].name).equals('w2');
|
|
83
|
+
expect(steps[0].number).equals(1);
|
|
84
|
+
expect(steps[0].angle).almost.equals(-1.57);
|
|
85
|
+
expect(steps[0].previousBearing).almost.equals(1.778);
|
|
86
|
+
expect(steps[0].nextBearing).almost.equals(0.207);
|
|
87
|
+
expect(steps[0].distance).almost.equals(2.328);
|
|
88
|
+
expect(steps[0].duration).almost.equals(1.676);
|
|
89
|
+
expect(steps[0].firstStep).true;
|
|
90
|
+
expect(steps[0].lastStep).false;
|
|
91
|
+
expect(steps[0].levelChange).is.null;
|
|
92
|
+
expect(steps[0].extras).is.empty;
|
|
93
|
+
|
|
94
|
+
expect(steps[1].coords.equalsTo(p[8].coords)).true;
|
|
95
|
+
expect(steps[1].name).equals('w3');
|
|
96
|
+
expect(steps[1].number).equals(2);
|
|
97
|
+
expect(steps[1].distance).almost.equals(1.764);
|
|
98
|
+
expect(steps[1].firstStep).false;
|
|
99
|
+
expect(steps[1].lastStep).false;
|
|
100
|
+
|
|
101
|
+
expect(steps[2].coords.equalsTo(p[9].coords)).true;
|
|
102
|
+
expect(steps[2].name).equals('w3');
|
|
103
|
+
|
|
104
|
+
expect(steps[3].coords.equalsTo(p[10].coords)).true;
|
|
105
|
+
expect(steps[3].name).equals('w5');
|
|
106
|
+
|
|
107
|
+
expect(steps[4].coords.equalsTo(p[11].coords)).true;
|
|
108
|
+
expect(steps[4].name).equals('w6');
|
|
109
|
+
expect(steps[4].levelChange).instanceOf(LevelChange);
|
|
110
|
+
expect(steps[4].levelChange.difference).equals(-1);
|
|
111
|
+
expect(steps[4].levelChange.direction).equals('down');
|
|
112
|
+
expect(steps[4].levelChange.type).equals('stairs');
|
|
113
|
+
|
|
114
|
+
expect(steps[5].coords.equalsTo(p[14].coords)).true;
|
|
115
|
+
expect(steps[5].name).equals('w7');
|
|
116
|
+
|
|
117
|
+
expect(steps[6].coords.equalsTo(p[15].coords)).true;
|
|
118
|
+
expect(steps[6].name).equals('w9');
|
|
119
|
+
|
|
120
|
+
expect(steps[7].coords.equalsTo(p[16].coords)).true;
|
|
121
|
+
expect(steps[7].name).is.null;
|
|
122
|
+
expect(steps[7].lastStep).true;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
it('router returns shortest path 2', () => {
|
|
127
|
+
|
|
128
|
+
const start = new Coordinates(43.609219, 3.8841743, null, new Level(2));
|
|
129
|
+
const end = new Coordinates(43.60917216742, 3.8842355275, null, new Level(2));
|
|
130
|
+
const itinerary2 = router.getShortestPath(start, end);
|
|
131
|
+
|
|
132
|
+
expect(getNodesNames(itinerary2)).deep.equals(['w1', 'p6', 'p5']);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('do not use stairs', () => {
|
|
136
|
+
|
|
137
|
+
const itineraryWithoutStairs = router.getShortestPath(
|
|
138
|
+
itineraryStart, itineraryEnd,
|
|
139
|
+
WemapRouterOptions.WITHOUT_STAIRS
|
|
140
|
+
);
|
|
141
|
+
expect(itineraryWithoutStairs.nodes.length).equal(11);
|
|
142
|
+
itineraryWithoutStairs.edges.forEach((edge) => {
|
|
143
|
+
expect(edge.builtFrom.tags.highway).is.not.equal('steps');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// it('toCompressedJson / fromCompressedJson', () => {
|
|
148
|
+
// const json = itinerary.toCompressedJson();
|
|
149
|
+
// expect(() => JSON.stringify(json)).not.throw(Error);
|
|
150
|
+
|
|
151
|
+
// const itineraryBis = Itinerary.fromCompressedJson(json);
|
|
152
|
+
// verifyCoherence(itineraryBis);
|
|
153
|
+
// expect(itineraryBis.nodes.length).equal(11);
|
|
154
|
+
// verifyNodesOrder(itineraryBis.nodes, ['w2', ...generateNodeNames(7, 16)]);
|
|
155
|
+
// });
|
|
156
|
+
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('WemapRouter - One Way itinerary', () => {
|
|
160
|
+
|
|
161
|
+
const networkModel = loadNetwork('one-way.osm');
|
|
162
|
+
const router = new WemapRouter(networkModel);
|
|
163
|
+
|
|
164
|
+
it('do not use oneway', () => {
|
|
165
|
+
|
|
166
|
+
const start = new Coordinates(43.6094542, 3.8842072);
|
|
167
|
+
const end = new Coordinates(43.6093792, 3.8841889);
|
|
168
|
+
|
|
169
|
+
const itinerary = router.getShortestPath(start, end);
|
|
170
|
+
expect(itinerary).is.not.undefined;
|
|
171
|
+
expect(getNodesNames(itinerary)).deep.equals(['p0', 'p1', 'p2', 'p3']);
|
|
172
|
+
|
|
173
|
+
const itineraryOtherWay = router.getShortestPath(end, start);
|
|
174
|
+
expect(itineraryOtherWay).is.not.undefined;
|
|
175
|
+
expect(getNodesNames(itineraryOtherWay)).deep.equals(['p3', 'p2', 'p5', 'p4', 'p1', 'p0']);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
describe('WemapRouter - Conveying', () => {
|
|
181
|
+
|
|
182
|
+
const networkModel = loadNetwork('gare-de-lyon-extract.osm');
|
|
183
|
+
const router = new WemapRouter(networkModel);
|
|
184
|
+
|
|
185
|
+
it('do not use oneway conveying', () => {
|
|
186
|
+
|
|
187
|
+
const start = new Coordinates(48.8445715, 2.3718927, null, new Level(0));
|
|
188
|
+
const end = new Coordinates(48.84443728652394, 2.3721685669363524, null, new Level(-1));
|
|
189
|
+
|
|
190
|
+
const itinerary = router.getShortestPath(start, end);
|
|
191
|
+
expect(itinerary).is.not.undefined;
|
|
192
|
+
expect(getNodesNames(itinerary)).deep.equals(['p11', 'p12', 'p9', 'p8', 'p6', null]);
|
|
193
|
+
|
|
194
|
+
const itineraryOtherWay = router.getShortestPath(end, start);
|
|
195
|
+
expect(itineraryOtherWay).is.not.undefined;
|
|
196
|
+
expect(getNodesNames(itineraryOtherWay)).deep.equals([null, 'p6', 'p7', 'p10', 'p11']);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
describe('WemapRouter - Conveying - backward', () => {
|
|
202
|
+
|
|
203
|
+
const networkModel = loadNetwork('network-conveying-backward.osm');
|
|
204
|
+
const router = new WemapRouter(networkModel);
|
|
205
|
+
|
|
206
|
+
it('do not use oneway conveying', () => {
|
|
207
|
+
|
|
208
|
+
const start = getNodeByName(networkModel, 'p6');
|
|
209
|
+
const end = getNodeByName(networkModel, 'p13');
|
|
210
|
+
|
|
211
|
+
const itinerary = router.getShortestPath(start, end);
|
|
212
|
+
expect(itinerary).is.not.undefined;
|
|
213
|
+
expect(getNodesNames(itinerary)).deep.equals(['p6', 'p7', 'p10', 'p11', 'p12', 'p13']);
|
|
214
|
+
|
|
215
|
+
const itineraryOtherWay = router.getShortestPath(end, start);
|
|
216
|
+
expect(itineraryOtherWay).is.not.undefined;
|
|
217
|
+
expect(getNodesNames(itineraryOtherWay)).deep.equals(['p13', 'p12', 'p9', 'p8', 'p6']);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { GraphRouterOptions } from '@wemap/geo';
|
|
2
|
+
import { OsmElement } from '@wemap/osm';
|
|
3
|
+
|
|
4
|
+
class WemapRouterOptions extends GraphRouterOptions {
|
|
5
|
+
|
|
6
|
+
/** @type {WemapRouterOptions} */
|
|
7
|
+
static DEFAULT = new WemapRouterOptions();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @returns {WemapRouterOptions}
|
|
11
|
+
*/
|
|
12
|
+
static get WITHOUT_STAIRS() {
|
|
13
|
+
const options = new WemapRouterOptions();
|
|
14
|
+
options.acceptEdgeFn = edge => edge.builtFrom.tags.highway !== 'steps';
|
|
15
|
+
return options;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get route duration
|
|
20
|
+
* @param {Number} speed in km/h
|
|
21
|
+
*/
|
|
22
|
+
static getDurationFromLength(length, speed = 5) {
|
|
23
|
+
return length / (speed * 1000 / 3600);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @type {function(GraphEdge<OsmElement>):boolean} */
|
|
27
|
+
weightEdgeFn = edge => edge.builtFrom.isElevator ? 30 : WemapRouterOptions.getDurationFromLength(edge.length);
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default WemapRouterOptions;
|