@osmix/router 0.0.11 → 0.1.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/CHANGELOG.md +78 -0
- package/LICENSE +21 -0
- package/README.md +138 -53
- package/dist/algorithms/bidirectional.d.ts +6 -9
- package/dist/algorithms/bidirectional.d.ts.map +1 -1
- package/dist/algorithms/bidirectional.js +84 -104
- package/dist/algorithms/bidirectional.js.map +1 -1
- package/dist/algorithms/index.d.ts +2 -2
- package/dist/algorithms/index.d.ts.map +1 -1
- package/dist/algorithms/index.js +1 -1
- package/dist/algorithms/index.js.map +1 -1
- package/dist/algorithms/shortest-path.d.ts +2 -1
- package/dist/algorithms/shortest-path.d.ts.map +1 -1
- package/dist/algorithms/shortest-path.js +9 -7
- package/dist/algorithms/shortest-path.js.map +1 -1
- package/dist/binary-heap.d.ts +5 -0
- package/dist/binary-heap.d.ts.map +1 -1
- package/dist/binary-heap.js +11 -4
- package/dist/binary-heap.js.map +1 -1
- package/dist/graph.d.ts +35 -7
- package/dist/graph.d.ts.map +1 -1
- package/dist/graph.js +161 -119
- package/dist/graph.js.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +6 -3
- package/dist/router.js.map +1 -1
- package/dist/topology-stats.d.ts +37 -0
- package/dist/topology-stats.d.ts.map +1 -0
- package/dist/topology-stats.js +62 -0
- package/dist/topology-stats.js.map +1 -0
- package/dist/types.d.ts +15 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -7
- package/dist/utils.js.map +1 -1
- package/package.json +32 -29
- package/src/algorithms/bidirectional.ts +124 -136
- package/src/algorithms/index.ts +10 -10
- package/src/algorithms/shortest-path.ts +96 -92
- package/src/binary-heap.ts +133 -129
- package/src/graph.ts +389 -355
- package/src/index.ts +14 -9
- package/src/router.ts +221 -233
- package/src/topology-stats.ts +84 -0
- package/src/types.ts +102 -87
- package/src/utils.ts +67 -74
- package/test/algorithms.test.ts +396 -353
- package/test/binary-heap.test.ts +27 -0
- package/test/limits.test.ts +82 -0
- package/test/no-shared-array-buffer.test.ts +37 -0
- package/test/router.bench.ts +171 -188
- package/test/router.test.ts +685 -726
- package/test/spatial-capability.test.ts +23 -0
- package/test/topology-stats.test.ts +82 -0
- package/tsconfig.build.json +6 -3
- package/tsconfig.json +7 -7
package/test/router.test.ts
CHANGED
|
@@ -1,738 +1,697 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { getFixtureFile, PBFs } from "@osmix/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import {
|
|
1
|
+
import { Osm } from "@osmix/core";
|
|
2
|
+
import { fromPbf } from "@osmix/load";
|
|
3
|
+
import { getFixtureFile, PBFs } from "@osmix/test-utils/fixtures";
|
|
4
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { buildGraph, getTransferableBuffers, RoutingGraph } from "../src/index.ts";
|
|
7
|
+
import { Router } from "../src/router";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Create a simple OSM with a routeable network
|
|
10
11
|
*/
|
|
11
12
|
function createTestOsm(): Osm {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
13
|
+
const osm = new Osm({ id: "test" });
|
|
14
|
+
|
|
15
|
+
// Create a simple grid network
|
|
16
|
+
// Nodes: 4 corners of a square
|
|
17
|
+
osm.nodes.addNode({ id: 1, lat: 0, lon: 0 });
|
|
18
|
+
osm.nodes.addNode({ id: 2, lat: 0, lon: 0.01 });
|
|
19
|
+
osm.nodes.addNode({ id: 3, lat: 0.01, lon: 0 });
|
|
20
|
+
osm.nodes.addNode({ id: 4, lat: 0.01, lon: 0.01 });
|
|
21
|
+
// Center intersection node
|
|
22
|
+
osm.nodes.addNode({ id: 5, lat: 0.005, lon: 0.005 });
|
|
23
|
+
|
|
24
|
+
osm.nodes.buildIndex();
|
|
25
|
+
osm.nodes.buildSpatialIndex();
|
|
26
|
+
|
|
27
|
+
// Create ways connecting the nodes
|
|
28
|
+
// Way 1: horizontal bottom (1 -> 2)
|
|
29
|
+
osm.ways.addWay({
|
|
30
|
+
id: 1,
|
|
31
|
+
refs: [1, 2],
|
|
32
|
+
tags: { highway: "primary" },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Way 2: vertical left (1 -> 3)
|
|
36
|
+
osm.ways.addWay({
|
|
37
|
+
id: 2,
|
|
38
|
+
refs: [1, 3],
|
|
39
|
+
tags: { highway: "primary" },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Way 3: horizontal top (3 -> 4)
|
|
43
|
+
osm.ways.addWay({
|
|
44
|
+
id: 3,
|
|
45
|
+
refs: [3, 4],
|
|
46
|
+
tags: { highway: "primary" },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Way 4: vertical right (2 -> 4)
|
|
50
|
+
osm.ways.addWay({
|
|
51
|
+
id: 4,
|
|
52
|
+
refs: [2, 4],
|
|
53
|
+
tags: { highway: "primary" },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Way 5: diagonal through center (1 -> 5 -> 4)
|
|
57
|
+
osm.ways.addWay({
|
|
58
|
+
id: 5,
|
|
59
|
+
refs: [1, 5, 4],
|
|
60
|
+
tags: { highway: "secondary" },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
osm.ways.buildIndex();
|
|
64
|
+
osm.ways.buildSpatialIndex();
|
|
65
|
+
osm.buildIndexes();
|
|
66
|
+
|
|
67
|
+
return osm;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
describe("Router", () => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
71
|
+
it("should create a router with default config", () => {
|
|
72
|
+
const osm = createTestOsm();
|
|
73
|
+
const router = new Router(osm, buildGraph(osm));
|
|
74
|
+
|
|
75
|
+
expect(router).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should create a router with custom highway filter", () => {
|
|
79
|
+
const osm = createTestOsm();
|
|
80
|
+
const graph = buildGraph(osm, (tags) => tags?.["highway"] === "primary");
|
|
81
|
+
const router = new Router(osm, graph);
|
|
82
|
+
|
|
83
|
+
expect(router).toBeDefined();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should find a route between two nearby nodes", () => {
|
|
87
|
+
const osm = createTestOsm();
|
|
88
|
+
const graph = buildGraph(osm);
|
|
89
|
+
const router = new Router(osm, graph);
|
|
90
|
+
|
|
91
|
+
// Snap coordinates to nodes
|
|
92
|
+
const from = graph.findNearestRoutableNode(osm, [0, 0], 500);
|
|
93
|
+
const to = graph.findNearestRoutableNode(osm, [0, 0.01], 500);
|
|
94
|
+
|
|
95
|
+
expect(from).not.toBeNull();
|
|
96
|
+
expect(to).not.toBeNull();
|
|
97
|
+
|
|
98
|
+
// Route between node indexes
|
|
99
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
100
|
+
|
|
101
|
+
expect(path).not.toBeNull();
|
|
102
|
+
expect(path!.length).toBeGreaterThan(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should return path segments with node and way info", () => {
|
|
106
|
+
const osm = createTestOsm();
|
|
107
|
+
const graph = buildGraph(osm);
|
|
108
|
+
const router = new Router(osm, graph);
|
|
109
|
+
|
|
110
|
+
const from = graph.findNearestRoutableNode(osm, [0, 0], 500);
|
|
111
|
+
const to = graph.findNearestRoutableNode(osm, [0.01, 0.01], 500);
|
|
112
|
+
|
|
113
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
114
|
+
|
|
115
|
+
expect(path).not.toBeNull();
|
|
116
|
+
if (path) {
|
|
117
|
+
// Verify path segments have required fields
|
|
118
|
+
for (const segment of path) {
|
|
119
|
+
expect(typeof segment.nodeIndex).toBe("number");
|
|
120
|
+
expect(typeof segment.cost).toBe("number");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Build result to get coordinates
|
|
124
|
+
const result = router.buildResult(path);
|
|
125
|
+
expect(result.coordinates.length).toBeGreaterThan(0);
|
|
126
|
+
expect(Array.isArray(result.wayIndexes)).toBe(true);
|
|
127
|
+
expect(Array.isArray(result.nodeIndexes)).toBe(true);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should return null for unreachable destinations", () => {
|
|
132
|
+
const osm = new Osm({ id: "test" });
|
|
133
|
+
osm.nodes.addNode({ id: 1, lat: 0, lon: 0 });
|
|
134
|
+
osm.nodes.addNode({ id: 2, lat: 10, lon: 10 }); // Far away, no way connecting
|
|
135
|
+
osm.nodes.buildIndex();
|
|
136
|
+
osm.nodes.buildSpatialIndex();
|
|
137
|
+
osm.buildIndexes();
|
|
138
|
+
|
|
139
|
+
const graph = buildGraph(osm);
|
|
140
|
+
|
|
141
|
+
// Can't snap because no routable ways
|
|
142
|
+
const from = graph.findNearestRoutableNode(osm, [0, 0], 500);
|
|
143
|
+
const to = graph.findNearestRoutableNode(osm, [10, 10], 500);
|
|
144
|
+
|
|
145
|
+
expect(from).toBeNull();
|
|
146
|
+
expect(to).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should filter ways based on highway filter", () => {
|
|
150
|
+
const osm = createTestOsm();
|
|
151
|
+
// Only include primary highways
|
|
152
|
+
const graph = buildGraph(osm, (tags) => tags?.["highway"] === "primary");
|
|
153
|
+
const router = new Router(osm, graph);
|
|
154
|
+
|
|
155
|
+
const from = graph.findNearestRoutableNode(osm, [0, 0], 500);
|
|
156
|
+
const to = graph.findNearestRoutableNode(osm, [0.01, 0.01], 500);
|
|
157
|
+
|
|
158
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
159
|
+
|
|
160
|
+
// Should find a route using only primary ways
|
|
161
|
+
if (path) {
|
|
162
|
+
const result = router.buildResult(path);
|
|
163
|
+
// Way index 4 is the secondary highway (ID 5)
|
|
164
|
+
expect(result.wayIndexes).not.toContain(4);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("treats roundabouts as one-way unless explicitly overridden", () => {
|
|
169
|
+
const createRoundabout = (oneway?: string) => {
|
|
170
|
+
const osm = new Osm({ id: `roundabout-${oneway ?? "implicit"}` });
|
|
171
|
+
osm.nodes.addNode({ id: 1, lat: 0, lon: 0 });
|
|
172
|
+
osm.nodes.addNode({ id: 2, lat: 0, lon: 0.001 });
|
|
173
|
+
osm.nodes.addNode({ id: 3, lat: 0.001, lon: 0.001 });
|
|
174
|
+
osm.ways.addWay({
|
|
175
|
+
id: 10,
|
|
176
|
+
refs: [1, 2, 3, 1],
|
|
177
|
+
tags: {
|
|
178
|
+
highway: "residential",
|
|
179
|
+
junction: "roundabout",
|
|
180
|
+
...(oneway === undefined ? {} : { oneway }),
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
osm.buildIndexes();
|
|
184
|
+
osm.buildSpatialIndexes();
|
|
185
|
+
return osm;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const implicit = createRoundabout();
|
|
189
|
+
const implicitRoute = new Router(implicit, buildGraph(implicit)).route(1, 0);
|
|
190
|
+
expect(implicitRoute?.map((segment) => implicit.nodes.ids.at(segment.nodeIndex))).toEqual([
|
|
191
|
+
2, 3, 1,
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
const overridden = createRoundabout("no");
|
|
195
|
+
const overriddenRoute = new Router(overridden, buildGraph(overridden)).route(1, 0);
|
|
196
|
+
expect(overriddenRoute?.map((segment) => overridden.nodes.ids.at(segment.nodeIndex))).toEqual([
|
|
197
|
+
2, 1,
|
|
198
|
+
]);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("creates reverse-only edges for oneway=-1", () => {
|
|
202
|
+
const osm = new Osm({ id: "reverse-oneway" });
|
|
203
|
+
osm.nodes.addNode({ id: 1, lat: 0, lon: 0 });
|
|
204
|
+
osm.nodes.addNode({ id: 2, lat: 0, lon: 0.001 });
|
|
205
|
+
osm.nodes.addNode({ id: 3, lat: 0, lon: 0.002 });
|
|
206
|
+
osm.ways.addWay({
|
|
207
|
+
id: 10,
|
|
208
|
+
refs: [1, 2, 3],
|
|
209
|
+
tags: { highway: "residential", oneway: "-1" },
|
|
210
|
+
});
|
|
211
|
+
osm.buildIndexes();
|
|
212
|
+
osm.buildSpatialIndexes();
|
|
213
|
+
const router = new Router(osm, buildGraph(osm));
|
|
214
|
+
|
|
215
|
+
expect(router.route(0, 2)).toBeNull();
|
|
216
|
+
expect(router.route(2, 0)?.map((segment) => osm.nodes.ids.at(segment.nodeIndex))).toEqual([
|
|
217
|
+
3, 2, 1,
|
|
218
|
+
]);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should handle same start and end node", () => {
|
|
222
|
+
const osm = createTestOsm();
|
|
223
|
+
const graph = buildGraph(osm);
|
|
224
|
+
const router = new Router(osm, graph);
|
|
225
|
+
|
|
226
|
+
const from = graph.findNearestRoutableNode(osm, [0, 0], 500);
|
|
227
|
+
|
|
228
|
+
const path = router.route(from!.nodeIndex, from!.nodeIndex);
|
|
229
|
+
|
|
230
|
+
expect(path).not.toBeNull();
|
|
231
|
+
expect(path!.length).toBe(1);
|
|
232
|
+
expect(path![0]!.nodeIndex).toBe(from!.nodeIndex);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should use different algorithms", () => {
|
|
236
|
+
const osm = createTestOsm();
|
|
237
|
+
const graph = buildGraph(osm);
|
|
238
|
+
const router = new Router(osm, graph);
|
|
239
|
+
|
|
240
|
+
const from = graph.findNearestRoutableNode(osm, [0, 0], 500);
|
|
241
|
+
const to = graph.findNearestRoutableNode(osm, [0.01, 0.01], 500);
|
|
242
|
+
|
|
243
|
+
const path1 = router.route(from!.nodeIndex, to!.nodeIndex, {
|
|
244
|
+
algorithm: "dijkstra",
|
|
245
|
+
});
|
|
246
|
+
const path2 = router.route(from!.nodeIndex, to!.nodeIndex, {
|
|
247
|
+
algorithm: "astar",
|
|
248
|
+
});
|
|
249
|
+
const path3 = router.route(from!.nodeIndex, to!.nodeIndex, {
|
|
250
|
+
algorithm: "bidirectional",
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// All should find a path
|
|
254
|
+
const paths = [path1, path2, path3].filter((p) => p !== null);
|
|
255
|
+
expect(paths.length).toBe(3);
|
|
256
|
+
|
|
257
|
+
// All paths should have segments
|
|
258
|
+
for (const path of paths) {
|
|
259
|
+
expect(path!.length).toBeGreaterThan(0);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should support distance and time metrics", () => {
|
|
264
|
+
const osm = createTestOsm();
|
|
265
|
+
const graph = buildGraph(osm);
|
|
266
|
+
const router = new Router(osm, graph);
|
|
267
|
+
|
|
268
|
+
const from = graph.findNearestRoutableNode(osm, [0, 0], 500);
|
|
269
|
+
const to = graph.findNearestRoutableNode(osm, [0.01, 0.01], 500);
|
|
270
|
+
|
|
271
|
+
const path1 = router.route(from!.nodeIndex, to!.nodeIndex, {
|
|
272
|
+
metric: "distance",
|
|
273
|
+
});
|
|
274
|
+
const path2 = router.route(from!.nodeIndex, to!.nodeIndex, {
|
|
275
|
+
metric: "time",
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Both should find paths
|
|
279
|
+
expect(path1).not.toBeNull();
|
|
280
|
+
expect(path2).not.toBeNull();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
229
283
|
|
|
230
284
|
describe("Router with Monaco PBF", () => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
// With includePathInfo: true
|
|
504
|
-
const resultWithPathInfo = router.buildResult(path!, {
|
|
505
|
-
includePathInfo: true,
|
|
506
|
-
})
|
|
507
|
-
expect(resultWithPathInfo.segments).toBeDefined()
|
|
508
|
-
expect(resultWithPathInfo.segments!.length).toBeGreaterThan(0)
|
|
509
|
-
expect(resultWithPathInfo.turnPoints).toBeDefined()
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
it("should include both stats and path info when both options are true", () => {
|
|
513
|
-
const graph = buildGraph(monacoOsm)
|
|
514
|
-
const router = new Router(monacoOsm, graph)
|
|
515
|
-
|
|
516
|
-
const from = graph.findNearestRoutableNode(
|
|
517
|
-
monacoOsm,
|
|
518
|
-
[7.4229093, 43.7371175],
|
|
519
|
-
500,
|
|
520
|
-
)
|
|
521
|
-
const to = graph.findNearestRoutableNode(
|
|
522
|
-
monacoOsm,
|
|
523
|
-
[7.4259193, 43.7377731],
|
|
524
|
-
500,
|
|
525
|
-
)
|
|
526
|
-
|
|
527
|
-
const path = router.route(from!.nodeIndex, to!.nodeIndex)
|
|
528
|
-
expect(path).not.toBeNull()
|
|
529
|
-
|
|
530
|
-
const result = router.buildResult(path!, {
|
|
531
|
-
includeStats: true,
|
|
532
|
-
includePathInfo: true,
|
|
533
|
-
})
|
|
534
|
-
|
|
535
|
-
// Should have coordinates (always present)
|
|
536
|
-
expect(result.coordinates.length).toBeGreaterThan(0)
|
|
537
|
-
|
|
538
|
-
// Should have stats
|
|
539
|
-
expect(result.distance).toBeGreaterThan(0)
|
|
540
|
-
expect(result.time).toBeGreaterThan(0)
|
|
541
|
-
|
|
542
|
-
// Should have path info
|
|
543
|
-
expect(result.segments).toBeDefined()
|
|
544
|
-
expect(result.segments!.length).toBeGreaterThan(0)
|
|
545
|
-
expect(result.turnPoints).toBeDefined()
|
|
546
|
-
|
|
547
|
-
// Verify segment totals roughly match stats
|
|
548
|
-
const segmentDistanceSum = result.segments!.reduce(
|
|
549
|
-
(sum, seg) => sum + seg.distance,
|
|
550
|
-
0,
|
|
551
|
-
)
|
|
552
|
-
const segmentTimeSum = result.segments!.reduce(
|
|
553
|
-
(sum, seg) => sum + seg.time,
|
|
554
|
-
0,
|
|
555
|
-
)
|
|
556
|
-
|
|
557
|
-
expect(segmentDistanceSum).toBeCloseTo(result.distance!, 1)
|
|
558
|
-
expect(segmentTimeSum).toBeCloseTo(result.time!, 1)
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
it("should use default options from constructor", () => {
|
|
562
|
-
const graph = buildGraph(monacoOsm)
|
|
563
|
-
const router = new Router(monacoOsm, graph, {
|
|
564
|
-
includeStats: true,
|
|
565
|
-
includePathInfo: true,
|
|
566
|
-
})
|
|
567
|
-
|
|
568
|
-
const from = graph.findNearestRoutableNode(
|
|
569
|
-
monacoOsm,
|
|
570
|
-
[7.4229093, 43.7371175],
|
|
571
|
-
500,
|
|
572
|
-
)
|
|
573
|
-
const to = graph.findNearestRoutableNode(
|
|
574
|
-
monacoOsm,
|
|
575
|
-
[7.4259193, 43.7377731],
|
|
576
|
-
500,
|
|
577
|
-
)
|
|
578
|
-
|
|
579
|
-
const path = router.route(from!.nodeIndex, to!.nodeIndex)
|
|
580
|
-
expect(path).not.toBeNull()
|
|
581
|
-
|
|
582
|
-
// buildResult without options should use constructor defaults
|
|
583
|
-
const result = router.buildResult(path!)
|
|
584
|
-
|
|
585
|
-
expect(result.distance).toBeGreaterThan(0)
|
|
586
|
-
expect(result.time).toBeGreaterThan(0)
|
|
587
|
-
expect(result.segments).toBeDefined()
|
|
588
|
-
expect(result.turnPoints).toBeDefined()
|
|
589
|
-
})
|
|
590
|
-
})
|
|
285
|
+
const monacoPbf = PBFs["monaco"]!;
|
|
286
|
+
let monacoOsm: Osm;
|
|
287
|
+
|
|
288
|
+
beforeAll(async () => {
|
|
289
|
+
const pbfData = await getFixtureFile(monacoPbf.url);
|
|
290
|
+
monacoOsm = await fromPbf(pbfData);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should create a router from Monaco OSM data", () => {
|
|
294
|
+
const graph = buildGraph(monacoOsm);
|
|
295
|
+
const router = new Router(monacoOsm, graph);
|
|
296
|
+
expect(router).toBeDefined();
|
|
297
|
+
// Directed edges are counted separately; roundabout and reverse-oneway edges are directional.
|
|
298
|
+
expect(graph.edges).toBe(10_831);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("should find routes between points in Monaco", () => {
|
|
302
|
+
const graph = buildGraph(monacoOsm);
|
|
303
|
+
const router = new Router(monacoOsm, graph);
|
|
304
|
+
|
|
305
|
+
// Use coordinates known to have routeable nodes nearby
|
|
306
|
+
const from = graph.findNearestRoutableNode(monacoOsm, [7.4229093, 43.7371175], 500);
|
|
307
|
+
const to = graph.findNearestRoutableNode(monacoOsm, [7.4259193, 43.7377731], 500);
|
|
308
|
+
|
|
309
|
+
expect(from).not.toBeNull();
|
|
310
|
+
expect(to).not.toBeNull();
|
|
311
|
+
|
|
312
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
313
|
+
|
|
314
|
+
expect(path).not.toBeNull();
|
|
315
|
+
expect(path!.length).toBeGreaterThan(0);
|
|
316
|
+
|
|
317
|
+
// Build result and verify coordinates
|
|
318
|
+
const result = router.buildResult(path!);
|
|
319
|
+
expect(result.coordinates.length).toBeGreaterThan(0);
|
|
320
|
+
expect(result.wayIndexes.length).toBeGreaterThan(0);
|
|
321
|
+
|
|
322
|
+
// Verify coordinates are in Monaco bbox
|
|
323
|
+
for (const coord of result.coordinates) {
|
|
324
|
+
expect(coord[0]).toBeGreaterThanOrEqual(7.4);
|
|
325
|
+
expect(coord[0]).toBeLessThanOrEqual(7.45);
|
|
326
|
+
expect(coord[1]).toBeGreaterThanOrEqual(43.72);
|
|
327
|
+
expect(coord[1]).toBeLessThanOrEqual(43.76);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("should return routes with valid way indexes from Monaco", () => {
|
|
332
|
+
const graph = buildGraph(monacoOsm);
|
|
333
|
+
const router = new Router(monacoOsm, graph);
|
|
334
|
+
|
|
335
|
+
const from = graph.findNearestRoutableNode(monacoOsm, [7.4229093, 43.7371175], 500);
|
|
336
|
+
const to = graph.findNearestRoutableNode(monacoOsm, [7.4259193, 43.7377731], 500);
|
|
337
|
+
|
|
338
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
339
|
+
|
|
340
|
+
if (path) {
|
|
341
|
+
const result = router.buildResult(path);
|
|
342
|
+
// Verify way indexes are valid
|
|
343
|
+
for (const wayIndex of result.wayIndexes) {
|
|
344
|
+
const way = monacoOsm.ways.getByIndex(wayIndex);
|
|
345
|
+
expect(way).not.toBeNull();
|
|
346
|
+
expect(way?.tags?.["highway"]).toBeDefined();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("should find routes using different algorithms in Monaco", () => {
|
|
352
|
+
const graph = buildGraph(monacoOsm);
|
|
353
|
+
const router = new Router(monacoOsm, graph);
|
|
354
|
+
|
|
355
|
+
const from = graph.findNearestRoutableNode(monacoOsm, [7.4229093, 43.7371175], 500);
|
|
356
|
+
const to = graph.findNearestRoutableNode(monacoOsm, [7.4259193, 43.7377731], 500);
|
|
357
|
+
|
|
358
|
+
const dijkstraPath = router.route(from!.nodeIndex, to!.nodeIndex, {
|
|
359
|
+
algorithm: "dijkstra",
|
|
360
|
+
});
|
|
361
|
+
const astarPath = router.route(from!.nodeIndex, to!.nodeIndex, {
|
|
362
|
+
algorithm: "astar",
|
|
363
|
+
});
|
|
364
|
+
const bidirectionalPath = router.route(from!.nodeIndex, to!.nodeIndex, {
|
|
365
|
+
algorithm: "bidirectional",
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// All should find paths
|
|
369
|
+
const paths = [dijkstraPath, astarPath, bidirectionalPath].filter((p) => p !== null);
|
|
370
|
+
expect(paths.length).toBe(3);
|
|
371
|
+
|
|
372
|
+
// All paths should have segments
|
|
373
|
+
for (const path of paths) {
|
|
374
|
+
expect(path!.length).toBeGreaterThan(0);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("should handle time-based routing in Monaco", () => {
|
|
379
|
+
const graph = buildGraph(monacoOsm);
|
|
380
|
+
const router = new Router(monacoOsm, graph);
|
|
381
|
+
|
|
382
|
+
const from = graph.findNearestRoutableNode(monacoOsm, [7.4229093, 43.7371175], 500);
|
|
383
|
+
const to = graph.findNearestRoutableNode(monacoOsm, [7.4259193, 43.7377731], 500);
|
|
384
|
+
|
|
385
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex, {
|
|
386
|
+
metric: "time",
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
expect(path).not.toBeNull();
|
|
390
|
+
expect(path!.length).toBeGreaterThan(0);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("should return null when snap fails", () => {
|
|
394
|
+
const graph = buildGraph(monacoOsm);
|
|
395
|
+
|
|
396
|
+
// Point far outside Monaco
|
|
397
|
+
const farPoint = graph.findNearestRoutableNode(monacoOsm, [10.0, 50.0], 100);
|
|
398
|
+
expect(farPoint).toBeNull();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("should calculate route statistics with getRouteStatistics()", () => {
|
|
402
|
+
const graph = buildGraph(monacoOsm);
|
|
403
|
+
const router = new Router(monacoOsm, graph);
|
|
404
|
+
|
|
405
|
+
const from = graph.findNearestRoutableNode(monacoOsm, [7.4229093, 43.7371175], 500);
|
|
406
|
+
const to = graph.findNearestRoutableNode(monacoOsm, [7.4259193, 43.7377731], 500);
|
|
407
|
+
|
|
408
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
409
|
+
expect(path).not.toBeNull();
|
|
410
|
+
|
|
411
|
+
const stats = router.getRouteStatistics(path!);
|
|
412
|
+
|
|
413
|
+
// Should have positive distance and time
|
|
414
|
+
expect(stats.distance).toBeGreaterThan(0);
|
|
415
|
+
expect(stats.time).toBeGreaterThan(0);
|
|
416
|
+
|
|
417
|
+
// Distance should be reasonable for Monaco (< 10km)
|
|
418
|
+
expect(stats.distance).toBeLessThan(10_000);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("should build path info with getRoutePathInfo()", () => {
|
|
422
|
+
const graph = buildGraph(monacoOsm);
|
|
423
|
+
const router = new Router(monacoOsm, graph);
|
|
424
|
+
|
|
425
|
+
const from = graph.findNearestRoutableNode(monacoOsm, [7.4229093, 43.7371175], 500);
|
|
426
|
+
const to = graph.findNearestRoutableNode(monacoOsm, [7.4259193, 43.7377731], 500);
|
|
427
|
+
|
|
428
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
429
|
+
expect(path).not.toBeNull();
|
|
430
|
+
|
|
431
|
+
const pathInfo = router.getRoutePathInfo(path!);
|
|
432
|
+
|
|
433
|
+
// Should have at least one segment
|
|
434
|
+
expect(pathInfo.segments.length).toBeGreaterThan(0);
|
|
435
|
+
|
|
436
|
+
// Each segment should have required fields
|
|
437
|
+
for (const segment of pathInfo.segments) {
|
|
438
|
+
expect(Array.isArray(segment.wayIds)).toBe(true);
|
|
439
|
+
expect(segment.wayIds.length).toBeGreaterThan(0);
|
|
440
|
+
expect(typeof segment.name).toBe("string");
|
|
441
|
+
expect(typeof segment.highway).toBe("string");
|
|
442
|
+
expect(segment.distance).toBeGreaterThan(0);
|
|
443
|
+
expect(segment.time).toBeGreaterThan(0);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Turn points should be coordinates
|
|
447
|
+
for (const turnPoint of pathInfo.turnPoints) {
|
|
448
|
+
expect(Array.isArray(turnPoint)).toBe(true);
|
|
449
|
+
expect(turnPoint.length).toBe(2);
|
|
450
|
+
expect(typeof turnPoint[0]).toBe("number");
|
|
451
|
+
expect(typeof turnPoint[1]).toBe("number");
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("should include stats in buildResult() when includeStats is true", () => {
|
|
456
|
+
const graph = buildGraph(monacoOsm);
|
|
457
|
+
const router = new Router(monacoOsm, graph);
|
|
458
|
+
|
|
459
|
+
const from = graph.findNearestRoutableNode(monacoOsm, [7.4229093, 43.7371175], 500);
|
|
460
|
+
const to = graph.findNearestRoutableNode(monacoOsm, [7.4259193, 43.7377731], 500);
|
|
461
|
+
|
|
462
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
463
|
+
expect(path).not.toBeNull();
|
|
464
|
+
|
|
465
|
+
// Without options - no stats
|
|
466
|
+
const resultWithoutStats = router.buildResult(path!);
|
|
467
|
+
expect(resultWithoutStats.distance).toBeUndefined();
|
|
468
|
+
expect(resultWithoutStats.time).toBeUndefined();
|
|
469
|
+
|
|
470
|
+
// With includeStats: true
|
|
471
|
+
const resultWithStats = router.buildResult(path!, { includeStats: true });
|
|
472
|
+
expect(resultWithStats.distance).toBeGreaterThan(0);
|
|
473
|
+
expect(resultWithStats.time).toBeGreaterThan(0);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it("should include path info in buildResult() when includePathInfo is true", () => {
|
|
477
|
+
const graph = buildGraph(monacoOsm);
|
|
478
|
+
const router = new Router(monacoOsm, graph);
|
|
479
|
+
|
|
480
|
+
const from = graph.findNearestRoutableNode(monacoOsm, [7.4229093, 43.7371175], 500);
|
|
481
|
+
const to = graph.findNearestRoutableNode(monacoOsm, [7.4259193, 43.7377731], 500);
|
|
482
|
+
|
|
483
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
484
|
+
expect(path).not.toBeNull();
|
|
485
|
+
|
|
486
|
+
// Without options - no path info
|
|
487
|
+
const resultWithoutPathInfo = router.buildResult(path!);
|
|
488
|
+
expect(resultWithoutPathInfo.segments).toBeUndefined();
|
|
489
|
+
expect(resultWithoutPathInfo.turnPoints).toBeUndefined();
|
|
490
|
+
|
|
491
|
+
// With includePathInfo: true
|
|
492
|
+
const resultWithPathInfo = router.buildResult(path!, {
|
|
493
|
+
includePathInfo: true,
|
|
494
|
+
});
|
|
495
|
+
expect(resultWithPathInfo.segments).toBeDefined();
|
|
496
|
+
expect(resultWithPathInfo.segments!.length).toBeGreaterThan(0);
|
|
497
|
+
expect(resultWithPathInfo.turnPoints).toBeDefined();
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it("should include both stats and path info when both options are true", () => {
|
|
501
|
+
const graph = buildGraph(monacoOsm);
|
|
502
|
+
const router = new Router(monacoOsm, graph);
|
|
503
|
+
|
|
504
|
+
const from = graph.findNearestRoutableNode(monacoOsm, [7.4229093, 43.7371175], 500);
|
|
505
|
+
const to = graph.findNearestRoutableNode(monacoOsm, [7.4259193, 43.7377731], 500);
|
|
506
|
+
|
|
507
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
508
|
+
expect(path).not.toBeNull();
|
|
509
|
+
|
|
510
|
+
const result = router.buildResult(path!, {
|
|
511
|
+
includeStats: true,
|
|
512
|
+
includePathInfo: true,
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Should have coordinates (always present)
|
|
516
|
+
expect(result.coordinates.length).toBeGreaterThan(0);
|
|
517
|
+
|
|
518
|
+
// Should have stats
|
|
519
|
+
expect(result.distance).toBeGreaterThan(0);
|
|
520
|
+
expect(result.time).toBeGreaterThan(0);
|
|
521
|
+
|
|
522
|
+
// Should have path info
|
|
523
|
+
expect(result.segments).toBeDefined();
|
|
524
|
+
expect(result.segments!.length).toBeGreaterThan(0);
|
|
525
|
+
expect(result.turnPoints).toBeDefined();
|
|
526
|
+
|
|
527
|
+
// Verify segment totals roughly match stats
|
|
528
|
+
const segmentDistanceSum = result.segments!.reduce((sum, seg) => sum + seg.distance, 0);
|
|
529
|
+
const segmentTimeSum = result.segments!.reduce((sum, seg) => sum + seg.time, 0);
|
|
530
|
+
|
|
531
|
+
expect(segmentDistanceSum).toBeCloseTo(result.distance!, 1);
|
|
532
|
+
expect(segmentTimeSum).toBeCloseTo(result.time!, 1);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it("should use default options from constructor", () => {
|
|
536
|
+
const graph = buildGraph(monacoOsm);
|
|
537
|
+
const router = new Router(monacoOsm, graph, {
|
|
538
|
+
includeStats: true,
|
|
539
|
+
includePathInfo: true,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
const from = graph.findNearestRoutableNode(monacoOsm, [7.4229093, 43.7371175], 500);
|
|
543
|
+
const to = graph.findNearestRoutableNode(monacoOsm, [7.4259193, 43.7377731], 500);
|
|
544
|
+
|
|
545
|
+
const path = router.route(from!.nodeIndex, to!.nodeIndex);
|
|
546
|
+
expect(path).not.toBeNull();
|
|
547
|
+
|
|
548
|
+
// buildResult without options should use constructor defaults
|
|
549
|
+
const result = router.buildResult(path!);
|
|
550
|
+
|
|
551
|
+
expect(result.distance).toBeGreaterThan(0);
|
|
552
|
+
expect(result.time).toBeGreaterThan(0);
|
|
553
|
+
expect(result.segments).toBeDefined();
|
|
554
|
+
expect(result.turnPoints).toBeDefined();
|
|
555
|
+
});
|
|
556
|
+
});
|
|
591
557
|
|
|
592
558
|
describe("RoutingGraph Serialization", () => {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
expect(buffers.length).toBe(7)
|
|
733
|
-
for (const buffer of buffers) {
|
|
734
|
-
expect(buffer).toBeInstanceOf(ArrayBuffer)
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
})
|
|
738
|
-
})
|
|
559
|
+
it("should serialize and reconstruct graph correctly", () => {
|
|
560
|
+
const osm = new Osm({ id: "test" });
|
|
561
|
+
|
|
562
|
+
// Create a simple network
|
|
563
|
+
osm.nodes.addNode({ id: 1, lat: 0, lon: 0 });
|
|
564
|
+
osm.nodes.addNode({ id: 2, lat: 0, lon: 0.01 });
|
|
565
|
+
osm.nodes.addNode({ id: 3, lat: 0.01, lon: 0.01 });
|
|
566
|
+
osm.nodes.buildIndex();
|
|
567
|
+
osm.nodes.buildSpatialIndex();
|
|
568
|
+
|
|
569
|
+
osm.ways.addWay({
|
|
570
|
+
id: 1,
|
|
571
|
+
refs: [1, 2, 3],
|
|
572
|
+
tags: { highway: "primary" },
|
|
573
|
+
});
|
|
574
|
+
osm.ways.buildIndex();
|
|
575
|
+
osm.ways.buildSpatialIndex();
|
|
576
|
+
osm.buildIndexes();
|
|
577
|
+
|
|
578
|
+
// Build and compact the graph
|
|
579
|
+
const originalGraph = buildGraph(osm);
|
|
580
|
+
|
|
581
|
+
// Get transferables
|
|
582
|
+
const transferables = originalGraph.transferables();
|
|
583
|
+
|
|
584
|
+
// Verify transferables structure
|
|
585
|
+
expect(transferables.nodeCount).toBe(osm.nodes.size);
|
|
586
|
+
expect(transferables.edgeCount).toBe(originalGraph.edges);
|
|
587
|
+
// Buffers can be either ArrayBuffer or SharedArrayBuffer depending on runtime
|
|
588
|
+
const isBuffer = (b: unknown) => b instanceof ArrayBuffer || b instanceof SharedArrayBuffer;
|
|
589
|
+
expect(isBuffer(transferables.edgeOffsets)).toBe(true);
|
|
590
|
+
expect(isBuffer(transferables.edgeTargets)).toBe(true);
|
|
591
|
+
expect(isBuffer(transferables.edgeDistances)).toBe(true);
|
|
592
|
+
expect(isBuffer(transferables.edgeTimes)).toBe(true);
|
|
593
|
+
expect(isBuffer(transferables.routableBits)).toBe(true);
|
|
594
|
+
expect(isBuffer(transferables.intersectionBits)).toBe(true);
|
|
595
|
+
|
|
596
|
+
// Reconstruct graph from transferables
|
|
597
|
+
const reconstructedGraph = new RoutingGraph(transferables);
|
|
598
|
+
|
|
599
|
+
// Verify properties match
|
|
600
|
+
expect(reconstructedGraph.size).toBe(originalGraph.size);
|
|
601
|
+
expect(reconstructedGraph.edges).toBe(originalGraph.edges);
|
|
602
|
+
|
|
603
|
+
// Verify edges for each node match
|
|
604
|
+
for (let i = 0; i < osm.nodes.size; i++) {
|
|
605
|
+
const origEdges = originalGraph.getEdges(i);
|
|
606
|
+
const reconEdges = reconstructedGraph.getEdges(i);
|
|
607
|
+
expect(reconEdges.length).toBe(origEdges.length);
|
|
608
|
+
|
|
609
|
+
for (let j = 0; j < origEdges.length; j++) {
|
|
610
|
+
expect(reconEdges[j]!.targetNodeIndex).toBe(origEdges[j]!.targetNodeIndex);
|
|
611
|
+
expect(reconEdges[j]!.wayIndex).toBe(origEdges[j]!.wayIndex);
|
|
612
|
+
expect(reconEdges[j]!.distance).toBeCloseTo(origEdges[j]!.distance, 2);
|
|
613
|
+
expect(reconEdges[j]!.time).toBeCloseTo(origEdges[j]!.time, 2);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Verify routable and intersection flags
|
|
618
|
+
expect(reconstructedGraph.isRoutable(0)).toBe(originalGraph.isRoutable(0));
|
|
619
|
+
expect(reconstructedGraph.isRoutable(1)).toBe(originalGraph.isRoutable(1));
|
|
620
|
+
expect(reconstructedGraph.isRoutable(2)).toBe(originalGraph.isRoutable(2));
|
|
621
|
+
expect(reconstructedGraph.isIntersection(0)).toBe(originalGraph.isIntersection(0));
|
|
622
|
+
expect(reconstructedGraph.isIntersection(1)).toBe(originalGraph.isIntersection(1));
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it("should produce working router after reconstruction", () => {
|
|
626
|
+
const osm = new Osm({ id: "test" });
|
|
627
|
+
|
|
628
|
+
osm.nodes.addNode({ id: 1, lat: 0, lon: 0 });
|
|
629
|
+
osm.nodes.addNode({ id: 2, lat: 0, lon: 0.01 });
|
|
630
|
+
osm.nodes.addNode({ id: 3, lat: 0.01, lon: 0.01 });
|
|
631
|
+
osm.nodes.buildIndex();
|
|
632
|
+
osm.nodes.buildSpatialIndex();
|
|
633
|
+
|
|
634
|
+
osm.ways.addWay({
|
|
635
|
+
id: 1,
|
|
636
|
+
refs: [1, 2, 3],
|
|
637
|
+
tags: { highway: "primary" },
|
|
638
|
+
});
|
|
639
|
+
osm.ways.buildIndex();
|
|
640
|
+
osm.ways.buildSpatialIndex();
|
|
641
|
+
osm.buildIndexes();
|
|
642
|
+
|
|
643
|
+
const originalGraph = buildGraph(osm);
|
|
644
|
+
const transferables = originalGraph.transferables();
|
|
645
|
+
const reconstructedGraph = new RoutingGraph(transferables);
|
|
646
|
+
|
|
647
|
+
// Create routers with both graphs
|
|
648
|
+
const router1 = new Router(osm, originalGraph);
|
|
649
|
+
const router2 = new Router(osm, reconstructedGraph);
|
|
650
|
+
|
|
651
|
+
// Both should find the same route
|
|
652
|
+
const path1 = router1.route(0, 2);
|
|
653
|
+
const path2 = router2.route(0, 2);
|
|
654
|
+
|
|
655
|
+
expect(path1).not.toBeNull();
|
|
656
|
+
expect(path2).not.toBeNull();
|
|
657
|
+
expect(path1!.length).toBe(path2!.length);
|
|
658
|
+
|
|
659
|
+
for (let i = 0; i < path1!.length; i++) {
|
|
660
|
+
expect(path1![i]!.nodeIndex).toBe(path2![i]!.nodeIndex);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it("should provide transferable buffers for postMessage", () => {
|
|
665
|
+
const osm = new Osm({ id: "test" });
|
|
666
|
+
osm.nodes.addNode({ id: 1, lat: 0, lon: 0 });
|
|
667
|
+
osm.nodes.addNode({ id: 2, lat: 0, lon: 0.01 });
|
|
668
|
+
osm.nodes.buildIndex();
|
|
669
|
+
osm.nodes.buildSpatialIndex();
|
|
670
|
+
osm.ways.addWay({
|
|
671
|
+
id: 1,
|
|
672
|
+
refs: [1, 2],
|
|
673
|
+
tags: { highway: "primary" },
|
|
674
|
+
});
|
|
675
|
+
osm.ways.buildIndex();
|
|
676
|
+
osm.ways.buildSpatialIndex();
|
|
677
|
+
osm.buildIndexes();
|
|
678
|
+
|
|
679
|
+
const graph = buildGraph(osm);
|
|
680
|
+
const transferables = graph.transferables();
|
|
681
|
+
const buffers = getTransferableBuffers(transferables);
|
|
682
|
+
|
|
683
|
+
// When SharedArrayBuffer is available, returns empty (they're shared, not transferred).
|
|
684
|
+
// When only ArrayBuffer is available, returns all 7 buffers.
|
|
685
|
+
const usesSharedArrayBuffer =
|
|
686
|
+
typeof SharedArrayBuffer !== "undefined" &&
|
|
687
|
+
transferables.edgeOffsets instanceof SharedArrayBuffer;
|
|
688
|
+
if (usesSharedArrayBuffer) {
|
|
689
|
+
expect(buffers.length).toBe(0);
|
|
690
|
+
} else {
|
|
691
|
+
expect(buffers.length).toBe(7);
|
|
692
|
+
for (const buffer of buffers) {
|
|
693
|
+
expect(buffer).toBeInstanceOf(ArrayBuffer);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
});
|