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.
- package/README.md +8 -6
- package/dist/distance/haversine.d.ts +1 -0
- package/dist/heap/min-heap.d.ts +9 -0
- package/dist/terra-route.cjs +1 -1
- package/dist/terra-route.cjs.map +1 -1
- package/dist/terra-route.d.ts +8 -3
- package/dist/terra-route.modern.js +1 -1
- package/dist/terra-route.modern.js.map +1 -1
- package/dist/terra-route.module.js +1 -1
- package/dist/terra-route.module.js.map +1 -1
- package/dist/terra-route.umd.js +1 -1
- package/dist/terra-route.umd.js.map +1 -1
- package/jest.config.js +1 -1
- package/package.json +1 -1
- package/src/data/network.geojson +822 -0
- package/src/distance/haversine.ts +2 -1
- package/src/heap/fibonacci-heap.spec.ts +98 -0
- package/src/heap/fibonacci-heap.ts +210 -0
- package/src/heap/heap.d.ts +10 -0
- package/src/heap/min-heap.spec.ts +127 -0
- package/src/heap/min-heap.ts +90 -0
- package/src/heap/pairing-heap.spec.ts +101 -0
- package/src/heap/pairing-heap.ts +109 -0
- package/src/terra-route.compare.spec.ts +89 -0
- package/src/terra-route.spec.ts +500 -446
- package/src/terra-route.ts +49 -64
- package/src/test-utils/test-utils.ts +77 -3
- package/src/fibonacci-heap.spec.ts +0 -55
- package/src/fibonacci-heap.ts +0 -131
|
@@ -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
|
+
});
|