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,85 +0,0 @@
1
- import { Feature, FeatureCollection, LineString, Position } from 'geojson'
2
-
3
- /**
4
- * Type representing a bounding box as [minLng, minLat, maxLng, maxLat]
5
- */
6
- export type BoundingBox = [number, number, number, number]
7
-
8
- /**
9
- * Filters a FeatureCollection of LineString features to only include LineStrings
10
- * that are completely within the specified bounding box.
11
- * @param featureCollection - A GeoJSON FeatureCollection containing LineString features
12
- * @param boundingBox - A bounding box array in the format [minLng, minLat, maxLng, maxLat]
13
- * @returns A new FeatureCollection<LineString> containing only the LineStrings completely within the bounding box
14
- */
15
- export function getNetworkInBoundingBox(
16
- featureCollection: FeatureCollection<LineString>,
17
- boundingBox: BoundingBox
18
- ): FeatureCollection<LineString> {
19
- const [minLng, minLat, maxLng, maxLat] = boundingBox
20
-
21
- // Validate bounding box
22
- if (minLng >= maxLng || minLat >= maxLat) {
23
- throw new Error('Invalid bounding box: min values must be less than max values')
24
- }
25
-
26
- const filteredFeatures: Feature<LineString>[] = []
27
-
28
- for (const feature of featureCollection.features) {
29
- if (isLineStringCompletelyWithinBounds(feature, minLng, minLat, maxLng, maxLat)) {
30
- filteredFeatures.push(feature)
31
- }
32
- }
33
-
34
- return {
35
- type: 'FeatureCollection',
36
- features: filteredFeatures
37
- }
38
- }
39
-
40
- /**
41
- * Checks if a LineString feature is completely within the specified bounds.
42
- * @param lineStringFeature - A GeoJSON Feature<LineString>
43
- * @param minLng - Minimum longitude
44
- * @param minLat - Minimum latitude
45
- * @param maxLng - Maximum longitude
46
- * @param maxLat - Maximum latitude
47
- * @returns true if all coordinates of the LineString are within the bounds, false otherwise
48
- */
49
- function isLineStringCompletelyWithinBounds(
50
- lineStringFeature: Feature<LineString>,
51
- minLng: number,
52
- minLat: number,
53
- maxLng: number,
54
- maxLat: number
55
- ): boolean {
56
- const coordinates = lineStringFeature.geometry.coordinates
57
-
58
- for (const coordinate of coordinates) {
59
- if (!isCoordinateWithinBounds(coordinate, minLng, minLat, maxLng, maxLat)) {
60
- return false
61
- }
62
- }
63
-
64
- return true
65
- }
66
-
67
- /**
68
- * Checks if a coordinate is within the specified bounds.
69
- * @param coordinate - A coordinate position [lng, lat]
70
- * @param minLng - Minimum longitude
71
- * @param minLat - Minimum latitude
72
- * @param maxLng - Maximum longitude
73
- * @param maxLat - Maximum latitude
74
- * @returns true if the coordinate is within the bounds, false otherwise
75
- */
76
- function isCoordinateWithinBounds(
77
- coordinate: Position,
78
- minLng: number,
79
- minLat: number,
80
- maxLng: number,
81
- maxLat: number
82
- ): boolean {
83
- const [lng, lat] = coordinate
84
- return lng >= minLng && lng <= maxLng && lat >= minLat && lat <= maxLat
85
- }
@@ -1,219 +0,0 @@
1
- import { FeatureCollection, LineString } from 'geojson';
2
- import { graphGetConnectedComponentCount, graphGetConnectedComponents } from './connected';
3
- import { generateTreeFeatureCollection } from '../../test-utils/generate-network';
4
- import { createFeatureCollection, createLineStringFeature } from '../../test-utils/create';
5
- import { readFileSync, writeFileSync } from 'fs';
6
- import { unifyCloseCoordinates } from './unify';
7
-
8
- describe('countConnectedComponents', () => {
9
- describe('for an empty feature collection', () => {
10
- it('returns 0', () => {
11
- const input: FeatureCollection<LineString> = createFeatureCollection([]);
12
- const output = graphGetConnectedComponentCount(input);
13
-
14
- expect(output).toBe(0);
15
- });
16
- });
17
-
18
- describe('for feature collection with 1 linestring', () => {
19
- it('returns 1', () => {
20
- const input: FeatureCollection<LineString> = createFeatureCollection([
21
- createLineStringFeature([[0, 0], [1, 1]])
22
- ]);
23
- const output = graphGetConnectedComponentCount(input);
24
-
25
- expect(output).toBe(1);
26
- });
27
- });
28
-
29
- describe('for feature collection with 2 linestring', () => {
30
- it('returns 1 if connected', () => {
31
- const input: FeatureCollection<LineString> = createFeatureCollection([
32
- createLineStringFeature([[0, 0], [1, 1]]),
33
- createLineStringFeature([[1, 1], [2, 2]]),
34
-
35
- ]);
36
- const output = graphGetConnectedComponentCount(input);
37
-
38
- expect(output).toBe(1);
39
- });
40
-
41
- it('returns 2 if unconnected', () => {
42
- const input: FeatureCollection<LineString> = createFeatureCollection([
43
- createLineStringFeature([[0, 0], [1, 1]]),
44
- createLineStringFeature([[10, 10], [11, 11]]),
45
-
46
- ]);
47
- const output = graphGetConnectedComponentCount(input);
48
-
49
- expect(output).toBe(2);
50
- });
51
- });
52
-
53
- describe('for feature collection with 3 linestring', () => {
54
- it('returns 1 if connected', () => {
55
- const input: FeatureCollection<LineString> = createFeatureCollection([
56
- createLineStringFeature([[0, 0], [1, 1]]),
57
- createLineStringFeature([[1, 1], [2, 2]]),
58
- createLineStringFeature([[2, 2], [3, 3]]),
59
-
60
- ]);
61
- const output = graphGetConnectedComponentCount(input);
62
-
63
- expect(output).toBe(1);
64
- });
65
-
66
- it('returns 3 if unconnected', () => {
67
- const input: FeatureCollection<LineString> = createFeatureCollection([
68
- createLineStringFeature([[0, 0], [1, 1]]),
69
- createLineStringFeature([[10, 10], [11, 11]]),
70
- createLineStringFeature([[20, 20], [21, 21]]),
71
- ]);
72
- const output = graphGetConnectedComponentCount(input);
73
-
74
- expect(output).toBe(3);
75
- });
76
- });
77
-
78
-
79
- describe('for feature collection with multiple linestring', () => {
80
- it('returns 1 when all lines share the same coordinate', () => {
81
- const input = createFeatureCollection([
82
- createLineStringFeature([[0, 0], [1, 1]]),
83
- createLineStringFeature([[1, 1], [2, 2]]),
84
- createLineStringFeature([[1, 1], [3, 3]]),
85
- createLineStringFeature([[4, 4], [1, 1]]),
86
- ]);
87
- const output = graphGetConnectedComponentCount(input);
88
-
89
- expect(output).toBe(1);
90
- });
91
-
92
- it('returns 2 when two disconnected groups exist', () => {
93
- const input = createFeatureCollection([
94
- createLineStringFeature([[0, 0], [1, 1]]),
95
- createLineStringFeature([[1, 1], [2, 2]]),
96
- createLineStringFeature([[10, 10], [11, 11]]),
97
- createLineStringFeature([[11, 11], [12, 12]]),
98
- ]);
99
- const output = graphGetConnectedComponentCount(input);
100
-
101
- expect(output).toBe(2);
102
- });
103
-
104
-
105
- it('returns 1 for a loop of connected lines', () => {
106
- const input = createFeatureCollection([
107
- createLineStringFeature([[0, 0], [1, 0]]),
108
- createLineStringFeature([[1, 0], [1, 1]]),
109
- createLineStringFeature([[1, 1], [0, 1]]),
110
- createLineStringFeature([[0, 1], [0, 0]]),
111
- ]);
112
- const output = graphGetConnectedComponentCount(input);
113
-
114
- expect(output).toBe(1);
115
- });
116
- });
117
-
118
- describe('for complex linestring network', () => {
119
- it('returns 1 when tree is connected', () => {
120
- const input = generateTreeFeatureCollection(3, 2)
121
- const output = graphGetConnectedComponentCount(input)
122
-
123
- expect(output).toBe(1);
124
- });
125
-
126
- it('returns 1 when it is connected correctly', () => {
127
- const input = generateTreeFeatureCollection(3, 2)
128
- const output = graphGetConnectedComponentCount(input)
129
-
130
- expect(output).toBe(1);
131
- });
132
- })
133
- });
134
-
135
- describe('splitIntoConnectedComponents', () => {
136
- it('returns empty array for empty feature collection', () => {
137
- const input: FeatureCollection<LineString> = createFeatureCollection([]);
138
- const output = graphGetConnectedComponents(input);
139
-
140
- expect(output).toEqual([]);
141
- });
142
-
143
- it('returns single component for single linestring', () => {
144
- const input: FeatureCollection<LineString> = createFeatureCollection([
145
- createLineStringFeature([[0, 0], [1, 1]])
146
- ]);
147
- const output = graphGetConnectedComponents(input);
148
-
149
- expect(output).toHaveLength(1);
150
- expect(output[0].features).toHaveLength(1);
151
- });
152
-
153
- it('returns multiple components for disconnected linestrings', () => {
154
- const input: FeatureCollection<LineString> = createFeatureCollection([
155
- createLineStringFeature([[0, 0], [1, 1]]),
156
- createLineStringFeature([[10, 10], [11, 11]])
157
- ]);
158
- const output = graphGetConnectedComponents(input);
159
-
160
- expect(output).toHaveLength(2);
161
- expect(output[0].features).toHaveLength(1);
162
- expect(output[1].features).toHaveLength(1);
163
- });
164
-
165
- it('returns single component for connected linestrings', () => {
166
- const input: FeatureCollection<LineString> = createFeatureCollection([
167
- createLineStringFeature([[0, 0], [1, 1]]),
168
- createLineStringFeature([[1, 1], [2, 2]])
169
- ]);
170
- const output = graphGetConnectedComponents(input);
171
-
172
- expect(output).toHaveLength(1);
173
- expect(output[0].features).toHaveLength(2);
174
- expect(output[0].features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
175
- expect(output[0].features[1].geometry.coordinates).toEqual([[1, 1], [2, 2]]);
176
- });
177
-
178
- it('returns multiple components for selection of connected linestrings', () => {
179
- const input: FeatureCollection<LineString> = createFeatureCollection([
180
- createLineStringFeature([[0, 0], [1, 1]]),
181
- createLineStringFeature([[1, 1], [2, 2]]),
182
- createLineStringFeature([[10, 10], [11, 11]]),
183
- createLineStringFeature([[20, 20], [21, 21]])
184
- ]);
185
- const output = graphGetConnectedComponents(input);
186
-
187
- expect(output).toHaveLength(3);
188
- expect(output[0].features).toHaveLength(1);
189
- expect(output[1].features).toHaveLength(1);
190
- expect(output[2].features).toHaveLength(2);
191
- });
192
-
193
- it('returns single component for complex selection of linestrings', () => {
194
- const network = JSON.parse(readFileSync('src/data/network.geojson', 'utf-8')) as FeatureCollection<LineString>;
195
- const output = graphGetConnectedComponents(network);
196
- expect(output).toHaveLength(1);
197
- expect(output[0].features).toHaveLength(network.features.length);
198
- });
199
-
200
- it('returns multiple components for complex selection of linestrings', () => {
201
- const network = JSON.parse(readFileSync('src/data/network-5-cc.geojson', 'utf-8')) as FeatureCollection<LineString>;
202
- const output = graphGetConnectedComponents(network);
203
- expect(output).toHaveLength(5);
204
- expect(output[4].features > output[0].features).toBeTruthy();
205
- });
206
-
207
- it('ensures splitIntoConnectedComponents and countConnectedComponents are consistent', () => {
208
- const network = JSON.parse(readFileSync('src/data/network.geojson', 'utf-8')) as FeatureCollection<LineString>;
209
- const components = graphGetConnectedComponents(network);
210
- const count = graphGetConnectedComponentCount(network);
211
- expect(components.length).toBe(count);
212
-
213
- const networkMultipleCC = JSON.parse(readFileSync('src/data/network-5-cc.geojson', 'utf-8')) as FeatureCollection<LineString>;
214
- const componentsMultipleCC = graphGetConnectedComponents(networkMultipleCC);
215
- const countMultipleCC = graphGetConnectedComponentCount(networkMultipleCC);
216
- expect(componentsMultipleCC.length).toBe(countMultipleCC);
217
- });
218
-
219
- })
@@ -1,168 +0,0 @@
1
- import { Feature, FeatureCollection, LineString, Position } from 'geojson'
2
-
3
- /**
4
- * Counts the number of connected components in a graph represented by LineString features in a GeoJSON FeatureCollection.
5
- * Each LineString is treated as an edge in the graph, and connected components are determined by shared coordinates.
6
- * @param featureCollection - A GeoJSON FeatureCollection containing LineString features
7
- * @returns The number of connected components in the graph represented by the LineStrings
8
- */
9
- export function graphGetConnectedComponentCount(
10
- featureCollection: FeatureCollection<LineString>
11
- ): number {
12
- const features = featureCollection.features
13
- const numberOfFeatures = features.length
14
-
15
- // Map coordinates to feature indices
16
- const coordinateToFeatureIndices = new Map<string, number[]>()
17
-
18
- for (let index = 0; index < numberOfFeatures; index++) {
19
- const coordinates = features[index].geometry.coordinates
20
-
21
- for (const coordinate of coordinates) {
22
- const key = coordinateKey(coordinate)
23
-
24
- if (!coordinateToFeatureIndices.has(key)) {
25
- coordinateToFeatureIndices.set(key, [])
26
- }
27
-
28
- coordinateToFeatureIndices.get(key)!.push(index)
29
- }
30
- }
31
-
32
- // Build adjacency list for the graph
33
- const adjacencyList: number[][] = Array.from({ length: numberOfFeatures }, () => [])
34
-
35
- for (const indices of coordinateToFeatureIndices.values()) {
36
- for (let i = 0; i < indices.length; i++) {
37
- for (let j = i + 1; j < indices.length; j++) {
38
- const a = indices[i]
39
- const b = indices[j]
40
- adjacencyList[a].push(b)
41
- adjacencyList[b].push(a)
42
- }
43
- }
44
- }
45
-
46
- const visited = new Array<boolean>(numberOfFeatures).fill(false)
47
- let connectedComponents = 0
48
-
49
- for (let index = 0; index < numberOfFeatures; index++) {
50
- if (!visited[index]) {
51
- dfs(index, adjacencyList, visited)
52
- connectedComponents++
53
- }
54
- }
55
-
56
- return connectedComponents
57
- }
58
-
59
- /**
60
- * Depth-first search to mark all reachable nodes from the given index.
61
- * @param index - The current node index to start DFS from.
62
- * @param adjacencyList - The adjacency list representing the graph.
63
- * @param visited - An array to keep track of visited nodes.
64
- */
65
- function dfs(index: number, adjacencyList: number[][], visited: boolean[]): void {
66
- visited[index] = true
67
-
68
- for (const neighbor of adjacencyList[index]) {
69
- if (!visited[neighbor]) {
70
- dfs(neighbor, adjacencyList, visited)
71
- }
72
- }
73
- }
74
-
75
- function coordinateKey(position: Position): string {
76
- return `${position[0]},${position[1]}`
77
- }
78
-
79
- export function graphGetConnectedComponents(
80
- featureCollection: FeatureCollection<LineString>
81
- ): FeatureCollection<LineString>[] {
82
- const features = featureCollection.features
83
- const graph: Map<number, Set<number>> = new Map()
84
- const coordinateMap: Map<string, Set<number>> = new Map()
85
-
86
- function coordinateKey(coordinate: Position): string {
87
- return `${coordinate[0]},${coordinate[1]}`
88
- }
89
-
90
- // Build coordinate map: coordinate string -> Set of feature indices
91
- for (let index = 0; index < features.length; index++) {
92
- const coordinates = features[index].geometry.coordinates
93
-
94
- for (const coordinate of coordinates) {
95
- const key = coordinateKey(coordinate)
96
-
97
- if (!coordinateMap.has(key)) {
98
- coordinateMap.set(key, new Set())
99
- }
100
-
101
- coordinateMap.get(key)!.add(index)
102
- }
103
- }
104
-
105
- // Build adjacency list for graph
106
- for (let index = 0; index < features.length; index++) {
107
- graph.set(index, new Set())
108
-
109
- const coordinates = features[index].geometry.coordinates
110
- for (const coordinate of coordinates) {
111
- const key = coordinateKey(coordinate)
112
- const neighbors = coordinateMap.get(key)
113
-
114
- if (neighbors) {
115
- for (const neighborIndex of neighbors) {
116
- if (neighborIndex !== index) {
117
- graph.get(index)!.add(neighborIndex)
118
- }
119
- }
120
- }
121
- }
122
- }
123
-
124
- // DFS to find connected components
125
- const visited = new Set<number>()
126
- const components: FeatureCollection<LineString>[] = []
127
-
128
- function dfs(startIndex: number, currentComponent: Feature<LineString>[]): void {
129
- const stack: number[] = [startIndex]
130
-
131
- while (stack.length > 0) {
132
- const currentIndex = stack.pop()!
133
-
134
- if (visited.has(currentIndex)) {
135
- continue
136
- }
137
-
138
- visited.add(currentIndex)
139
- currentComponent.push(features[currentIndex])
140
-
141
- const neighbors = graph.get(currentIndex)
142
- if (neighbors) {
143
- for (const neighbor of neighbors) {
144
- if (!visited.has(neighbor)) {
145
- stack.push(neighbor)
146
- }
147
- }
148
- }
149
- }
150
- }
151
-
152
- for (let index = 0; index < features.length; index++) {
153
- if (!visited.has(index)) {
154
- const component: Feature<LineString>[] = []
155
- dfs(index, component)
156
- components.push({
157
- type: 'FeatureCollection',
158
- features: component
159
- })
160
- }
161
- }
162
-
163
-
164
- // Sort components by the number of features in ascending order
165
- components.sort((a, b) => a.features.length - b.features.length)
166
-
167
- return components
168
- }
@@ -1,161 +0,0 @@
1
- import { FeatureCollection, LineString } from 'geojson';
2
- import { createFeatureCollection } from '../../test-utils/create';
3
- import { removeDuplicateAndSubsectionLines } from './duplicates';
4
- import { readFileSync } from 'fs';
5
- import { graphGetNodeAndEdgeCount } from './nodes';
6
-
7
- describe('removeDuplicateAndSubsectionLines', () => {
8
- describe('for an empty feature collection', () => {
9
- it('returns 0', () => {
10
- const input: FeatureCollection<LineString> = createFeatureCollection([]);
11
- const output = removeDuplicateAndSubsectionLines(input);
12
-
13
- expect(output).toEqual(createFeatureCollection([]));
14
- });
15
- });
16
-
17
- describe('for feature collection with 1 linestring', () => {
18
- it('returns 1', () => {
19
- const input: FeatureCollection<LineString> = createFeatureCollection([
20
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} }
21
- ]);
22
- const output = removeDuplicateAndSubsectionLines(input);
23
-
24
- expect(output.features.length).toBe(1);
25
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
26
- });
27
- });
28
-
29
- describe('for feature collection with 2 linestrings', () => {
30
- it('handles duplicates', () => {
31
- const input: FeatureCollection<LineString> = createFeatureCollection([
32
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
33
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} }
34
- ]);
35
- const output = removeDuplicateAndSubsectionLines(input);
36
-
37
- expect(output.features.length).toBe(1);
38
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
39
- });
40
-
41
- it('returns one linestring if one is a subsection of the other', () => {
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: [[0, 0], [1, 1], [2, 2]] }, properties: {} }
45
- ]);
46
- const output = removeDuplicateAndSubsectionLines(input);
47
-
48
- expect(output.features.length).toBe(1);
49
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1], [2, 2]]);
50
- });
51
-
52
- it('returns both if they are not subsections or duplicates', () => {
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: [[2, 2], [3, 3]] }, properties: {} }
56
- ]);
57
- const output = removeDuplicateAndSubsectionLines(input);
58
-
59
- expect(output.features.length).toBe(2);
60
- });
61
-
62
- it('returns one linestring if one is a inverse duplicate of the other', () => {
63
- const input: FeatureCollection<LineString> = createFeatureCollection([
64
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
65
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[1, 1], [0, 0]] }, properties: {} }
66
- ]);
67
- const output = removeDuplicateAndSubsectionLines(input);
68
- expect(output.features.length).toBe(1);
69
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
70
- });
71
- });
72
-
73
- describe('for feature collection with 3 linestrings', () => {
74
- it('returns 1 linestring if all are 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
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} }
79
- ]);
80
- const output = removeDuplicateAndSubsectionLines(input);
81
- expect(output.features.length).toBe(1);
82
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
83
- });
84
-
85
- it('returns 2 linestrings if one is a subsection of another', () => {
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: [[0, 0], [1, 1], [2, 2]] }, properties: {} },
89
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[3, 3], [4, 4]] }, properties: {} }
90
- ]);
91
- const output = removeDuplicateAndSubsectionLines(input);
92
- expect(output.features.length).toBe(2);
93
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1], [2, 2]]);
94
- expect(output.features[1].geometry.coordinates).toEqual([[3, 3], [4, 4]]);
95
- });
96
-
97
- it('returns all if none are subsections or duplicates', () => {
98
- const input: FeatureCollection<LineString> = createFeatureCollection([
99
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
100
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[2, 2], [3, 3]] }, properties: {} },
101
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[4, 4], [5, 5]] }, properties: {} }
102
- ]);
103
- const output = removeDuplicateAndSubsectionLines(input);
104
- expect(output.features.length).toBe(3);
105
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1]]);
106
- expect(output.features[1].geometry.coordinates).toEqual([[2, 2], [3, 3]]);
107
- expect(output.features[2].geometry.coordinates).toEqual([[4, 4], [5, 5]]);
108
- });
109
-
110
- it('returns 1 linestring if one is a subsection of another and the third is a duplicate', () => {
111
- const input: FeatureCollection<LineString> = createFeatureCollection([
112
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
113
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1], [2, 2]] }, properties: {} },
114
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} }
115
- ]);
116
- const output = removeDuplicateAndSubsectionLines(input);
117
- expect(output.features.length).toBe(1);
118
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1], [2, 2]]);
119
- });
120
-
121
- it('returns 1 linestring if one is a subsection of another and the third is an inverse duplicate', () => {
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: [[1, 1], [0, 0]] }, properties: {} }
126
- ]);
127
- const output = removeDuplicateAndSubsectionLines(input);
128
- expect(output.features.length).toBe(1);
129
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1], [2, 2]]);
130
- });
131
- });
132
-
133
- describe('for feature collection with multiple linestrings', () => {
134
- it('handles multiple duplicates and subsections', () => {
135
- const input: FeatureCollection<LineString> = createFeatureCollection([
136
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] }, properties: {} },
137
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1], [2, 2]] }, properties: {} },
138
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[3, 3], [4, 4]] }, properties: {} },
139
- { type: 'Feature', geometry: { type: 'LineString', coordinates: [[3, 3], [4, 4], [5, 5]] }, properties: {} }
140
- ]);
141
- const output = removeDuplicateAndSubsectionLines(input);
142
-
143
- expect(output.features.length).toBe(2);
144
- expect(output.features[0].geometry.coordinates).toEqual([[0, 0], [1, 1], [2, 2]]);
145
- expect(output.features[1].geometry.coordinates).toEqual([[3, 3], [4, 4], [5, 5]]);
146
- });
147
-
148
- it('ensures it does not effect the network in a complex scenario', () => {
149
- const network = JSON.parse(readFileSync('src/data/network.geojson', 'utf-8')) as FeatureCollection<LineString>;
150
-
151
- expect(network.features.length).toBe(811);
152
-
153
- const after = removeDuplicateAndSubsectionLines(network);
154
-
155
- // There are no duplicates or subsections in the network
156
- expect(after.features.length).toBe(811);
157
-
158
- expect(graphGetNodeAndEdgeCount(removeDuplicateAndSubsectionLines(after))).toEqual(graphGetNodeAndEdgeCount(network))
159
- });
160
- })
161
- });