scgraph 2.7.0__tar.gz → 2.8.0__tar.gz
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.
- {scgraph-2.7.0/scgraph.egg-info → scgraph-2.8.0}/PKG-INFO +5 -4
- {scgraph-2.7.0 → scgraph-2.8.0}/README.md +5 -4
- {scgraph-2.7.0 → scgraph-2.8.0}/pyproject.toml +1 -1
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/__init__.py +8 -5
- scgraph-2.8.0/scgraph/core.py +7 -0
- scgraph-2.7.0/scgraph/core.py → scgraph-2.8.0/scgraph/geograph.py +50 -420
- scgraph-2.8.0/scgraph/graph.py +551 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/grid.py +84 -10
- {scgraph-2.7.0 → scgraph-2.8.0/scgraph.egg-info}/PKG-INFO +5 -4
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph.egg-info/SOURCES.txt +2 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/setup.cfg +1 -1
- {scgraph-2.7.0 → scgraph-2.8.0}/LICENSE +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/cache.py +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/geographs/__init__.py +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/geographs/marnet.py +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/geographs/north_america_rail.py +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/geographs/oak_ridge_maritime.py +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/geographs/us_freeway.py +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/helpers/__init__.py +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/helpers/shape_mover_utils.py +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/spanning.py +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph/utils.py +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph.egg-info/dependency_links.txt +0 -0
- {scgraph-2.7.0 → scgraph-2.8.0}/scgraph.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scgraph
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.8.0
|
|
4
4
|
Summary: Determine an approximate route between two points on earth.
|
|
5
5
|
Author-email: Connor Makowski <conmak@mit.edu>
|
|
6
6
|
Project-URL: Homepage, https://github.com/connor-makowski/scgraph
|
|
@@ -41,9 +41,10 @@ Low Level: https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
|
41
41
|
- Algorithms:
|
|
42
42
|
- Dijkstra's algorithm (Modified for sparse networks)
|
|
43
43
|
- Modified to support sparse network data structures
|
|
44
|
-
-
|
|
45
|
-
- Modified for O(n) performance
|
|
46
|
-
|
|
44
|
+
- Modified Sparse Dijkstra algorithm
|
|
45
|
+
- Modified for O((n+m)log(n)) performance where n is the number of nodes and m is the number of edges
|
|
46
|
+
- Uses a priority queue to improve performance on large graphs
|
|
47
|
+
- A* algorithm (Extension of the Modified Sparse Dijkstra)
|
|
47
48
|
- Uses a heuristic function to improve performance on large graphs
|
|
48
49
|
- Note: The heuristic function is optional and defaults to Dijkstra's algorithm
|
|
49
50
|
- Possible future support for other algorithms
|
|
@@ -25,9 +25,10 @@ Low Level: https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
|
25
25
|
- Algorithms:
|
|
26
26
|
- Dijkstra's algorithm (Modified for sparse networks)
|
|
27
27
|
- Modified to support sparse network data structures
|
|
28
|
-
-
|
|
29
|
-
- Modified for O(n) performance
|
|
30
|
-
|
|
28
|
+
- Modified Sparse Dijkstra algorithm
|
|
29
|
+
- Modified for O((n+m)log(n)) performance where n is the number of nodes and m is the number of edges
|
|
30
|
+
- Uses a priority queue to improve performance on large graphs
|
|
31
|
+
- A* algorithm (Extension of the Modified Sparse Dijkstra)
|
|
31
32
|
- Uses a heuristic function to improve performance on large graphs
|
|
32
33
|
- Note: The heuristic function is optional and defaults to Dijkstra's algorithm
|
|
33
34
|
- Possible future support for other algorithms
|
|
@@ -363,4 +364,4 @@ Make sure Docker is installed and running on a Unix system (Linux, MacOS, WSL2).
|
|
|
363
364
|
|
|
364
365
|
|
|
365
366
|
## Attributions and Thanks
|
|
366
|
-
Originally inspired by [searoute](https://github.com/genthalili/searoute-py) including the use of one of their [datasets](https://github.com/genthalili/searoute-py/blob/main/searoute/data/marnet_densified_v2_old.geojson) that has been modified to work properly with this package.
|
|
367
|
+
Originally inspired by [searoute](https://github.com/genthalili/searoute-py) including the use of one of their [datasets](https://github.com/genthalili/searoute-py/blob/main/searoute/data/marnet_densified_v2_old.geojson) that has been modified to work properly with this package.
|
|
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
|
|
|
12
12
|
|
|
13
13
|
[project]
|
|
14
14
|
name = "scgraph"
|
|
15
|
-
version = "2.
|
|
15
|
+
version = "2.8.0"
|
|
16
16
|
description = "Determine an approximate route between two points on earth."
|
|
17
17
|
authors = [
|
|
18
18
|
{name="Connor Makowski", email="conmak@mit.edu"}
|
|
@@ -26,9 +26,10 @@ Low Level: https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
|
26
26
|
- Algorithms:
|
|
27
27
|
- Dijkstra's algorithm (Modified for sparse networks)
|
|
28
28
|
- Modified to support sparse network data structures
|
|
29
|
-
-
|
|
30
|
-
- Modified for O(n) performance
|
|
31
|
-
|
|
29
|
+
- Modified Sparse Dijkstra algorithm
|
|
30
|
+
- Modified for O((n+m)log(n)) performance where n is the number of nodes and m is the number of edges
|
|
31
|
+
- Uses a priority queue to improve performance on large graphs
|
|
32
|
+
- A* algorithm (Extension of the Modified Sparse Dijkstra)
|
|
32
33
|
- Uses a heuristic function to improve performance on large graphs
|
|
33
34
|
- Note: The heuristic function is optional and defaults to Dijkstra's algorithm
|
|
34
35
|
- Possible future support for other algorithms
|
|
@@ -364,7 +365,9 @@ Make sure Docker is installed and running on a Unix system (Linux, MacOS, WSL2).
|
|
|
364
365
|
|
|
365
366
|
|
|
366
367
|
## Attributions and Thanks
|
|
367
|
-
Originally inspired by [searoute](https://github.com/genthalili/searoute-py) including the use of one of their [datasets](https://github.com/genthalili/searoute-py/blob/main/searoute/data/marnet_densified_v2_old.geojson) that has been modified to work properly with this package.
|
|
368
|
+
Originally inspired by [searoute](https://github.com/genthalili/searoute-py) including the use of one of their [datasets](https://github.com/genthalili/searoute-py/blob/main/searoute/data/marnet_densified_v2_old.geojson) that has been modified to work properly with this package.
|
|
369
|
+
"""
|
|
368
370
|
|
|
369
|
-
from .
|
|
371
|
+
from .graph import Graph
|
|
372
|
+
from .geograph import GeoGraph
|
|
370
373
|
from .grid import GridGraph
|
|
@@ -1,424 +1,7 @@
|
|
|
1
1
|
from .utils import haversine, distance_converter, get_line_path, cheap_ruler
|
|
2
2
|
import json
|
|
3
|
-
from heapq import heappop, heappush
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
class Graph:
|
|
7
|
-
@staticmethod
|
|
8
|
-
def validate_graph(
|
|
9
|
-
graph: list[dict[int, int | float]],
|
|
10
|
-
check_symmetry: bool = True,
|
|
11
|
-
check_connected: bool = True,
|
|
12
|
-
) -> None:
|
|
13
|
-
"""
|
|
14
|
-
Function:
|
|
15
|
-
|
|
16
|
-
- Validate that a graph is properly formatted
|
|
17
|
-
|
|
18
|
-
Required Arguments:
|
|
19
|
-
|
|
20
|
-
- `graph`:
|
|
21
|
-
- Type: list of dictionaries with integer keys and integer or float values
|
|
22
|
-
- What: A list of dictionaries where the indicies are origin node ids and the values are dictionaries of destination node indices and graph weights
|
|
23
|
-
- Note: All nodes must be included as origins in the graph regardless of if they have any connected destinations
|
|
24
|
-
- EG:
|
|
25
|
-
```
|
|
26
|
-
[
|
|
27
|
-
# From London (index 0)
|
|
28
|
-
{
|
|
29
|
-
# To Paris (index 1)
|
|
30
|
-
1: 311,
|
|
31
|
-
},
|
|
32
|
-
# From Paris (index 1)
|
|
33
|
-
{
|
|
34
|
-
# To London (index 0)
|
|
35
|
-
0: 311,
|
|
36
|
-
# To Berlin (index 2)
|
|
37
|
-
2: 878,
|
|
38
|
-
# To Rome (index 3)
|
|
39
|
-
3: 1439,
|
|
40
|
-
# To Madrid (index 4)
|
|
41
|
-
4: 1053
|
|
42
|
-
},
|
|
43
|
-
# From Berlin (index 2)
|
|
44
|
-
{
|
|
45
|
-
# To Paris (index 1)
|
|
46
|
-
1: 878,
|
|
47
|
-
# To Rome (index 3)
|
|
48
|
-
3: 1181,
|
|
49
|
-
},
|
|
50
|
-
# From Rome (index 3)
|
|
51
|
-
{
|
|
52
|
-
# To Paris (index 1)
|
|
53
|
-
1: 1439,
|
|
54
|
-
# To Berlin (index 2)
|
|
55
|
-
2: 1181,
|
|
56
|
-
},
|
|
57
|
-
# From Madrid (index 4)
|
|
58
|
-
{
|
|
59
|
-
# To Paris (index 1)
|
|
60
|
-
1: 1053,
|
|
61
|
-
# To Lisbon (index 5)
|
|
62
|
-
5: 623
|
|
63
|
-
},
|
|
64
|
-
# From Lisbon (index 5)
|
|
65
|
-
{
|
|
66
|
-
# To Madrid (index 4)
|
|
67
|
-
4: 623
|
|
68
|
-
}
|
|
69
|
-
]
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Optional Arguments:
|
|
73
|
-
|
|
74
|
-
- `check_symmetry`
|
|
75
|
-
- Type: bool
|
|
76
|
-
- What: Whether to check that the graph is symmetric
|
|
77
|
-
- Default: True
|
|
78
|
-
- Note: This is forced to True if `check_connected` is True
|
|
79
|
-
- `check_connected`
|
|
80
|
-
- Type: bool
|
|
81
|
-
- What: Whether to check that the graph is fully connected
|
|
82
|
-
- Default: True
|
|
83
|
-
- Note: For computational efficiency, only symmetric graphs are checked for connectivity
|
|
84
|
-
- Note: If this is True, `check_symmetry` is forced to True and the graph will be checked for symmetry prior to checking for connectivity
|
|
85
|
-
"""
|
|
86
|
-
check_symmetry = check_symmetry or check_connected
|
|
87
|
-
assert isinstance(graph, list), "Your graph must be a list"
|
|
88
|
-
len_graph = len(graph)
|
|
89
|
-
for origin_id, origin_dict in enumerate(graph):
|
|
90
|
-
assert isinstance(
|
|
91
|
-
origin_dict, dict
|
|
92
|
-
), f"Your graph must be a list of dictionaries but the value for origin {origin_id} is not a dictionary"
|
|
93
|
-
destinations = list(origin_dict.keys())
|
|
94
|
-
lengths = list(origin_dict.values())
|
|
95
|
-
assert all(
|
|
96
|
-
[
|
|
97
|
-
(isinstance(i, int) and i >= 0 and i < len_graph)
|
|
98
|
-
for i in destinations
|
|
99
|
-
]
|
|
100
|
-
), f"Destination ids must be non-negative integers and equivalent to an existing index, but graph[{origin_id}] has an error in the destination ids"
|
|
101
|
-
assert all(
|
|
102
|
-
[(isinstance(i, (int, float)) and i >= 0) for i in lengths]
|
|
103
|
-
), f"Distances must be integers or floats, but graph[{origin_id}] contains a non-integer or non-float distance"
|
|
104
|
-
if check_symmetry:
|
|
105
|
-
for destination_id, distance in origin_dict.items():
|
|
106
|
-
assert (
|
|
107
|
-
graph[destination_id].get(origin_id) == distance
|
|
108
|
-
), f"Your graph is not symmetric, the distance from node {origin_id} to node {destination_id} is {distance} but the distance from node {destination_id} to node {origin_id} is {graph.get(destination_id, {}).get(origin_id)}"
|
|
109
|
-
if check_connected:
|
|
110
|
-
assert Graph.validate_connected(
|
|
111
|
-
graph
|
|
112
|
-
), "Your graph is not fully connected"
|
|
113
|
-
|
|
114
|
-
@staticmethod
|
|
115
|
-
def validate_connected(
|
|
116
|
-
graph: list[dict[int, int | float]], origin_id: int = 0
|
|
117
|
-
) -> bool:
|
|
118
|
-
"""
|
|
119
|
-
Function:
|
|
120
|
-
|
|
121
|
-
- Validate that a graph is fully connected
|
|
122
|
-
- This means that every node in the graph has a path to every other node in the graph
|
|
123
|
-
- Note: This assumes that the graph is symmetric
|
|
124
|
-
- Return True if the graph is fully connected and False if it is not
|
|
125
|
-
|
|
126
|
-
Required Arguments:
|
|
127
|
-
|
|
128
|
-
- `graph`:
|
|
129
|
-
- Type: list of dictionaries
|
|
130
|
-
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#Graph.validate_graph
|
|
131
|
-
|
|
132
|
-
Optional Arguments:
|
|
133
|
-
|
|
134
|
-
- `origin_id`
|
|
135
|
-
- Type: int
|
|
136
|
-
- What: The id of the origin node from which to start the connectivity check
|
|
137
|
-
- Default: 0
|
|
138
|
-
"""
|
|
139
|
-
visited = [0] * len(graph)
|
|
140
|
-
open_leaves = [origin_id]
|
|
141
|
-
|
|
142
|
-
while open_leaves:
|
|
143
|
-
current_id = open_leaves.pop()
|
|
144
|
-
visited[current_id] = 1
|
|
145
|
-
for connected_id, connected_distance in graph[current_id].items():
|
|
146
|
-
if visited[connected_id] == 0:
|
|
147
|
-
open_leaves.append(connected_id)
|
|
148
|
-
return min(visited) == 1
|
|
149
|
-
|
|
150
|
-
@staticmethod
|
|
151
|
-
def input_check(
|
|
152
|
-
graph: list[dict[int, int | float]], origin_id: int, destination_id: int
|
|
153
|
-
) -> None:
|
|
154
|
-
"""
|
|
155
|
-
Function:
|
|
156
|
-
|
|
157
|
-
- Check that the inputs passed to the shortest path algorithm are valid
|
|
158
|
-
- Raises an exception if the inputs passed are not valid
|
|
159
|
-
|
|
160
|
-
Required Arguments:
|
|
161
|
-
|
|
162
|
-
- `graph`:
|
|
163
|
-
- Type: list of dictionaries
|
|
164
|
-
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#Graph.validate_graph
|
|
165
|
-
- `origin_id`
|
|
166
|
-
- Type: int
|
|
167
|
-
- What: The id of the origin node from the graph dictionary to start the shortest path from
|
|
168
|
-
- `destination_id`
|
|
169
|
-
- Type: int
|
|
170
|
-
- What: The id of the destination node from the graph dictionary to end the shortest path at
|
|
171
|
-
|
|
172
|
-
Optional Arguments:
|
|
173
|
-
|
|
174
|
-
- None
|
|
175
|
-
"""
|
|
176
|
-
if (
|
|
177
|
-
not isinstance(origin_id, int)
|
|
178
|
-
and origin_id < len(graph)
|
|
179
|
-
and origin_id >= 0
|
|
180
|
-
):
|
|
181
|
-
raise Exception(f"Origin node ({origin_id}) is not in the graph")
|
|
182
|
-
if (
|
|
183
|
-
not isinstance(destination_id, int)
|
|
184
|
-
and origin_id < len(graph)
|
|
185
|
-
and origin_id >= 0
|
|
186
|
-
):
|
|
187
|
-
raise Exception(
|
|
188
|
-
f"Destination node ({destination_id}) is not in the graph"
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
@staticmethod
|
|
192
|
-
def dijkstra(
|
|
193
|
-
graph: list[dict[int, int | float]], origin_id: int, destination_id: int
|
|
194
|
-
) -> dict:
|
|
195
|
-
"""
|
|
196
|
-
Function:
|
|
197
|
-
|
|
198
|
-
- Identify the shortest path between two nodes in a sparse network graph using a modified dijkstra algorithm
|
|
199
|
-
- Modifications allow for a sparse distance matrix to be used instead of a dense distance matrix
|
|
200
|
-
- This can dramatically reduce the memory and compute requirements of the algorithm
|
|
201
|
-
- This algorithm runs in O(n^2) time where n is the number of nodes in the graph
|
|
202
|
-
- Return a dictionary of various path information including:
|
|
203
|
-
- `id_path`: A list of node ids in the order they are visited
|
|
204
|
-
- `path`: A list of node dictionaries (lat + long) in the order they are visited
|
|
205
|
-
|
|
206
|
-
Required Arguments:
|
|
207
|
-
|
|
208
|
-
- `graph`:
|
|
209
|
-
- Type: list of dictionaries
|
|
210
|
-
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#Graph.validate_graph
|
|
211
|
-
- `origin_id`
|
|
212
|
-
- Type: int
|
|
213
|
-
- What: The id of the origin node from the graph dictionary to start the shortest path from
|
|
214
|
-
- `destination_id`
|
|
215
|
-
- Type: int
|
|
216
|
-
- What: The id of the destination node from the graph dictionary to end the shortest path at
|
|
217
|
-
|
|
218
|
-
Optional Arguments:
|
|
219
|
-
|
|
220
|
-
- None
|
|
221
|
-
"""
|
|
222
|
-
Graph.input_check(
|
|
223
|
-
graph=graph, origin_id=origin_id, destination_id=destination_id
|
|
224
|
-
)
|
|
225
|
-
distance_matrix = [float("inf")] * len(graph)
|
|
226
|
-
branch_tip_distances = [float("inf")] * len(graph)
|
|
227
|
-
predecessor = [-1] * len(graph)
|
|
228
|
-
|
|
229
|
-
distance_matrix[origin_id] = 0
|
|
230
|
-
branch_tip_distances[origin_id] = 0
|
|
231
|
-
|
|
232
|
-
while True:
|
|
233
|
-
current_distance = min(branch_tip_distances)
|
|
234
|
-
if current_distance == float("inf"):
|
|
235
|
-
raise Exception(
|
|
236
|
-
"Something went wrong, the origin and destination nodes are not connected."
|
|
237
|
-
)
|
|
238
|
-
current_id = branch_tip_distances.index(current_distance)
|
|
239
|
-
branch_tip_distances[current_id] = float("inf")
|
|
240
|
-
if current_id == destination_id:
|
|
241
|
-
break
|
|
242
|
-
for connected_id, connected_distance in graph[current_id].items():
|
|
243
|
-
possible_distance = current_distance + connected_distance
|
|
244
|
-
if possible_distance < distance_matrix[connected_id]:
|
|
245
|
-
distance_matrix[connected_id] = possible_distance
|
|
246
|
-
predecessor[connected_id] = current_id
|
|
247
|
-
branch_tip_distances[connected_id] = possible_distance
|
|
248
|
-
|
|
249
|
-
output_path = [current_id]
|
|
250
|
-
while predecessor[current_id] != -1:
|
|
251
|
-
current_id = predecessor[current_id]
|
|
252
|
-
output_path.append(current_id)
|
|
253
|
-
|
|
254
|
-
output_path.reverse()
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
"path": output_path,
|
|
258
|
-
"length": distance_matrix[destination_id],
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
@staticmethod
|
|
262
|
-
def dijkstra_makowski(
|
|
263
|
-
graph: list[dict[int, int | float]], origin_id: int, destination_id: int
|
|
264
|
-
) -> dict:
|
|
265
|
-
"""
|
|
266
|
-
Function:
|
|
267
|
-
|
|
268
|
-
- Identify the shortest path between two nodes in a sparse network graph using Makowski's modified Dijkstra algorithm
|
|
269
|
-
- Modifications allow for a sparse distance matrix to be used instead of a dense distance matrix
|
|
270
|
-
- Improvements include only computing future potential nodes based on the open leaves for each branch
|
|
271
|
-
- Open leaves are nodes that have not been visited yet but are adjacent to other visited nodes
|
|
272
|
-
- This can dramatically reduce the memory and compute requirements of the algorithm
|
|
273
|
-
- For particularly sparse graphs, this algorithm runs close to O(n log n) time
|
|
274
|
-
- Where n is the number of nodes in the graph
|
|
275
|
-
- For dense graphs, this algorithm runs closer to O(n^2) time (similar to the standard Dijkstra algorithm)
|
|
276
|
-
- Where n is the number of nodes in the graph
|
|
277
|
-
- Return a dictionary of various path information including:
|
|
278
|
-
- `id_path`: A list of node ids in the order they are visited
|
|
279
|
-
- `path`: A list of node dictionaries (lat + long) in the order they are visited
|
|
280
|
-
|
|
281
|
-
Required Arguments:
|
|
282
|
-
|
|
283
|
-
- `graph`:
|
|
284
|
-
- Type: list of dictionaries
|
|
285
|
-
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#Graph.validate_graph
|
|
286
|
-
- `origin_id`
|
|
287
|
-
- Type: int
|
|
288
|
-
- What: The id of the origin node from the graph dictionary to start the shortest path from
|
|
289
|
-
- `destination_id`
|
|
290
|
-
- Type: int
|
|
291
|
-
- What: The id of the destination node from the graph dictionary to end the shortest path at
|
|
292
|
-
|
|
293
|
-
Optional Arguments:
|
|
294
|
-
|
|
295
|
-
- None
|
|
296
|
-
"""
|
|
297
|
-
# Input Validation
|
|
298
|
-
Graph.input_check(
|
|
299
|
-
graph=graph, origin_id=origin_id, destination_id=destination_id
|
|
300
|
-
)
|
|
301
|
-
# Variable Initialization
|
|
302
|
-
distance_matrix = [float("inf")] * len(graph)
|
|
303
|
-
distance_matrix[origin_id] = 0
|
|
304
|
-
open_leaves = []
|
|
305
|
-
heappush(open_leaves, (0, origin_id))
|
|
306
|
-
predecessor = [-1] * len(graph)
|
|
307
|
-
|
|
308
|
-
while open_leaves:
|
|
309
|
-
current_distance, current_id = heappop(open_leaves)
|
|
310
|
-
if current_id == destination_id:
|
|
311
|
-
break
|
|
312
|
-
for connected_id, connected_distance in graph[current_id].items():
|
|
313
|
-
possible_distance = current_distance + connected_distance
|
|
314
|
-
if possible_distance < distance_matrix[connected_id]:
|
|
315
|
-
distance_matrix[connected_id] = possible_distance
|
|
316
|
-
predecessor[connected_id] = current_id
|
|
317
|
-
heappush(open_leaves, (possible_distance, connected_id))
|
|
318
|
-
if current_id != destination_id:
|
|
319
|
-
raise Exception(
|
|
320
|
-
"Something went wrong, the origin and destination nodes are not connected."
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
output_path = [current_id]
|
|
324
|
-
while predecessor[current_id] != -1:
|
|
325
|
-
current_id = predecessor[current_id]
|
|
326
|
-
output_path.append(current_id)
|
|
327
|
-
|
|
328
|
-
output_path.reverse()
|
|
329
|
-
|
|
330
|
-
return {
|
|
331
|
-
"path": output_path,
|
|
332
|
-
"length": distance_matrix[destination_id],
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
@staticmethod
|
|
336
|
-
def a_star(
|
|
337
|
-
graph: list[dict[int, int | float]],
|
|
338
|
-
origin_id: int,
|
|
339
|
-
destination_id: int,
|
|
340
|
-
heuristic_fn=None,
|
|
341
|
-
) -> dict:
|
|
342
|
-
"""
|
|
343
|
-
Function:
|
|
344
|
-
|
|
345
|
-
- Identify the shortest path between two nodes in a sparse network graph using an A* extension of Makowski's modified Dijkstra algorithm
|
|
346
|
-
- Return a dictionary of various path information including:
|
|
347
|
-
- `id_path`: A list of node ids in the order they are visited
|
|
348
|
-
- `path`: A list of node dictionaries (lat + long) in the order they are visited
|
|
349
|
-
|
|
350
|
-
Required Arguments:
|
|
351
|
-
|
|
352
|
-
- `graph`:
|
|
353
|
-
- Type: list of dictionaries
|
|
354
|
-
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#Graph.validate_graph
|
|
355
|
-
- `origin_id`
|
|
356
|
-
- Type: int
|
|
357
|
-
- What: The id of the origin node from the graph dictionary to start the shortest path from
|
|
358
|
-
- `destination_id`
|
|
359
|
-
- Type: int
|
|
360
|
-
- What: The id of the destination node from the graph dictionary to end the shortest path at
|
|
361
|
-
- `heuristic_fn`
|
|
362
|
-
- Type: function
|
|
363
|
-
- What: A heuristic function that takes two node ids and returns an estimated distance between them
|
|
364
|
-
- Note: If None, returns the shortest path using Makowski's modified Dijkstra algorithm
|
|
365
|
-
- Default: None
|
|
366
|
-
|
|
367
|
-
Optional Arguments:
|
|
368
|
-
|
|
369
|
-
- None
|
|
370
|
-
"""
|
|
371
|
-
if heuristic_fn is None:
|
|
372
|
-
return Graph.dijkstra_makowski(
|
|
373
|
-
graph=graph,
|
|
374
|
-
origin_id=origin_id,
|
|
375
|
-
destination_id=destination_id,
|
|
376
|
-
)
|
|
377
|
-
# Input Validation
|
|
378
|
-
Graph.input_check(
|
|
379
|
-
graph=graph, origin_id=origin_id, destination_id=destination_id
|
|
380
|
-
)
|
|
381
|
-
# Variable Initialization
|
|
382
|
-
distance_matrix = [float("inf")] * len(graph)
|
|
383
|
-
distance_matrix[origin_id] = 0
|
|
384
|
-
open_leaves = []
|
|
385
|
-
heappush(open_leaves, (0, origin_id))
|
|
386
|
-
predecessor = [-1] * len(graph)
|
|
387
|
-
|
|
388
|
-
while open_leaves:
|
|
389
|
-
current_id = heappop(open_leaves)[1]
|
|
390
|
-
if current_id == destination_id:
|
|
391
|
-
break
|
|
392
|
-
current_distance = distance_matrix[current_id]
|
|
393
|
-
for connected_id, connected_distance in graph[current_id].items():
|
|
394
|
-
possible_distance = current_distance + connected_distance
|
|
395
|
-
if possible_distance < distance_matrix[connected_id]:
|
|
396
|
-
distance_matrix[connected_id] = possible_distance
|
|
397
|
-
predecessor[connected_id] = current_id
|
|
398
|
-
heappush(
|
|
399
|
-
open_leaves,
|
|
400
|
-
(
|
|
401
|
-
possible_distance
|
|
402
|
-
+ heuristic_fn(connected_id, destination_id),
|
|
403
|
-
connected_id,
|
|
404
|
-
),
|
|
405
|
-
)
|
|
406
|
-
if current_id != destination_id:
|
|
407
|
-
raise Exception(
|
|
408
|
-
"Something went wrong, the origin and destination nodes are not connected."
|
|
409
|
-
)
|
|
410
|
-
|
|
411
|
-
output_path = [current_id]
|
|
412
|
-
while predecessor[current_id] != -1:
|
|
413
|
-
current_id = predecessor[current_id]
|
|
414
|
-
output_path.append(current_id)
|
|
415
|
-
|
|
416
|
-
output_path.reverse()
|
|
417
|
-
|
|
418
|
-
return {
|
|
419
|
-
"path": output_path,
|
|
420
|
-
"length": distance_matrix[destination_id],
|
|
421
|
-
}
|
|
4
|
+
from scgraph.graph import Graph
|
|
422
5
|
|
|
423
6
|
|
|
424
7
|
class GeoGraph:
|
|
@@ -540,6 +123,24 @@ class GeoGraph:
|
|
|
540
123
|
origin_id: int,
|
|
541
124
|
destination_id: int,
|
|
542
125
|
):
|
|
126
|
+
"""
|
|
127
|
+
Function:
|
|
128
|
+
|
|
129
|
+
- Calculate the haversine distance between two points on the Earth.
|
|
130
|
+
|
|
131
|
+
Required Arguments:
|
|
132
|
+
|
|
133
|
+
- `origin_id`
|
|
134
|
+
- Type: int
|
|
135
|
+
- What: The id of the origin node in the graph
|
|
136
|
+
- `destination_id`
|
|
137
|
+
- Type: int
|
|
138
|
+
- What: The id of the destination node in the graph
|
|
139
|
+
|
|
140
|
+
Optional Arguments:
|
|
141
|
+
|
|
142
|
+
- None
|
|
143
|
+
"""
|
|
543
144
|
return haversine(
|
|
544
145
|
origin=self.nodes[origin_id],
|
|
545
146
|
destination=self.nodes[destination_id],
|
|
@@ -552,6 +153,26 @@ class GeoGraph:
|
|
|
552
153
|
origin_id: int,
|
|
553
154
|
destination_id: int,
|
|
554
155
|
):
|
|
156
|
+
"""
|
|
157
|
+
Function:
|
|
158
|
+
|
|
159
|
+
- Calculate the distance between two points on the Earth using the cheap ruler algorithm.
|
|
160
|
+
- This is based off of Mapbox's cheap ruler algorithm which is a fast approximation of the haversine distance
|
|
161
|
+
- It has modifications to support distances across the antimeridian
|
|
162
|
+
|
|
163
|
+
Required Arguments:
|
|
164
|
+
|
|
165
|
+
- `origin_id`
|
|
166
|
+
- Type: int
|
|
167
|
+
- What: The id of the origin node in the graph
|
|
168
|
+
- `destination_id`
|
|
169
|
+
- Type: int
|
|
170
|
+
- What: The id of the destination node in the graph
|
|
171
|
+
|
|
172
|
+
Optional Arguments:
|
|
173
|
+
|
|
174
|
+
- None
|
|
175
|
+
"""
|
|
555
176
|
return cheap_ruler(
|
|
556
177
|
origin=self.nodes[origin_id],
|
|
557
178
|
destination=self.nodes[destination_id],
|
|
@@ -719,7 +340,7 @@ class GeoGraph:
|
|
|
719
340
|
**algorithm_kwargs,
|
|
720
341
|
)
|
|
721
342
|
output["coordinate_path"] = self.get_coordinate_path(output["path"])
|
|
722
|
-
output["length"] = self.
|
|
343
|
+
output["length"] = self.adjust_circuity_length(
|
|
723
344
|
output=output,
|
|
724
345
|
node_addition_circuity=node_addition_circuity,
|
|
725
346
|
off_graph_circuity=off_graph_circuity,
|
|
@@ -749,7 +370,7 @@ class GeoGraph:
|
|
|
749
370
|
self.remove_appended_node()
|
|
750
371
|
raise e
|
|
751
372
|
|
|
752
|
-
def
|
|
373
|
+
def adjust_circuity_length(
|
|
753
374
|
self,
|
|
754
375
|
output: dict,
|
|
755
376
|
node_addition_circuity: [float | int],
|
|
@@ -789,6 +410,15 @@ class GeoGraph:
|
|
|
789
410
|
4,
|
|
790
411
|
)
|
|
791
412
|
|
|
413
|
+
def adujust_circuity_length(self, *args, **kwargs):
|
|
414
|
+
"""
|
|
415
|
+
Maintain backwards compatibility with the old method that had a typo.
|
|
416
|
+
|
|
417
|
+
This is now just an alias for `adjust_circuity_length`.
|
|
418
|
+
"""
|
|
419
|
+
# TODO: Remove in next major release
|
|
420
|
+
return self.adjust_circuity_length(*args, **kwargs)
|
|
421
|
+
|
|
792
422
|
def get_coordinate_path(self, path: list[int]) -> list[dict[float | int]]:
|
|
793
423
|
"""
|
|
794
424
|
Function:
|