terra-route 0.0.6 → 0.0.7

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.
@@ -0,0 +1,109 @@
1
+ import { Heap } from "./heap";
2
+
3
+ class PairingHeapNode {
4
+ key: number;
5
+ value: number;
6
+ child: PairingHeapNode | null = null;
7
+ sibling: PairingHeapNode | null = null;
8
+
9
+ constructor(key: number, value: number) {
10
+ this.key = key;
11
+ this.value = value;
12
+ }
13
+ }
14
+
15
+ export class PairingHeap implements Heap {
16
+ private root: PairingHeapNode | null = null;
17
+ private heapSize = 0;
18
+
19
+ public insert(key: number, value: number): void {
20
+ const node = new PairingHeapNode(key, value);
21
+ this.root = this.merge(this.root, node);
22
+ this.heapSize++;
23
+ }
24
+
25
+ public extractMin(): number | null {
26
+ if (!this.root) {
27
+ return null;
28
+ }
29
+
30
+ const minValue = this.root.value;
31
+ const minChild = this.root.child;
32
+
33
+ // Detach the old root
34
+ this.root.child = null;
35
+
36
+ // Combine all children of the old root
37
+ this.root = this.combineSiblings(minChild);
38
+ this.heapSize--;
39
+
40
+ return minValue;
41
+ }
42
+
43
+ public size(): number {
44
+ return this.heapSize;
45
+ }
46
+
47
+ /**
48
+ * Merges two pairing heaps into one. Assumes each parameter is a root.
49
+ * Returns the root of the merged tree.
50
+ */
51
+ private merge(a: PairingHeapNode | null, b: PairingHeapNode | null): PairingHeapNode | null {
52
+ if (!a) {
53
+ return b;
54
+ }
55
+ if (!b) {
56
+ return a;
57
+ }
58
+ if (a.key <= b.key) {
59
+ // Make b a child of a
60
+ b.sibling = a.child;
61
+ a.child = b;
62
+ return a;
63
+ } else {
64
+ // Make a a child of b
65
+ a.sibling = b.child;
66
+ b.child = a;
67
+ return b;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Merges an entire list of siblings into a single root using a two‐pass pairing strategy.
73
+ */
74
+ private combineSiblings(firstSibling: PairingHeapNode | null): PairingHeapNode | null {
75
+ if (!firstSibling || !firstSibling.sibling) {
76
+ // Zero or one sibling, so it's already merged
77
+ return firstSibling;
78
+ }
79
+
80
+ // Step 1: Collect siblings into an array
81
+ const siblingArray: PairingHeapNode[] = [];
82
+ let current: PairingHeapNode | null = firstSibling;
83
+ while (current) {
84
+ const next: PairingHeapNode = current.sibling!;
85
+ current.sibling = null;
86
+ siblingArray.push(current);
87
+ current = next;
88
+ }
89
+
90
+ // Step 2: Pair up neighbors and merge them
91
+ let i = 0;
92
+ for (; i + 1 < siblingArray.length; i += 2) {
93
+ siblingArray[i] = this.merge(siblingArray[i], siblingArray[i + 1])!;
94
+ }
95
+
96
+ // If we had an odd number of siblings, 'i' is now pointing at the last single sibling
97
+ // Step 3: Merge the resulting heaps left to right
98
+ let j = i - 2;
99
+ // j starts at the second‐to‐last pair
100
+ if (j < 0) {
101
+ j = 0;
102
+ }
103
+ for (; j >= 0; j -= 2) {
104
+ siblingArray[j] = this.merge(siblingArray[j], siblingArray[j + 2])!;
105
+ }
106
+
107
+ return siblingArray[0];
108
+ }
109
+ }
@@ -0,0 +1,89 @@
1
+ import PathFinder, { pathToGeoJSON } from "geojson-path-finder";
2
+ import { FeatureCollection, LineString, Position } from "geojson";
3
+ import { haversineDistance, TerraRoute } from "./terra-route";
4
+ import { createPointFeature, routeLength } from "./test-utils/test-utils";
5
+ import { readFileSync } from 'fs';
6
+
7
+
8
+ /** Test on a "real" network to ensure that the path is as short or shorter than GeoJSON Path Finder */
9
+ describe("TerraRoute", () => {
10
+ describe('getRoute', () => {
11
+ it('matches route create from GeoJSON Path Finder', () => {
12
+
13
+ const network = JSON.parse(readFileSync('src/data/network.geojson', 'utf-8')) as FeatureCollection<LineString>;
14
+
15
+ const pairs: [Position, Position][] = [];
16
+
17
+ // Connect midpoints of lines across the network
18
+ for (let i = 0, j = network.features.length - 1; i < j; i++, j--) {
19
+ const coordsA = network.features[i].geometry.coordinates;
20
+ const coordsB = network.features[j].geometry.coordinates;
21
+
22
+ const midA = Math.floor(coordsA.length / 2);
23
+ const midB = Math.floor(coordsB.length / 2);
24
+
25
+ pairs.push([
26
+ coordsA[midA] as Position,
27
+ coordsB[midB] as Position
28
+ ]);
29
+ }
30
+
31
+ // Connect starting and ending points of lines across the network
32
+ for (let i = 0, j = network.features.length - 1; i < j; i++, j--) {
33
+ const coordsA = network.features[i].geometry.coordinates;
34
+ const coordsB = network.features[j].geometry.coordinates;
35
+
36
+ pairs.push([
37
+ coordsA[0] as Position,
38
+ coordsB[coordsB.length - 1] as Position
39
+ ]);
40
+ }
41
+
42
+ const terraRoute = new TerraRoute();
43
+
44
+ terraRoute.buildRouteGraph(network as FeatureCollection<LineString>);
45
+
46
+ const pathFinder = new PathFinder(network as FeatureCollection<LineString>, {
47
+ // Mimic points having to be identical
48
+ tolerance: 0.000000000000000000001,
49
+ weight: (a, b) => haversineDistance(a, b)
50
+ });
51
+
52
+ for (let i = 0; i < pairs.length; i++) {
53
+ // Start and end points are the same
54
+ const startIsEnd = pairs[i][0][0] === pairs[i][1][0] && pairs[i][0][1] === pairs[i][1][1]
55
+ if (startIsEnd) {
56
+ continue;
57
+ }
58
+
59
+ const start = createPointFeature(pairs[i][0]);
60
+ const end = createPointFeature(pairs[i][1]);
61
+
62
+ const route = pathFinder.findPath(start, end);
63
+ expect(route).not.toBeNull();
64
+
65
+ // Route not found
66
+ if (!route || route.path.length <= 1) {
67
+ const routeFromTerraRoute = terraRoute.getRoute(start, end);
68
+
69
+ // We want to check terra-route also returns null in this scenario
70
+ expect(routeFromTerraRoute).toBeNull();
71
+ continue
72
+ }
73
+
74
+ const routeFromPathFinder = pathToGeoJSON(route);
75
+ expect(routeFromPathFinder).toBeDefined();
76
+
77
+ const routeFromTerraRoute = terraRoute.getRoute(start, end);
78
+ expect(routeFromTerraRoute).not.toBeNull();
79
+
80
+ const pathFinderLength = routeLength(routeFromPathFinder!)
81
+ const terraDrawLength = routeLength(routeFromTerraRoute!);
82
+
83
+ expect(pathFinderLength).toBeGreaterThanOrEqual(terraDrawLength);
84
+
85
+ }
86
+ });
87
+
88
+ })
89
+ });