@osmix/router 0.0.8 → 0.0.9

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