terra-route 0.0.11 → 0.0.13

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.
Files changed (37) hide show
  1. package/README.md +7 -17
  2. package/dist/terra-route.cjs +1 -1
  3. package/dist/terra-route.cjs.map +1 -1
  4. package/dist/terra-route.d.ts +1 -2
  5. package/dist/terra-route.modern.js +1 -1
  6. package/dist/terra-route.modern.js.map +1 -1
  7. package/dist/terra-route.module.js +1 -1
  8. package/dist/terra-route.module.js.map +1 -1
  9. package/dist/terra-route.umd.js +1 -1
  10. package/dist/terra-route.umd.js.map +1 -1
  11. package/instructions.md +13 -0
  12. package/package.json +2 -1
  13. package/src/terra-route.compare.spec.ts +81 -0
  14. package/src/terra-route.spec.ts +576 -0
  15. package/src/terra-route.ts +370 -154
  16. package/src/graph/graph.spec.ts +0 -238
  17. package/src/graph/graph.ts +0 -212
  18. package/src/graph/methods/bounding-box.spec.ts +0 -199
  19. package/src/graph/methods/bounding-box.ts +0 -85
  20. package/src/graph/methods/connected.spec.ts +0 -219
  21. package/src/graph/methods/connected.ts +0 -168
  22. package/src/graph/methods/duplicates.spec.ts +0 -161
  23. package/src/graph/methods/duplicates.ts +0 -117
  24. package/src/graph/methods/leaf.spec.ts +0 -224
  25. package/src/graph/methods/leaf.ts +0 -88
  26. package/src/graph/methods/nodes.spec.ts +0 -317
  27. package/src/graph/methods/nodes.ts +0 -77
  28. package/src/graph/methods/spatial-index/geokdbush.spec.ts +0 -86
  29. package/src/graph/methods/spatial-index/geokdbush.ts +0 -189
  30. package/src/graph/methods/spatial-index/kdbush.spec.ts +0 -67
  31. package/src/graph/methods/spatial-index/kdbush.ts +0 -189
  32. package/src/graph/methods/spatial-index/tinyqueue.spec.ts +0 -51
  33. package/src/graph/methods/spatial-index/tinyqueue.ts +0 -108
  34. package/src/graph/methods/unify.spec.ts +0 -475
  35. package/src/graph/methods/unify.ts +0 -132
  36. package/src/graph/methods/unique.spec.ts +0 -65
  37. package/src/graph/methods/unique.ts +0 -69
@@ -1,117 +0,0 @@
1
- import { FeatureCollection, LineString, Feature, Position } from 'geojson'
2
-
3
- /**
4
- * Are the two coordinate sequences exactly equal (in same order)?
5
- */
6
- function areCoordinatesEqual(a: Position[], b: Position[]): boolean {
7
- if (a.length !== b.length) {
8
- return false
9
- }
10
-
11
- for (let i = 0; i < a.length; i++) {
12
- if (a[i][0] !== b[i][0] || a[i][1] !== b[i][1]) {
13
- return false
14
- }
15
- }
16
-
17
- return true
18
- }
19
-
20
- /**
21
- * Is `sub` a contiguous subsequence of `full`, either forward or reversed?
22
- */
23
- function isSubsequence(sub: Position[], full: Position[]): boolean {
24
- const subLength = sub.length
25
- const fullLength = full.length
26
-
27
- if (subLength > fullLength) {
28
- return false
29
- }
30
-
31
- // check forward
32
- for (let start = 0; start <= fullLength - subLength; start++) {
33
- let matches = true
34
-
35
- for (let offset = 0; offset < subLength; offset++) {
36
- if (
37
- full[start + offset][0] !== sub[offset][0] ||
38
- full[start + offset][1] !== sub[offset][1]
39
- ) {
40
- matches = false
41
- break
42
- }
43
- }
44
-
45
- if (matches) {
46
- return true
47
- }
48
- }
49
-
50
- // check reversed
51
- const reversedSub = [...sub].reverse()
52
-
53
- for (let start = 0; start <= fullLength - subLength; start++) {
54
- let matches = true
55
-
56
- for (let offset = 0; offset < subLength; offset++) {
57
- if (
58
- full[start + offset][0] !== reversedSub[offset][0] ||
59
- full[start + offset][1] !== reversedSub[offset][1]
60
- ) {
61
- matches = false
62
- break
63
- }
64
- }
65
-
66
- if (matches) {
67
- return true
68
- }
69
- }
70
-
71
- return false
72
- }
73
-
74
- /**
75
- * Remove any LineString that is either
76
- * - an exact duplicate of an earlier one, or
77
- * - a contiguous subsequence (in either direction) of any other.
78
- */
79
- export function removeDuplicateAndSubsectionLines(
80
- collection: FeatureCollection<LineString>
81
- ): FeatureCollection<LineString> {
82
- const features = collection.features
83
- const toRemove = new Set<number>()
84
-
85
- for (let i = 0; i < features.length; i++) {
86
- const coordsI = features[i].geometry.coordinates
87
-
88
- for (let j = 0; j < features.length; j++) {
89
- if (i === j) {
90
- continue
91
- }
92
-
93
- const coordsJ = features[j].geometry.coordinates
94
-
95
- // if coordsI are a subsequence of coordsJ, OR exactly equal,
96
- // then mark coordsI for removal—but only if
97
- // • coordsI is strictly shorter than coordsJ, or
98
- // • they’re the same length and this is the later duplicate (i > j)
99
- if (
100
- isSubsequence(coordsI, coordsJ) &&
101
- (
102
- coordsI.length < coordsJ.length ||
103
- (coordsI.length === coordsJ.length && i > j)
104
- )
105
- ) {
106
- toRemove.add(i)
107
- break
108
- }
109
- }
110
- }
111
-
112
- const filtered = features.filter((_, index) => !toRemove.has(index))
113
- return {
114
- type: 'FeatureCollection',
115
- features: filtered
116
- }
117
- }
@@ -1,224 +0,0 @@
1
- import { FeatureCollection, LineString } from 'geojson';
2
- import { createFeatureCollection } from '../../test-utils/create';
3
- import { getLeafEdges } from './leaf';
4
-
5
- describe('getLeafEdges', () => {
6
-
7
- describe('leafEdges', () => {
8
- describe('for an empty feature collection', () => {
9
- it('returns 0 nodes and edges', () => {
10
- const input: FeatureCollection<LineString> = createFeatureCollection([]);
11
- const output = getLeafEdges(input).leafEdges
12
-
13
- expect(output).toEqual(createFeatureCollection([]));
14
- });
15
- });
16
-
17
- describe('for feature collection with 1 linestring', () => {
18
- it('returns the single linestring as a leaf edge', () => {
19
- const input: FeatureCollection<LineString> = createFeatureCollection([
20
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} }
21
- ]);
22
- const output = getLeafEdges(input).leafEdges
23
-
24
- expect(output.features.length).toBe(1);
25
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
26
- });
27
-
28
- it('returns the two linestring as as leaf edge for long linestring', () => {
29
- const input: FeatureCollection<LineString> = createFeatureCollection([
30
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1], [2, 2]] }, properties: {} }
31
- ]);
32
- const output = getLeafEdges(input).leafEdges
33
-
34
- expect(output.features.length).toBe(2);
35
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
36
- expect(output.features[1].geometry.coordinates).toEqual([[1, 1], [2, 2]]);
37
- });
38
- });
39
-
40
- describe('for feature collection with 2 linestrings', () => {
41
- it('returns both linestrings if they are unconnected leaf edges', () => {
42
- const input: FeatureCollection<LineString> = createFeatureCollection([
43
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
44
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[2, 2], [3, 3]] }, properties: {} }
45
- ]);
46
- const output = getLeafEdges(input).leafEdges
47
- expect(output.features.length).toBe(2);
48
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
49
- expect(output.features[1].geometry.coordinates).toEqual([[2, 2], [3, 3]]);
50
- });
51
-
52
- it('returns both linestrings if they are connected leaf edges', () => {
53
- const input: FeatureCollection<LineString> = createFeatureCollection([
54
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
55
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[1, 1], [2, 2]] }, properties: {} }
56
- ]);
57
- const output = getLeafEdges(input).leafEdges
58
- expect(output.features.length).toBe(2);
59
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
60
- expect(output.features[1].geometry.coordinates).toEqual([[1, 1], [2, 2]]);
61
- });
62
-
63
- it('returns two linestrings if one is a subsection of the other but they are both leaf edges', () => {
64
- const input: FeatureCollection<LineString> = createFeatureCollection([
65
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
66
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1], [2, 2]] }, properties: {} }
67
- ]);
68
- const output = getLeafEdges(input).leafEdges
69
- expect(output.features.length).toBe(2);
70
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
71
- expect(output.features[1].geometry.coordinates).toEqual([[1, 1], [2, 2]]);
72
- });
73
-
74
- it('handles duplicates', () => {
75
- const input: FeatureCollection<LineString> = createFeatureCollection([
76
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
77
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} }
78
- ]);
79
- const output = getLeafEdges(input).leafEdges
80
- expect(output.features.length).toBe(1);
81
- });
82
- });
83
-
84
- describe('for feature collection with 3 linestrings', () => {
85
- it('returns 3 leafs when all are disconnected', () => {
86
- const input: FeatureCollection<LineString> = createFeatureCollection([
87
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
88
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[2, 2], [3, 3]] }, properties: {} },
89
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[4, 4], [5, 5]] }, properties: {} }
90
- ]);
91
- const output = getLeafEdges(input).leafEdges
92
- expect(output.features.length).toBe(3);
93
- });
94
-
95
- it('returns 3 leafs when all three linestrings are connected by a shared central point', () => {
96
- const input: FeatureCollection<LineString> = createFeatureCollection([
97
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
98
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[1, 1], [2, 2]] }, properties: {} },
99
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[1, 1], [3, 3]] }, properties: {} }
100
- ]);
101
- const output = getLeafEdges(input).leafEdges
102
- expect(output.features.length).toBe(3);
103
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
104
- expect(output.features[1].geometry.coordinates).toEqual([[1, 1], [2, 2]]);
105
- expect(output.features[2].geometry.coordinates).toEqual([[1, 1], [3, 3]]);
106
- });
107
-
108
- it('returns 3 leafs when two are connected and one is disconnected', () => {
109
- const input: FeatureCollection<LineString> = createFeatureCollection([
110
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
111
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[1, 1], [2, 2]] }, properties: {} },
112
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[3, 3], [4, 4]] }, properties: {} }
113
- ]);
114
- const output = getLeafEdges(input).leafEdges
115
- expect(output.features.length).toBe(3);
116
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
117
- expect(output.features[1].geometry.coordinates).toEqual([[1, 1], [2, 2]]);
118
- expect(output.features[2].geometry.coordinates).toEqual([[3, 3], [4, 4]]);
119
- });
120
-
121
- it('returns 3 leafs when one linestring is a subsection of another and 3rd is disconnected', () => {
122
- const input: FeatureCollection<LineString> = createFeatureCollection([
123
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
124
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1], [2, 2]] }, properties: {} },
125
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[3, 3], [4, 4]] }, properties: {} }
126
- ]);
127
- const output = getLeafEdges(input).leafEdges
128
- expect(output.features.length).toBe(3);
129
- });
130
- });
131
- });
132
-
133
- describe('nonLeafEdges', () => {
134
- it('returns an empty feature collection for empty input', () => {
135
- const input: FeatureCollection<LineString> = createFeatureCollection([]);
136
- const output = getLeafEdges(input).nonLeafEdges;
137
-
138
- expect(output).toEqual(createFeatureCollection([]));
139
- });
140
-
141
- it('returns no linestrings for a single input linestring as it would be a leaf edge', () => {
142
- const input: FeatureCollection<LineString> = createFeatureCollection([
143
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} }
144
- ]);
145
- const output = getLeafEdges(input).nonLeafEdges;
146
-
147
- expect(output.features.length).toBe(0);
148
- });
149
-
150
- it('returns no linestrings for two unconnected linestrings as they would be leaf edges', () => {
151
- const input: FeatureCollection<LineString> = createFeatureCollection([
152
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
153
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[2, 2], [3, 3]] }, properties: {} }
154
- ]);
155
- const output = getLeafEdges(input).nonLeafEdges;
156
-
157
- expect(output.features.length).toBe(0);
158
- });
159
-
160
- it('returns no linestrings for two connected linestrings as they would be leaf edges', () => {
161
- const input: FeatureCollection<LineString> = createFeatureCollection([
162
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
163
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[1, 1], [2, 2]] }, properties: {} }
164
- ]);
165
- const output = getLeafEdges(input).nonLeafEdges;
166
-
167
- expect(output.features.length).toBe(0);
168
- });
169
-
170
- it('returns no linestrings if one is a subsection of the other as they would be leaf edges', () => {
171
- const input: FeatureCollection<LineString> = createFeatureCollection([
172
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
173
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1], [2, 2]] }, properties: {} }
174
- ]);
175
- const output = getLeafEdges(input).nonLeafEdges;
176
-
177
- expect(output.features.length).toBe(0);
178
- });
179
-
180
- it('returns no linestrings for two linestrings that are duplicates', () => {
181
- const input: FeatureCollection<LineString> = createFeatureCollection([
182
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
183
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} }
184
- ]);
185
- const output = getLeafEdges(input).nonLeafEdges;
186
-
187
- expect(output.features.length).toBe(0);
188
- });
189
-
190
- it('returns no linestrings for two linestrings that are inverse duplicates', () => {
191
- const input: FeatureCollection<LineString> = createFeatureCollection([
192
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
193
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[1, 1], [0, 0]] }, properties: {} }
194
- ]);
195
- const output = getLeafEdges(input).nonLeafEdges;
196
-
197
- expect(output.features.length).toBe(0);
198
- });
199
-
200
- it('returns a no linestring for three linestrings where two are connected and one is disconnected', () => {
201
- const input: FeatureCollection<LineString> = createFeatureCollection([
202
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
203
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[1, 1], [2, 2]] }, properties: {} },
204
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[3, 3], [4, 4]] }, properties: {} }
205
- ]);
206
- const output = getLeafEdges(input).nonLeafEdges;
207
-
208
- expect(output.features.length).toBe(0);
209
- });
210
-
211
- it('returns a linestring for three linestrings when one is a middle edge and the other two are leaf edges', () => {
212
- const input: FeatureCollection<LineString> = createFeatureCollection([
213
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
214
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[1, 1], [2, 2]] }, properties: {} },
215
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[2, 2], [3, 3]] }, properties: {} }
216
- ]);
217
- const output = getLeafEdges(input).nonLeafEdges;
218
-
219
- expect(output.features.length).toBe(1);
220
- expect(output.features[0].geometry.coordinates).toEqual([[1, 1], [2, 2]]);
221
- });
222
- })
223
- });
224
-
@@ -1,88 +0,0 @@
1
- import { FeatureCollection, LineString, Feature } from 'geojson';
2
- import { graphGetUniqueSegments } from './unique';
3
-
4
- /**
5
- * Separates a graph's edges into leaf and non-leaf edges.
6
- * A leaf edge has a start or end node with degree 1.
7
- *
8
- * @param edgesFc - FeatureCollection containing LineString features representing edges of a graph
9
- * @returns Object containing two FeatureCollections: leafEdges and nonLeafEdges
10
- */
11
- export function getLeafEdges(
12
- edgesFc: FeatureCollection<LineString>
13
- ): {
14
- leafEdges: FeatureCollection<LineString>;
15
- nonLeafEdges: FeatureCollection<LineString>;
16
- } {
17
- const edges = graphGetUniqueSegments(edgesFc);
18
-
19
- const endpointCountMap: Map<string, number> = new Map();
20
-
21
- function coordKey(position: number[]): string {
22
- return position.join(",");
23
- }
24
-
25
- // Count the degree (number of edge endpoints) for each coordinate
26
- for (let i = 0; i < edges.features.length; i++) {
27
- const feature = edges.features[i];
28
- const coordinates = feature.geometry.coordinates;
29
-
30
- if (coordinates.length < 2) {
31
- continue;
32
- }
33
-
34
- const startKey = coordKey(coordinates[0]);
35
- const endKey = coordKey(coordinates[coordinates.length - 1]);
36
-
37
- endpointCountMap.set(
38
- startKey,
39
- (endpointCountMap.get(startKey) || 0) + 1
40
- );
41
- endpointCountMap.set(
42
- endKey,
43
- (endpointCountMap.get(endKey) || 0) + 1
44
- );
45
- }
46
-
47
- const leafEdgeMap: Map<string, Feature<LineString>> = new Map();
48
- const nonLeafEdgeMap: Map<string, Feature<LineString>> = new Map();
49
-
50
- for (let i = 0; i < edges.features.length; i++) {
51
- const feature = edges.features[i];
52
- const coordinates = feature.geometry.coordinates;
53
-
54
- if (coordinates.length < 2) {
55
- continue;
56
- }
57
-
58
- const startKey = coordKey(coordinates[0]);
59
- const endKey = coordKey(coordinates[coordinates.length - 1]);
60
-
61
- const startCount = endpointCountMap.get(startKey) || 0;
62
- const endCount = endpointCountMap.get(endKey) || 0;
63
-
64
- const edgeKey = coordinates.map(coordKey).join(";");
65
-
66
- if (startCount === 1 || endCount === 1) {
67
- if (!leafEdgeMap.has(edgeKey)) {
68
- leafEdgeMap.set(edgeKey, feature);
69
- }
70
- } else {
71
- if (!nonLeafEdgeMap.has(edgeKey)) {
72
- nonLeafEdgeMap.set(edgeKey, feature);
73
- }
74
- }
75
- }
76
-
77
- return {
78
- leafEdges: {
79
- type: "FeatureCollection",
80
- features: Array.from(leafEdgeMap.values())
81
- },
82
- nonLeafEdges: {
83
- type: "FeatureCollection",
84
- features: Array.from(nonLeafEdgeMap.values())
85
- }
86
- };
87
- }
88
-