scgraph 2.1.2__tar.gz → 2.3.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.1.2/scgraph.egg-info → scgraph-2.3.0}/PKG-INFO +91 -34
- {scgraph-2.1.2 → scgraph-2.3.0}/README.md +89 -32
- {scgraph-2.1.2 → scgraph-2.3.0}/pyproject.toml +2 -2
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph/core.py +495 -43
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph/utils.py +7 -7
- {scgraph-2.1.2 → scgraph-2.3.0/scgraph.egg-info}/PKG-INFO +91 -34
- {scgraph-2.1.2 → scgraph-2.3.0}/setup.cfg +1 -1
- {scgraph-2.1.2 → scgraph-2.3.0}/LICENSE +0 -0
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph/__init__.py +0 -0
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph/geographs/__init__.py +0 -0
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph/geographs/marnet.py +0 -0
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph/geographs/north_america_rail.py +0 -0
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph/geographs/oak_ridge_maritime.py +0 -0
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph/geographs/us_freeway.py +0 -0
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph.egg-info/SOURCES.txt +0 -0
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph.egg-info/dependency_links.txt +0 -0
- {scgraph-2.1.2 → scgraph-2.3.0}/scgraph.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scgraph
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -9,7 +9,7 @@ Project-URL: Documentation, https://connor-makowski.github.io/scgraph/core.html
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
|
|
@@ -57,7 +57,9 @@ Low Level: https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
|
57
57
|
|
|
58
58
|
## Setup
|
|
59
59
|
|
|
60
|
-
Make sure you have Python 3.
|
|
60
|
+
Make sure you have Python 3.10.x (or higher) installed on your system. You can download it [here](https://www.python.org/downloads/).
|
|
61
|
+
|
|
62
|
+
Support for python3.6-3.9 is available up to version 2.2.0
|
|
61
63
|
|
|
62
64
|
## Installation
|
|
63
65
|
|
|
@@ -67,7 +69,10 @@ pip install scgraph
|
|
|
67
69
|
|
|
68
70
|
## Use with Google Colab
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
- [Getting Started](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/getting_started.ipynb)
|
|
73
|
+
- [Creating A Multi Path Geojson](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/multi_path_geojson.ipynb)
|
|
74
|
+
- [Modifying A Geograph](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/geograph_modifications.ipynb)
|
|
75
|
+
|
|
71
76
|
|
|
72
77
|
# Getting Started
|
|
73
78
|
|
|
@@ -101,7 +106,7 @@ In the above example, the `output` variable is a dictionary with three keys: `le
|
|
|
101
106
|
- `ft` (feet)
|
|
102
107
|
- `coordinate_path`: A list of lists [`latitude`,`longitude`] that make up the shortest path
|
|
103
108
|
|
|
104
|
-
For more examples including viewing the output on a map, see the [example notebook](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/
|
|
109
|
+
For more examples including viewing the output on a map, see the [example notebook](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/getting_started.ipynb).
|
|
105
110
|
|
|
106
111
|
## Included GeoGraphs
|
|
107
112
|
|
|
@@ -165,15 +170,17 @@ output = marnet_geograph.get_shortest_path(
|
|
|
165
170
|
get_line_path(output, filename='output.geojson')
|
|
166
171
|
```
|
|
167
172
|
|
|
173
|
+
Modify an existing geograph: See the notebook [here](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/geograph_modifications.ipynb)
|
|
174
|
+
|
|
168
175
|
|
|
169
176
|
You can specify your own custom graphs for direct access to the solving algorithms. This requires the use of the low level `Graph` class
|
|
170
177
|
|
|
171
178
|
```py
|
|
172
179
|
from scgraph import Graph
|
|
173
180
|
|
|
174
|
-
# Define
|
|
181
|
+
# Define an arbitrary graph
|
|
175
182
|
# See the graph definitions here:
|
|
176
|
-
# https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
183
|
+
# https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
177
184
|
graph = [
|
|
178
185
|
{1: 5, 2: 1},
|
|
179
186
|
{0: 5, 2: 2, 3: 1},
|
|
@@ -198,25 +205,67 @@ from scgraph import GeoGraph
|
|
|
198
205
|
|
|
199
206
|
# Define nodes
|
|
200
207
|
# See the nodes definitions here:
|
|
201
|
-
# https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
208
|
+
# https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
202
209
|
nodes = [
|
|
203
|
-
|
|
204
|
-
[0
|
|
205
|
-
|
|
206
|
-
[
|
|
207
|
-
|
|
208
|
-
[
|
|
210
|
+
# London
|
|
211
|
+
[51.5074, -0.1278],
|
|
212
|
+
# Paris
|
|
213
|
+
[48.8566, 2.3522],
|
|
214
|
+
# Berlin
|
|
215
|
+
[52.5200, 13.4050],
|
|
216
|
+
# Rome
|
|
217
|
+
[41.9028, 12.4964],
|
|
218
|
+
# Madrid
|
|
219
|
+
[40.4168, -3.7038],
|
|
220
|
+
# Lisbon
|
|
221
|
+
[38.7223, -9.1393]
|
|
209
222
|
]
|
|
210
223
|
# Define a graph
|
|
211
224
|
# See the graph definitions here:
|
|
212
|
-
# https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
225
|
+
# https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
213
226
|
graph = [
|
|
214
|
-
|
|
215
|
-
{
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
227
|
+
# From London
|
|
228
|
+
{
|
|
229
|
+
# To Paris
|
|
230
|
+
1: 311,
|
|
231
|
+
},
|
|
232
|
+
# From Paris
|
|
233
|
+
{
|
|
234
|
+
# To London
|
|
235
|
+
0: 311,
|
|
236
|
+
# To Berlin
|
|
237
|
+
2: 878,
|
|
238
|
+
# To Rome
|
|
239
|
+
3: 1439,
|
|
240
|
+
# To Madrid
|
|
241
|
+
4: 1053
|
|
242
|
+
},
|
|
243
|
+
# From Berlin
|
|
244
|
+
{
|
|
245
|
+
# To Paris
|
|
246
|
+
1: 878,
|
|
247
|
+
# To Rome
|
|
248
|
+
3: 1181,
|
|
249
|
+
},
|
|
250
|
+
# From Rome
|
|
251
|
+
{
|
|
252
|
+
# To Paris
|
|
253
|
+
1: 1439,
|
|
254
|
+
# To Berlin
|
|
255
|
+
2: 1181,
|
|
256
|
+
},
|
|
257
|
+
# From Madrid
|
|
258
|
+
{
|
|
259
|
+
# To Paris
|
|
260
|
+
1: 1053,
|
|
261
|
+
# To Lisbon
|
|
262
|
+
5: 623
|
|
263
|
+
},
|
|
264
|
+
# From Lisbon
|
|
265
|
+
{
|
|
266
|
+
# To Madrid
|
|
267
|
+
4: 623
|
|
268
|
+
}
|
|
220
269
|
]
|
|
221
270
|
|
|
222
271
|
# Create a GeoGraph object
|
|
@@ -229,23 +278,31 @@ my_geograph.validate_graph()
|
|
|
229
278
|
my_geograph.validate_nodes()
|
|
230
279
|
|
|
231
280
|
# Get the shortest path between two points
|
|
281
|
+
# In this case, Birmingham England and Zaragoza Spain
|
|
282
|
+
# Since Birmingham and Zaragoza are not in the graph,
|
|
283
|
+
# the algorithm will add them into the graph.
|
|
284
|
+
# See: https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph.get_shortest_path
|
|
285
|
+
# Expected output would be to go from
|
|
286
|
+
# Birmingham -> London -> Paris -> Madrid -> Zaragoza
|
|
287
|
+
|
|
232
288
|
output = my_geograph.get_shortest_path(
|
|
233
|
-
|
|
234
|
-
|
|
289
|
+
# Birmingham England
|
|
290
|
+
origin_node = {'latitude': 52.4862, 'longitude': -1.8904},
|
|
291
|
+
# Zaragoza Spain
|
|
292
|
+
destination_node = {'latitude': 41.6488, 'longitude': -0.8891}
|
|
235
293
|
)
|
|
236
|
-
|
|
294
|
+
print(output)
|
|
237
295
|
# {
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
# [
|
|
241
|
-
# [
|
|
242
|
-
# [
|
|
243
|
-
# [
|
|
244
|
-
# [
|
|
245
|
-
#
|
|
246
|
-
# ],
|
|
247
|
-
# "length": 10
|
|
296
|
+
# 'length': 1799.4323,
|
|
297
|
+
# 'coordinate_path': [
|
|
298
|
+
# [52.4862, -1.8904],
|
|
299
|
+
# [51.5074, -0.1278],
|
|
300
|
+
# [48.8566, 2.3522],
|
|
301
|
+
# [40.4168, -3.7038],
|
|
302
|
+
# [41.6488, -0.8891]
|
|
303
|
+
# ]
|
|
248
304
|
# }
|
|
305
|
+
|
|
249
306
|
```
|
|
250
307
|
|
|
251
308
|
## Attributions and Thanks
|
|
@@ -42,7 +42,9 @@ Low Level: https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
|
42
42
|
|
|
43
43
|
## Setup
|
|
44
44
|
|
|
45
|
-
Make sure you have Python 3.
|
|
45
|
+
Make sure you have Python 3.10.x (or higher) installed on your system. You can download it [here](https://www.python.org/downloads/).
|
|
46
|
+
|
|
47
|
+
Support for python3.6-3.9 is available up to version 2.2.0
|
|
46
48
|
|
|
47
49
|
## Installation
|
|
48
50
|
|
|
@@ -52,7 +54,10 @@ pip install scgraph
|
|
|
52
54
|
|
|
53
55
|
## Use with Google Colab
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
- [Getting Started](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/getting_started.ipynb)
|
|
58
|
+
- [Creating A Multi Path Geojson](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/multi_path_geojson.ipynb)
|
|
59
|
+
- [Modifying A Geograph](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/geograph_modifications.ipynb)
|
|
60
|
+
|
|
56
61
|
|
|
57
62
|
# Getting Started
|
|
58
63
|
|
|
@@ -86,7 +91,7 @@ In the above example, the `output` variable is a dictionary with three keys: `le
|
|
|
86
91
|
- `ft` (feet)
|
|
87
92
|
- `coordinate_path`: A list of lists [`latitude`,`longitude`] that make up the shortest path
|
|
88
93
|
|
|
89
|
-
For more examples including viewing the output on a map, see the [example notebook](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/
|
|
94
|
+
For more examples including viewing the output on a map, see the [example notebook](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/getting_started.ipynb).
|
|
90
95
|
|
|
91
96
|
## Included GeoGraphs
|
|
92
97
|
|
|
@@ -150,15 +155,17 @@ output = marnet_geograph.get_shortest_path(
|
|
|
150
155
|
get_line_path(output, filename='output.geojson')
|
|
151
156
|
```
|
|
152
157
|
|
|
158
|
+
Modify an existing geograph: See the notebook [here](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/geograph_modifications.ipynb)
|
|
159
|
+
|
|
153
160
|
|
|
154
161
|
You can specify your own custom graphs for direct access to the solving algorithms. This requires the use of the low level `Graph` class
|
|
155
162
|
|
|
156
163
|
```py
|
|
157
164
|
from scgraph import Graph
|
|
158
165
|
|
|
159
|
-
# Define
|
|
166
|
+
# Define an arbitrary graph
|
|
160
167
|
# See the graph definitions here:
|
|
161
|
-
# https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
168
|
+
# https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
162
169
|
graph = [
|
|
163
170
|
{1: 5, 2: 1},
|
|
164
171
|
{0: 5, 2: 2, 3: 1},
|
|
@@ -183,25 +190,67 @@ from scgraph import GeoGraph
|
|
|
183
190
|
|
|
184
191
|
# Define nodes
|
|
185
192
|
# See the nodes definitions here:
|
|
186
|
-
# https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
193
|
+
# https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
187
194
|
nodes = [
|
|
188
|
-
|
|
189
|
-
[0
|
|
190
|
-
|
|
191
|
-
[
|
|
192
|
-
|
|
193
|
-
[
|
|
195
|
+
# London
|
|
196
|
+
[51.5074, -0.1278],
|
|
197
|
+
# Paris
|
|
198
|
+
[48.8566, 2.3522],
|
|
199
|
+
# Berlin
|
|
200
|
+
[52.5200, 13.4050],
|
|
201
|
+
# Rome
|
|
202
|
+
[41.9028, 12.4964],
|
|
203
|
+
# Madrid
|
|
204
|
+
[40.4168, -3.7038],
|
|
205
|
+
# Lisbon
|
|
206
|
+
[38.7223, -9.1393]
|
|
194
207
|
]
|
|
195
208
|
# Define a graph
|
|
196
209
|
# See the graph definitions here:
|
|
197
|
-
# https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
210
|
+
# https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
198
211
|
graph = [
|
|
199
|
-
|
|
200
|
-
{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
212
|
+
# From London
|
|
213
|
+
{
|
|
214
|
+
# To Paris
|
|
215
|
+
1: 311,
|
|
216
|
+
},
|
|
217
|
+
# From Paris
|
|
218
|
+
{
|
|
219
|
+
# To London
|
|
220
|
+
0: 311,
|
|
221
|
+
# To Berlin
|
|
222
|
+
2: 878,
|
|
223
|
+
# To Rome
|
|
224
|
+
3: 1439,
|
|
225
|
+
# To Madrid
|
|
226
|
+
4: 1053
|
|
227
|
+
},
|
|
228
|
+
# From Berlin
|
|
229
|
+
{
|
|
230
|
+
# To Paris
|
|
231
|
+
1: 878,
|
|
232
|
+
# To Rome
|
|
233
|
+
3: 1181,
|
|
234
|
+
},
|
|
235
|
+
# From Rome
|
|
236
|
+
{
|
|
237
|
+
# To Paris
|
|
238
|
+
1: 1439,
|
|
239
|
+
# To Berlin
|
|
240
|
+
2: 1181,
|
|
241
|
+
},
|
|
242
|
+
# From Madrid
|
|
243
|
+
{
|
|
244
|
+
# To Paris
|
|
245
|
+
1: 1053,
|
|
246
|
+
# To Lisbon
|
|
247
|
+
5: 623
|
|
248
|
+
},
|
|
249
|
+
# From Lisbon
|
|
250
|
+
{
|
|
251
|
+
# To Madrid
|
|
252
|
+
4: 623
|
|
253
|
+
}
|
|
205
254
|
]
|
|
206
255
|
|
|
207
256
|
# Create a GeoGraph object
|
|
@@ -214,23 +263,31 @@ my_geograph.validate_graph()
|
|
|
214
263
|
my_geograph.validate_nodes()
|
|
215
264
|
|
|
216
265
|
# Get the shortest path between two points
|
|
266
|
+
# In this case, Birmingham England and Zaragoza Spain
|
|
267
|
+
# Since Birmingham and Zaragoza are not in the graph,
|
|
268
|
+
# the algorithm will add them into the graph.
|
|
269
|
+
# See: https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph.get_shortest_path
|
|
270
|
+
# Expected output would be to go from
|
|
271
|
+
# Birmingham -> London -> Paris -> Madrid -> Zaragoza
|
|
272
|
+
|
|
217
273
|
output = my_geograph.get_shortest_path(
|
|
218
|
-
|
|
219
|
-
|
|
274
|
+
# Birmingham England
|
|
275
|
+
origin_node = {'latitude': 52.4862, 'longitude': -1.8904},
|
|
276
|
+
# Zaragoza Spain
|
|
277
|
+
destination_node = {'latitude': 41.6488, 'longitude': -0.8891}
|
|
220
278
|
)
|
|
221
|
-
|
|
279
|
+
print(output)
|
|
222
280
|
# {
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
# [
|
|
226
|
-
# [
|
|
227
|
-
# [
|
|
228
|
-
# [
|
|
229
|
-
# [
|
|
230
|
-
#
|
|
231
|
-
# ],
|
|
232
|
-
# "length": 10
|
|
281
|
+
# 'length': 1799.4323,
|
|
282
|
+
# 'coordinate_path': [
|
|
283
|
+
# [52.4862, -1.8904],
|
|
284
|
+
# [51.5074, -0.1278],
|
|
285
|
+
# [48.8566, 2.3522],
|
|
286
|
+
# [40.4168, -3.7038],
|
|
287
|
+
# [41.6488, -0.8891]
|
|
288
|
+
# ]
|
|
233
289
|
# }
|
|
290
|
+
|
|
234
291
|
```
|
|
235
292
|
|
|
236
293
|
## Attributions and Thanks
|
|
@@ -12,13 +12,13 @@ build-backend = "setuptools.build_meta"
|
|
|
12
12
|
|
|
13
13
|
[project]
|
|
14
14
|
name = "scgraph"
|
|
15
|
-
version = "2.
|
|
15
|
+
version = "2.3.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"}
|
|
19
19
|
]
|
|
20
20
|
readme = "README.md"
|
|
21
|
-
requires-python = ">=3.
|
|
21
|
+
requires-python = ">=3.10"
|
|
22
22
|
classifiers = [
|
|
23
23
|
"Programming Language :: Python :: 3",
|
|
24
24
|
'License :: OSI Approved :: MIT License',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .utils import haversine, hard_round, distance_converter
|
|
1
|
+
from .utils import haversine, hard_round, distance_converter, get_line_path
|
|
2
2
|
import json
|
|
3
3
|
|
|
4
4
|
|
|
@@ -16,11 +16,10 @@ class Graph:
|
|
|
16
16
|
|
|
17
17
|
Required Arguments:
|
|
18
18
|
|
|
19
|
-
- `graph
|
|
19
|
+
- `graph`:
|
|
20
20
|
- Type: list of dictionaries
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
22
|
+
|
|
24
23
|
Optional Arguments:
|
|
25
24
|
|
|
26
25
|
- `check_symmetry`
|
|
@@ -75,10 +74,9 @@ class Graph:
|
|
|
75
74
|
|
|
76
75
|
Required Arguments:
|
|
77
76
|
|
|
78
|
-
- `graph
|
|
77
|
+
- `graph`:
|
|
79
78
|
- Type: list of dictionaries
|
|
80
|
-
-
|
|
81
|
-
- Note: All nodes must be included as origins in the graph regardless of if they have any connected destinations
|
|
79
|
+
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
82
80
|
|
|
83
81
|
Optional Arguments:
|
|
84
82
|
|
|
@@ -121,9 +119,9 @@ class Graph:
|
|
|
121
119
|
|
|
122
120
|
Required Arguments:
|
|
123
121
|
|
|
124
|
-
- `graph
|
|
125
|
-
- Type: list
|
|
126
|
-
-
|
|
122
|
+
- `graph`:
|
|
123
|
+
- Type: list of dictionaries
|
|
124
|
+
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
127
125
|
- `origin_id`
|
|
128
126
|
- Type: int
|
|
129
127
|
- What: The id of the origin node from the graph dictionary to start the shortest path from
|
|
@@ -167,9 +165,9 @@ class Graph:
|
|
|
167
165
|
|
|
168
166
|
Required Arguments:
|
|
169
167
|
|
|
170
|
-
- `graph
|
|
171
|
-
- Type: list
|
|
172
|
-
-
|
|
168
|
+
- `graph`:
|
|
169
|
+
- Type: list of dictionaries
|
|
170
|
+
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
173
171
|
- `origin_id`
|
|
174
172
|
- Type: int
|
|
175
173
|
- What: The id of the origin node from the graph dictionary to start the shortest path from
|
|
@@ -242,9 +240,9 @@ class Graph:
|
|
|
242
240
|
|
|
243
241
|
Required Arguments:
|
|
244
242
|
|
|
245
|
-
- `graph
|
|
246
|
-
- Type: list
|
|
247
|
-
-
|
|
243
|
+
- `graph`:
|
|
244
|
+
- Type: list of dictionaries
|
|
245
|
+
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
248
246
|
- `origin_id`
|
|
249
247
|
- Type: int
|
|
250
248
|
- What: The id of the origin node from the graph dictionary to start the shortest path from
|
|
@@ -298,7 +296,7 @@ class Graph:
|
|
|
298
296
|
|
|
299
297
|
class GeoGraph:
|
|
300
298
|
def __init__(
|
|
301
|
-
self, graph: list[dict], nodes: list[list[int
|
|
299
|
+
self, graph: list[dict], nodes: list[list[float|int]]
|
|
302
300
|
) -> None:
|
|
303
301
|
"""
|
|
304
302
|
Function:
|
|
@@ -311,13 +309,74 @@ class GeoGraph:
|
|
|
311
309
|
- Type: list of dictionaries
|
|
312
310
|
- What: A list of dictionaries where the indicies are origin node ids and the values are dictionaries of destination node indices and distances
|
|
313
311
|
- Note: All nodes must be included as origins in the graph regardless of if they have any connected destinations
|
|
312
|
+
- EG:
|
|
313
|
+
```
|
|
314
|
+
[
|
|
315
|
+
# From London
|
|
316
|
+
{
|
|
317
|
+
# To Paris
|
|
318
|
+
1: 311,
|
|
319
|
+
},
|
|
320
|
+
# From Paris
|
|
321
|
+
{
|
|
322
|
+
# To London
|
|
323
|
+
0: 311,
|
|
324
|
+
# To Berlin
|
|
325
|
+
2: 878,
|
|
326
|
+
# To Rome
|
|
327
|
+
3: 1439,
|
|
328
|
+
# To Madrid
|
|
329
|
+
4: 1053
|
|
330
|
+
},
|
|
331
|
+
# From Berlin
|
|
332
|
+
{
|
|
333
|
+
# To Paris
|
|
334
|
+
1: 878,
|
|
335
|
+
# To Rome
|
|
336
|
+
3: 1181,
|
|
337
|
+
},
|
|
338
|
+
# From Rome
|
|
339
|
+
{
|
|
340
|
+
# To Paris
|
|
341
|
+
1: 1439,
|
|
342
|
+
# To Berlin
|
|
343
|
+
2: 1181,
|
|
344
|
+
},
|
|
345
|
+
# From Madrid
|
|
346
|
+
{
|
|
347
|
+
# To Paris
|
|
348
|
+
1: 1053,
|
|
349
|
+
# To Lisbon
|
|
350
|
+
5: 623
|
|
351
|
+
},
|
|
352
|
+
# From Lisbon
|
|
353
|
+
{
|
|
354
|
+
# To Madrid
|
|
355
|
+
4: 623
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
```
|
|
314
359
|
- `nodes`
|
|
315
|
-
- Type: list of lists
|
|
360
|
+
- Type: list of lists of ints or floats
|
|
316
361
|
- What: A list of lists where the values are coordinates (latitude then longitude)
|
|
317
362
|
- Note: The length of the nodes list must be the same as that of the graph list
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
363
|
+
- EG:
|
|
364
|
+
```
|
|
365
|
+
[
|
|
366
|
+
# London
|
|
367
|
+
[51.5074, 0.1278],
|
|
368
|
+
# Paris
|
|
369
|
+
[48.8566, 2.3522],
|
|
370
|
+
# Berlin
|
|
371
|
+
[52.5200, 13.4050],
|
|
372
|
+
# Rome
|
|
373
|
+
[41.9028, 12.4964],
|
|
374
|
+
# Madrid
|
|
375
|
+
[40.4168, 3.7038],
|
|
376
|
+
# Lisbon
|
|
377
|
+
[38.7223, 9.1393]
|
|
378
|
+
]
|
|
379
|
+
```
|
|
321
380
|
"""
|
|
322
381
|
self.graph = graph
|
|
323
382
|
self.nodes = nodes
|
|
@@ -397,17 +456,17 @@ class GeoGraph:
|
|
|
397
456
|
|
|
398
457
|
def get_shortest_path(
|
|
399
458
|
self,
|
|
400
|
-
origin_node: dict[int
|
|
401
|
-
destination_node: dict[int
|
|
459
|
+
origin_node: dict[float|int],
|
|
460
|
+
destination_node: dict[float|int],
|
|
402
461
|
output_units: str = "km",
|
|
403
462
|
algorithm_fn=Graph.dijkstra_makowski,
|
|
404
|
-
off_graph_circuity: [int
|
|
463
|
+
off_graph_circuity: [float|int] = 1,
|
|
405
464
|
node_addition_type: str = "quadrant",
|
|
406
|
-
node_addition_circuity: [int
|
|
465
|
+
node_addition_circuity: [float|int] = 4,
|
|
407
466
|
geograph_units: str = "km",
|
|
408
467
|
output_coordinate_path: str = "list_of_lists",
|
|
409
468
|
output_path: bool = False,
|
|
410
|
-
node_addition_lat_lon_bound: [int
|
|
469
|
+
node_addition_lat_lon_bound: [float|int] = 5,
|
|
411
470
|
node_addition_math: str = "euclidean",
|
|
412
471
|
**kwargs,
|
|
413
472
|
) -> dict:
|
|
@@ -450,6 +509,7 @@ class GeoGraph:
|
|
|
450
509
|
- 'Graph.dijkstra_makowski': A modified dijkstra algorithm that uses a sparse distance matrix to identify the shortest path
|
|
451
510
|
- Any user defined algorithm that takes the arguments:
|
|
452
511
|
- `graph`: A dictionary of dictionaries where the keys are origin node ids and the values are dictionaries of destination node ids and distances
|
|
512
|
+
- See: https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
453
513
|
- `origin`: The id of the origin node from the graph dictionary to start the shortest path from
|
|
454
514
|
- `destination`: The id of the destination node from the graph dictionary to end the shortest path at
|
|
455
515
|
- `off_graph_circuity`
|
|
@@ -582,9 +642,9 @@ class GeoGraph:
|
|
|
582
642
|
def adujust_circuity_length(
|
|
583
643
|
self,
|
|
584
644
|
output: dict,
|
|
585
|
-
node_addition_circuity: [float
|
|
586
|
-
off_graph_circuity: [float
|
|
587
|
-
) -> [float
|
|
645
|
+
node_addition_circuity: [float|int],
|
|
646
|
+
off_graph_circuity: [float|int],
|
|
647
|
+
) -> [float|int]:
|
|
588
648
|
"""
|
|
589
649
|
Function:
|
|
590
650
|
|
|
@@ -619,7 +679,7 @@ class GeoGraph:
|
|
|
619
679
|
4,
|
|
620
680
|
)
|
|
621
681
|
|
|
622
|
-
def get_coordinate_path(self, path: list[int]) -> list[dict[int
|
|
682
|
+
def get_coordinate_path(self, path: list[int]) -> list[dict[float|int]]:
|
|
623
683
|
"""
|
|
624
684
|
Function:
|
|
625
685
|
|
|
@@ -663,11 +723,11 @@ class GeoGraph:
|
|
|
663
723
|
def get_node_distances(
|
|
664
724
|
self,
|
|
665
725
|
node: list,
|
|
666
|
-
circuity: [int
|
|
726
|
+
circuity: [float|int],
|
|
667
727
|
node_addition_type: str,
|
|
668
728
|
node_addition_math: str,
|
|
669
|
-
lat_lon_bound: [int
|
|
670
|
-
) -> dict[int
|
|
729
|
+
lat_lon_bound: [float|int],
|
|
730
|
+
) -> dict[float|int]:
|
|
671
731
|
"""
|
|
672
732
|
Function:
|
|
673
733
|
|
|
@@ -767,11 +827,11 @@ class GeoGraph:
|
|
|
767
827
|
|
|
768
828
|
def add_node(
|
|
769
829
|
self,
|
|
770
|
-
node: dict[int
|
|
771
|
-
circuity: [int
|
|
830
|
+
node: dict[float|int],
|
|
831
|
+
circuity: [float|int],
|
|
772
832
|
node_addition_type: str = "quadrant",
|
|
773
833
|
node_addition_math: str = "euclidean",
|
|
774
|
-
lat_lon_bound: [int
|
|
834
|
+
lat_lon_bound: [float|int] = 5,
|
|
775
835
|
) -> int:
|
|
776
836
|
"""
|
|
777
837
|
Function:
|
|
@@ -877,13 +937,13 @@ class GeoGraph:
|
|
|
877
937
|
|
|
878
938
|
"""
|
|
879
939
|
features = []
|
|
880
|
-
for origin_idx, destinations in self.graph
|
|
940
|
+
for origin_idx, destinations in enumerate(self.graph):
|
|
881
941
|
for destination_idx, distance in destinations.items():
|
|
882
942
|
# Create an undirected graph for geojson purposes
|
|
883
943
|
if origin_idx > destination_idx:
|
|
884
944
|
continue
|
|
885
|
-
origin = self.nodes
|
|
886
|
-
destination = self.nodes
|
|
945
|
+
origin = self.nodes[origin_idx]
|
|
946
|
+
destination = self.nodes[destination_idx]
|
|
887
947
|
features.append(
|
|
888
948
|
{
|
|
889
949
|
"type": "Feature",
|
|
@@ -895,10 +955,10 @@ class GeoGraph:
|
|
|
895
955
|
"geometry": {
|
|
896
956
|
"type": "LineString",
|
|
897
957
|
"coordinates": [
|
|
898
|
-
[origin[
|
|
958
|
+
[origin[1], origin[0]],
|
|
899
959
|
[
|
|
900
|
-
destination[
|
|
901
|
-
destination[
|
|
960
|
+
destination[1],
|
|
961
|
+
destination[0],
|
|
902
962
|
],
|
|
903
963
|
],
|
|
904
964
|
},
|
|
@@ -908,3 +968,395 @@ class GeoGraph:
|
|
|
908
968
|
out_dict = {"type": "FeatureCollection", "features": features}
|
|
909
969
|
with open(filename, "w") as f:
|
|
910
970
|
json.dump(out_dict, f)
|
|
971
|
+
|
|
972
|
+
def save_as_geograph(self, name: str) -> None:
|
|
973
|
+
"""
|
|
974
|
+
Function:
|
|
975
|
+
|
|
976
|
+
- Save the current geograph as an importable python file
|
|
977
|
+
|
|
978
|
+
Required Arguments:
|
|
979
|
+
|
|
980
|
+
- `name`
|
|
981
|
+
- Type: str
|
|
982
|
+
- What: The name of the geograph and file
|
|
983
|
+
- EG: 'custom'
|
|
984
|
+
- Stored as: 'custom.py'
|
|
985
|
+
- In your current directory
|
|
986
|
+
- Import as: 'from .custom import custom_geograph'
|
|
987
|
+
"""
|
|
988
|
+
self.validate_nodes()
|
|
989
|
+
self.validate_graph(check_symmetry=True, check_connected=False)
|
|
990
|
+
out_string = f"""from scgraph.core import GeoGraph\ngraph={str(self.graph)}\nnodes={str(self.nodes)}\n{name}_geograph = GeoGraph(graph=graph, nodes=nodes)"""
|
|
991
|
+
with open(name + ".py", "w") as f:
|
|
992
|
+
f.write(out_string)
|
|
993
|
+
|
|
994
|
+
def mod_remove_arc(
|
|
995
|
+
self, origin_idx: int, destination_idx: int, undirected: bool = True
|
|
996
|
+
) -> None:
|
|
997
|
+
"""
|
|
998
|
+
Function:
|
|
999
|
+
|
|
1000
|
+
- Remove an arc from the graph
|
|
1001
|
+
|
|
1002
|
+
Required Arguments:
|
|
1003
|
+
|
|
1004
|
+
- `origin_idx`
|
|
1005
|
+
- Type: int
|
|
1006
|
+
- What: The index of the origin node
|
|
1007
|
+
- `destination_idx`
|
|
1008
|
+
- Type: int
|
|
1009
|
+
- What: The index of the destination node
|
|
1010
|
+
|
|
1011
|
+
Optional Arguments:
|
|
1012
|
+
|
|
1013
|
+
- `undirected`
|
|
1014
|
+
- Type: bool
|
|
1015
|
+
- What: Whether to remove the arc in both directions
|
|
1016
|
+
- Default: True
|
|
1017
|
+
"""
|
|
1018
|
+
assert origin_idx < len(self.graph), "Origin node does not exist"
|
|
1019
|
+
assert destination_idx < len(
|
|
1020
|
+
self.graph
|
|
1021
|
+
), "Destination node does not exist"
|
|
1022
|
+
assert destination_idx in self.graph[origin_idx], "Arc does not exist"
|
|
1023
|
+
del self.graph[origin_idx][destination_idx]
|
|
1024
|
+
if undirected:
|
|
1025
|
+
if origin_idx in self.graph[destination_idx]:
|
|
1026
|
+
del self.graph[destination_idx][origin_idx]
|
|
1027
|
+
|
|
1028
|
+
def mod_add_node(
|
|
1029
|
+
self, latitude: [float|int], longitude: [float|int]
|
|
1030
|
+
) -> int:
|
|
1031
|
+
"""
|
|
1032
|
+
Function:
|
|
1033
|
+
|
|
1034
|
+
- Add a node to the graph
|
|
1035
|
+
|
|
1036
|
+
Required Arguments:
|
|
1037
|
+
|
|
1038
|
+
- `latitude`
|
|
1039
|
+
- Type: int | float
|
|
1040
|
+
- What: The latitude of the node
|
|
1041
|
+
- `longitude`
|
|
1042
|
+
- Type: int | float
|
|
1043
|
+
- What: The longitude of the node
|
|
1044
|
+
|
|
1045
|
+
Returns:
|
|
1046
|
+
|
|
1047
|
+
- The index of the new node
|
|
1048
|
+
"""
|
|
1049
|
+
self.nodes.append([latitude, longitude])
|
|
1050
|
+
self.graph.append({})
|
|
1051
|
+
return len(self.graph) - 1
|
|
1052
|
+
|
|
1053
|
+
def mod_add_arc(
|
|
1054
|
+
self,
|
|
1055
|
+
origin_idx: int,
|
|
1056
|
+
destination_idx: int,
|
|
1057
|
+
distance: [float|int] = 0,
|
|
1058
|
+
use_haversine_distance=True,
|
|
1059
|
+
undirected: bool = True,
|
|
1060
|
+
) -> None:
|
|
1061
|
+
"""
|
|
1062
|
+
Function:
|
|
1063
|
+
|
|
1064
|
+
- Add an arc to the graph
|
|
1065
|
+
|
|
1066
|
+
Required Arguments:
|
|
1067
|
+
|
|
1068
|
+
- `origin_idx`
|
|
1069
|
+
- Type: int
|
|
1070
|
+
- What: The index of the origin node
|
|
1071
|
+
- `destination_idx`
|
|
1072
|
+
- Type: int
|
|
1073
|
+
- What: The index of the destination node
|
|
1074
|
+
|
|
1075
|
+
Optional Arguments:
|
|
1076
|
+
|
|
1077
|
+
- `distance`
|
|
1078
|
+
- Type: int | float
|
|
1079
|
+
- What: The distance between the origin and destination nodes in terms of the graph distance (normally km)
|
|
1080
|
+
- Default: 0
|
|
1081
|
+
- `use_haversine_distance`
|
|
1082
|
+
- Type: bool
|
|
1083
|
+
- What: Whether to calculate the haversine distance (km) between the nodes when calculating the distance
|
|
1084
|
+
- Default: True
|
|
1085
|
+
- Note: If true, overrides the `distance` argument
|
|
1086
|
+
- `undirected`
|
|
1087
|
+
- Type: bool
|
|
1088
|
+
- What: Whether to add the arc in both directions
|
|
1089
|
+
- Default: True
|
|
1090
|
+
"""
|
|
1091
|
+
assert origin_idx < len(self.graph), "Origin node does not exist"
|
|
1092
|
+
assert destination_idx < len(
|
|
1093
|
+
self.graph
|
|
1094
|
+
), "Destination node does not exist"
|
|
1095
|
+
if use_haversine_distance:
|
|
1096
|
+
distance = haversine(
|
|
1097
|
+
self.nodes[origin_idx], self.nodes[destination_idx]
|
|
1098
|
+
)
|
|
1099
|
+
self.graph[origin_idx][destination_idx] = distance
|
|
1100
|
+
if undirected:
|
|
1101
|
+
self.graph[destination_idx][origin_idx] = distance
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
def load_geojson_as_geograph(geojson_filename: str) -> GeoGraph:
|
|
1105
|
+
"""
|
|
1106
|
+
Function:
|
|
1107
|
+
|
|
1108
|
+
- Create a CustomGeoGraph object loaded from a geojson file
|
|
1109
|
+
|
|
1110
|
+
Required Arguments:
|
|
1111
|
+
|
|
1112
|
+
- `geojson_filename`
|
|
1113
|
+
- Type: str
|
|
1114
|
+
- What: The filename of the geojson file to load
|
|
1115
|
+
- Note: All arcs read in will be undirected
|
|
1116
|
+
- Note: This geojson file must be formatted in a specific way
|
|
1117
|
+
- The geojson file must be a FeatureCollection
|
|
1118
|
+
- Each feature must be a LineString with two coordinate pairs
|
|
1119
|
+
- The first coordinate pair must be the origin node
|
|
1120
|
+
- The second coordinate pair must be the destination node
|
|
1121
|
+
- The properties of the feature must include the distance between the origin and destination nodes
|
|
1122
|
+
- The properties of the feature must include the origin and destination node idxs
|
|
1123
|
+
- Origin and destination node idxs must be integers between 0 and n-1 where n is the number of nodes in the graph
|
|
1124
|
+
- EG:
|
|
1125
|
+
```
|
|
1126
|
+
{
|
|
1127
|
+
"type": "FeatureCollection",
|
|
1128
|
+
"features": [
|
|
1129
|
+
{
|
|
1130
|
+
"type": "Feature",
|
|
1131
|
+
"properties": {
|
|
1132
|
+
"origin_idx": 0,
|
|
1133
|
+
"destination_idx": 1,
|
|
1134
|
+
"distance": 10
|
|
1135
|
+
},
|
|
1136
|
+
"geometry": {
|
|
1137
|
+
"type": "LineString",
|
|
1138
|
+
"coordinates": [
|
|
1139
|
+
[121.47, 31.23],
|
|
1140
|
+
[121.48, 31.24]
|
|
1141
|
+
]
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
]
|
|
1145
|
+
}
|
|
1146
|
+
```
|
|
1147
|
+
"""
|
|
1148
|
+
with open(geojson_filename, "r") as f:
|
|
1149
|
+
geojson_features = json.load(f).get("features", [])
|
|
1150
|
+
|
|
1151
|
+
nodes_dict = {}
|
|
1152
|
+
graph_dict = {}
|
|
1153
|
+
for feature in geojson_features:
|
|
1154
|
+
properties = feature.get("properties", {})
|
|
1155
|
+
origin_idx = properties.get("origin_idx")
|
|
1156
|
+
destination_idx = properties.get("destination_idx")
|
|
1157
|
+
distance = properties.get("distance")
|
|
1158
|
+
geometry = feature.get("geometry", {})
|
|
1159
|
+
coordinates = geometry.get("coordinates", [])
|
|
1160
|
+
|
|
1161
|
+
# Validations
|
|
1162
|
+
assert (
|
|
1163
|
+
feature.get("type") == "Feature"
|
|
1164
|
+
), "All features must be of type 'Feature'"
|
|
1165
|
+
assert (
|
|
1166
|
+
geometry.get("type") == "LineString"
|
|
1167
|
+
), "All geometries must be of type 'LineString'"
|
|
1168
|
+
assert (
|
|
1169
|
+
len(coordinates) == 2
|
|
1170
|
+
), "All LineStrings must have exactly 2 coordinates"
|
|
1171
|
+
assert isinstance(
|
|
1172
|
+
origin_idx, int
|
|
1173
|
+
), "All features must have an 'origin_idx' property that is an integer"
|
|
1174
|
+
assert isinstance(
|
|
1175
|
+
destination_idx, int
|
|
1176
|
+
), "All features must have a 'destination_idx' property that is an integer"
|
|
1177
|
+
assert isinstance(
|
|
1178
|
+
distance, (int, float)
|
|
1179
|
+
), "All features must have a 'distance' property that is a number"
|
|
1180
|
+
assert (
|
|
1181
|
+
origin_idx >= 0
|
|
1182
|
+
), "All origin_idxs must be greater than or equal to 0"
|
|
1183
|
+
assert (
|
|
1184
|
+
destination_idx >= 0
|
|
1185
|
+
), "All destination_idxs must be greater than or equal to 0"
|
|
1186
|
+
assert distance >= 0, "All distances must be greater than or equal to 0"
|
|
1187
|
+
origin = coordinates[0]
|
|
1188
|
+
destination = coordinates[1]
|
|
1189
|
+
assert isinstance(origin, list), "All coordinates must be lists"
|
|
1190
|
+
assert isinstance(destination, list), "All coordinates must be lists"
|
|
1191
|
+
assert len(origin) == 2, "All coordinates must have a length of 2"
|
|
1192
|
+
assert len(destination) == 2, "All coordinates must have a length of 2"
|
|
1193
|
+
assert all(
|
|
1194
|
+
[isinstance(i, (int, float)) for i in origin]
|
|
1195
|
+
), "All coordinates must be numeric"
|
|
1196
|
+
assert all(
|
|
1197
|
+
[isinstance(i, (int, float)) for i in destination]
|
|
1198
|
+
), "All coordinates must be numeric"
|
|
1199
|
+
# assert all([origin[0] >= -90, origin[0] <= 90, origin[1] >= -180, origin[1] <= 180]), "All coordinates must be valid latitudes and longitudes"
|
|
1200
|
+
# assert all([destination[0] >= -90, destination[0] <= 90, destination[1] >= -180, destination[1] <= 180]), "All coordinates must be valid latitudes and longitudes"
|
|
1201
|
+
|
|
1202
|
+
# Update the data
|
|
1203
|
+
nodes_dict[origin_idx] = origin
|
|
1204
|
+
nodes_dict[destination_idx] = destination
|
|
1205
|
+
graph_dict[origin_idx] = {
|
|
1206
|
+
**graph_dict.get(origin_idx, {}),
|
|
1207
|
+
destination_idx: distance,
|
|
1208
|
+
}
|
|
1209
|
+
graph_dict[destination_idx] = {
|
|
1210
|
+
**graph_dict.get(destination_idx, {}),
|
|
1211
|
+
origin_idx: distance,
|
|
1212
|
+
}
|
|
1213
|
+
assert len(nodes_dict) == len(
|
|
1214
|
+
graph_dict
|
|
1215
|
+
), "All nodes must be included as origins in the graph dictionary"
|
|
1216
|
+
nodes = [
|
|
1217
|
+
[i[1][1], i[1][0]]
|
|
1218
|
+
for i in sorted(nodes_dict.items(), key=lambda x: x[0])
|
|
1219
|
+
]
|
|
1220
|
+
ordered_graph_tuple = sorted(graph_dict.items(), key=lambda x: x[0])
|
|
1221
|
+
graph_map = {i[0]: idx for idx, i in enumerate(ordered_graph_tuple)}
|
|
1222
|
+
graph = [
|
|
1223
|
+
{graph_map[k]: v for k, v in i[1].items()} for i in ordered_graph_tuple
|
|
1224
|
+
]
|
|
1225
|
+
return GeoGraph(graph=graph, nodes=nodes)
|
|
1226
|
+
|
|
1227
|
+
|
|
1228
|
+
def get_multi_path_geojson(
|
|
1229
|
+
routes: list[dict],
|
|
1230
|
+
filename: [str, None] = None,
|
|
1231
|
+
show_progress: bool = False,
|
|
1232
|
+
) -> dict:
|
|
1233
|
+
"""
|
|
1234
|
+
Creates a GeoJSON file with the shortest path between the origin and destination of each route.
|
|
1235
|
+
|
|
1236
|
+
Required Parameters:
|
|
1237
|
+
|
|
1238
|
+
- `routes`: list[dict]
|
|
1239
|
+
- List of dictionaries with the following keys:
|
|
1240
|
+
- geograph: GeoGraph
|
|
1241
|
+
- Geograph object to use for the shortest path calculation.
|
|
1242
|
+
- origin: dict[float|float]
|
|
1243
|
+
- Origin coordinates
|
|
1244
|
+
- EG: {"latitude":39.2904, "longitude":-76.6122}
|
|
1245
|
+
- destination: dict[float|int]
|
|
1246
|
+
- Destination coordinates
|
|
1247
|
+
- EG: {"latitude":39.2904, "longitude":-76.6122}
|
|
1248
|
+
- properties: dict
|
|
1249
|
+
- Dictionary with the properties of the route
|
|
1250
|
+
- Everything in this dictionary will be included in the output GeoJSON file as properties of the route.
|
|
1251
|
+
- EG: {"id":"route_1", "name":"Route 1", "color":"#ff0000"}
|
|
1252
|
+
|
|
1253
|
+
Optional Parameters:
|
|
1254
|
+
|
|
1255
|
+
- `filename`: str | None
|
|
1256
|
+
- Name of the output GeoJSON file.
|
|
1257
|
+
- If None, the function will not save the file
|
|
1258
|
+
- Default: None
|
|
1259
|
+
- `show_progress`: bool
|
|
1260
|
+
- Whether to show basic progress information
|
|
1261
|
+
- Default: False
|
|
1262
|
+
|
|
1263
|
+
Returns
|
|
1264
|
+
|
|
1265
|
+
- `output`: dict
|
|
1266
|
+
- Dictionary with the GeoJSON file content.
|
|
1267
|
+
"""
|
|
1268
|
+
assert isinstance(routes, list), "Routes must be a list"
|
|
1269
|
+
assert all(
|
|
1270
|
+
[isinstance(i, dict) for i in routes]
|
|
1271
|
+
), "Routes must be a list of dictionaries"
|
|
1272
|
+
assert all(
|
|
1273
|
+
[isinstance(i.get("geograph"), GeoGraph) for i in routes]
|
|
1274
|
+
), "All routes must have a 'geograph' key with a GeoGraph object"
|
|
1275
|
+
assert all(
|
|
1276
|
+
[isinstance(i.get("origin"), dict) for i in routes]
|
|
1277
|
+
), "All routes must have an 'origin' key with a dictionary"
|
|
1278
|
+
assert all(
|
|
1279
|
+
[isinstance(i.get("destination"), dict) for i in routes]
|
|
1280
|
+
), "All routes must have a 'destination' key with a dictionary"
|
|
1281
|
+
assert all(
|
|
1282
|
+
[isinstance(i.get("properties"), dict) for i in routes]
|
|
1283
|
+
), "All routes must have a 'properties' key with a dictionary"
|
|
1284
|
+
assert all(
|
|
1285
|
+
[isinstance(i["origin"].get("latitude"), (int, float)) for i in routes]
|
|
1286
|
+
), "All origins must have a 'latitude' key with a number"
|
|
1287
|
+
assert all(
|
|
1288
|
+
[isinstance(i["origin"].get("longitude"), (int, float)) for i in routes]
|
|
1289
|
+
), "All origins must have a 'longitude' key with a number"
|
|
1290
|
+
assert all(
|
|
1291
|
+
[
|
|
1292
|
+
isinstance(i["destination"].get("latitude"), (int, float))
|
|
1293
|
+
for i in routes
|
|
1294
|
+
]
|
|
1295
|
+
), "All destinations must have a 'latitude' key with a number"
|
|
1296
|
+
assert all(
|
|
1297
|
+
[
|
|
1298
|
+
isinstance(i["destination"].get("longitude"), (int, float))
|
|
1299
|
+
for i in routes
|
|
1300
|
+
]
|
|
1301
|
+
), "All destinations must have a 'longitude' key with a number"
|
|
1302
|
+
assert all(
|
|
1303
|
+
[
|
|
1304
|
+
(
|
|
1305
|
+
i["origin"].get("latitude") >= -90
|
|
1306
|
+
and i["origin"].get("latitude") <= 90
|
|
1307
|
+
)
|
|
1308
|
+
for i in routes
|
|
1309
|
+
]
|
|
1310
|
+
), "All origin latitudes must be between -90 and 90"
|
|
1311
|
+
assert all(
|
|
1312
|
+
[
|
|
1313
|
+
(
|
|
1314
|
+
i["origin"].get("longitude") >= -180
|
|
1315
|
+
and i["origin"].get("longitude") <= 180
|
|
1316
|
+
)
|
|
1317
|
+
for i in routes
|
|
1318
|
+
]
|
|
1319
|
+
), "All origin longitudes must be between -180 and 180"
|
|
1320
|
+
assert all(
|
|
1321
|
+
[
|
|
1322
|
+
(
|
|
1323
|
+
i["destination"].get("latitude") >= -90
|
|
1324
|
+
and i["destination"].get("latitude") <= 90
|
|
1325
|
+
)
|
|
1326
|
+
for i in routes
|
|
1327
|
+
]
|
|
1328
|
+
), "All destination latitudes must be between -90 and 90"
|
|
1329
|
+
assert all(
|
|
1330
|
+
[
|
|
1331
|
+
(
|
|
1332
|
+
i["destination"].get("longitude") >= -180
|
|
1333
|
+
and i["destination"].get("longitude") <= 180
|
|
1334
|
+
)
|
|
1335
|
+
for i in routes
|
|
1336
|
+
]
|
|
1337
|
+
), "All destination longitudes must be between -180 and 180"
|
|
1338
|
+
|
|
1339
|
+
output = {"type": "FeatureCollection", "features": []}
|
|
1340
|
+
len_routes = len(routes)
|
|
1341
|
+
for idx, route in enumerate(routes):
|
|
1342
|
+
shortest_path = route["geograph"].get_shortest_path(
|
|
1343
|
+
route["origin"], route["destination"]
|
|
1344
|
+
)
|
|
1345
|
+
shortest_line_path = get_line_path(shortest_path)
|
|
1346
|
+
output["features"].append(
|
|
1347
|
+
{
|
|
1348
|
+
"type": "Feature",
|
|
1349
|
+
"properties": route["properties"],
|
|
1350
|
+
"geometry": shortest_line_path,
|
|
1351
|
+
}
|
|
1352
|
+
)
|
|
1353
|
+
if show_progress:
|
|
1354
|
+
print(
|
|
1355
|
+
f"[{'='*(int((idx+1)/len_routes*20))}>{' '*(20-int((idx+1)/len_routes*20))}] {idx+1}/{len_routes}",
|
|
1356
|
+
end="\r",
|
|
1357
|
+
)
|
|
1358
|
+
if show_progress:
|
|
1359
|
+
print()
|
|
1360
|
+
if filename is not None:
|
|
1361
|
+
json.dump(output, open(filename, "w"))
|
|
1362
|
+
return output
|
|
@@ -2,10 +2,10 @@ import math, json
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def haversine(
|
|
5
|
-
origin: list[float
|
|
6
|
-
destination: list[float
|
|
5
|
+
origin: list[float|int],
|
|
6
|
+
destination: list[float|int],
|
|
7
7
|
units: str = "km",
|
|
8
|
-
circuity: [int
|
|
8
|
+
circuity: [float|int] = 1,
|
|
9
9
|
):
|
|
10
10
|
"""
|
|
11
11
|
Function:
|
|
@@ -16,10 +16,10 @@ def haversine(
|
|
|
16
16
|
|
|
17
17
|
- `origin`:
|
|
18
18
|
- Type: list of two floats | ints
|
|
19
|
-
- What: The origin point as a list of "
|
|
19
|
+
- What: The origin point as a list of "latitude" and "longitude"
|
|
20
20
|
- `destination`:
|
|
21
21
|
- Type: list of two floats | ints
|
|
22
|
-
- What: The destination point as a list of "
|
|
22
|
+
- What: The destination point as a list of "latitude" and "longitude"
|
|
23
23
|
|
|
24
24
|
Optional Arguments:
|
|
25
25
|
|
|
@@ -69,7 +69,7 @@ def haversine(
|
|
|
69
69
|
raise Exception()
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
def hard_round(decimal_places: int, a: [int
|
|
72
|
+
def hard_round(decimal_places: int, a: [float|int]):
|
|
73
73
|
"""
|
|
74
74
|
Function:
|
|
75
75
|
|
|
@@ -90,7 +90,7 @@ def hard_round(decimal_places: int, a: [int, float]):
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def distance_converter(
|
|
93
|
-
distance: [int
|
|
93
|
+
distance: [float|int], input_units: str, output_units: str
|
|
94
94
|
):
|
|
95
95
|
"""
|
|
96
96
|
Function:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scgraph
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -9,7 +9,7 @@ Project-URL: Documentation, https://connor-makowski.github.io/scgraph/core.html
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
|
|
@@ -57,7 +57,9 @@ Low Level: https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
|
57
57
|
|
|
58
58
|
## Setup
|
|
59
59
|
|
|
60
|
-
Make sure you have Python 3.
|
|
60
|
+
Make sure you have Python 3.10.x (or higher) installed on your system. You can download it [here](https://www.python.org/downloads/).
|
|
61
|
+
|
|
62
|
+
Support for python3.6-3.9 is available up to version 2.2.0
|
|
61
63
|
|
|
62
64
|
## Installation
|
|
63
65
|
|
|
@@ -67,7 +69,10 @@ pip install scgraph
|
|
|
67
69
|
|
|
68
70
|
## Use with Google Colab
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
- [Getting Started](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/getting_started.ipynb)
|
|
73
|
+
- [Creating A Multi Path Geojson](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/multi_path_geojson.ipynb)
|
|
74
|
+
- [Modifying A Geograph](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/geograph_modifications.ipynb)
|
|
75
|
+
|
|
71
76
|
|
|
72
77
|
# Getting Started
|
|
73
78
|
|
|
@@ -101,7 +106,7 @@ In the above example, the `output` variable is a dictionary with three keys: `le
|
|
|
101
106
|
- `ft` (feet)
|
|
102
107
|
- `coordinate_path`: A list of lists [`latitude`,`longitude`] that make up the shortest path
|
|
103
108
|
|
|
104
|
-
For more examples including viewing the output on a map, see the [example notebook](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/
|
|
109
|
+
For more examples including viewing the output on a map, see the [example notebook](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/getting_started.ipynb).
|
|
105
110
|
|
|
106
111
|
## Included GeoGraphs
|
|
107
112
|
|
|
@@ -165,15 +170,17 @@ output = marnet_geograph.get_shortest_path(
|
|
|
165
170
|
get_line_path(output, filename='output.geojson')
|
|
166
171
|
```
|
|
167
172
|
|
|
173
|
+
Modify an existing geograph: See the notebook [here](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/examples/geograph_modifications.ipynb)
|
|
174
|
+
|
|
168
175
|
|
|
169
176
|
You can specify your own custom graphs for direct access to the solving algorithms. This requires the use of the low level `Graph` class
|
|
170
177
|
|
|
171
178
|
```py
|
|
172
179
|
from scgraph import Graph
|
|
173
180
|
|
|
174
|
-
# Define
|
|
181
|
+
# Define an arbitrary graph
|
|
175
182
|
# See the graph definitions here:
|
|
176
|
-
# https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
183
|
+
# https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
177
184
|
graph = [
|
|
178
185
|
{1: 5, 2: 1},
|
|
179
186
|
{0: 5, 2: 2, 3: 1},
|
|
@@ -198,25 +205,67 @@ from scgraph import GeoGraph
|
|
|
198
205
|
|
|
199
206
|
# Define nodes
|
|
200
207
|
# See the nodes definitions here:
|
|
201
|
-
# https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
208
|
+
# https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
202
209
|
nodes = [
|
|
203
|
-
|
|
204
|
-
[0
|
|
205
|
-
|
|
206
|
-
[
|
|
207
|
-
|
|
208
|
-
[
|
|
210
|
+
# London
|
|
211
|
+
[51.5074, -0.1278],
|
|
212
|
+
# Paris
|
|
213
|
+
[48.8566, 2.3522],
|
|
214
|
+
# Berlin
|
|
215
|
+
[52.5200, 13.4050],
|
|
216
|
+
# Rome
|
|
217
|
+
[41.9028, 12.4964],
|
|
218
|
+
# Madrid
|
|
219
|
+
[40.4168, -3.7038],
|
|
220
|
+
# Lisbon
|
|
221
|
+
[38.7223, -9.1393]
|
|
209
222
|
]
|
|
210
223
|
# Define a graph
|
|
211
224
|
# See the graph definitions here:
|
|
212
|
-
# https://connor-makowski.github.io/scgraph/scgraph/core.html
|
|
225
|
+
# https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph
|
|
213
226
|
graph = [
|
|
214
|
-
|
|
215
|
-
{
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
227
|
+
# From London
|
|
228
|
+
{
|
|
229
|
+
# To Paris
|
|
230
|
+
1: 311,
|
|
231
|
+
},
|
|
232
|
+
# From Paris
|
|
233
|
+
{
|
|
234
|
+
# To London
|
|
235
|
+
0: 311,
|
|
236
|
+
# To Berlin
|
|
237
|
+
2: 878,
|
|
238
|
+
# To Rome
|
|
239
|
+
3: 1439,
|
|
240
|
+
# To Madrid
|
|
241
|
+
4: 1053
|
|
242
|
+
},
|
|
243
|
+
# From Berlin
|
|
244
|
+
{
|
|
245
|
+
# To Paris
|
|
246
|
+
1: 878,
|
|
247
|
+
# To Rome
|
|
248
|
+
3: 1181,
|
|
249
|
+
},
|
|
250
|
+
# From Rome
|
|
251
|
+
{
|
|
252
|
+
# To Paris
|
|
253
|
+
1: 1439,
|
|
254
|
+
# To Berlin
|
|
255
|
+
2: 1181,
|
|
256
|
+
},
|
|
257
|
+
# From Madrid
|
|
258
|
+
{
|
|
259
|
+
# To Paris
|
|
260
|
+
1: 1053,
|
|
261
|
+
# To Lisbon
|
|
262
|
+
5: 623
|
|
263
|
+
},
|
|
264
|
+
# From Lisbon
|
|
265
|
+
{
|
|
266
|
+
# To Madrid
|
|
267
|
+
4: 623
|
|
268
|
+
}
|
|
220
269
|
]
|
|
221
270
|
|
|
222
271
|
# Create a GeoGraph object
|
|
@@ -229,23 +278,31 @@ my_geograph.validate_graph()
|
|
|
229
278
|
my_geograph.validate_nodes()
|
|
230
279
|
|
|
231
280
|
# Get the shortest path between two points
|
|
281
|
+
# In this case, Birmingham England and Zaragoza Spain
|
|
282
|
+
# Since Birmingham and Zaragoza are not in the graph,
|
|
283
|
+
# the algorithm will add them into the graph.
|
|
284
|
+
# See: https://connor-makowski.github.io/scgraph/scgraph/core.html#GeoGraph.get_shortest_path
|
|
285
|
+
# Expected output would be to go from
|
|
286
|
+
# Birmingham -> London -> Paris -> Madrid -> Zaragoza
|
|
287
|
+
|
|
232
288
|
output = my_geograph.get_shortest_path(
|
|
233
|
-
|
|
234
|
-
|
|
289
|
+
# Birmingham England
|
|
290
|
+
origin_node = {'latitude': 52.4862, 'longitude': -1.8904},
|
|
291
|
+
# Zaragoza Spain
|
|
292
|
+
destination_node = {'latitude': 41.6488, 'longitude': -0.8891}
|
|
235
293
|
)
|
|
236
|
-
|
|
294
|
+
print(output)
|
|
237
295
|
# {
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
# [
|
|
241
|
-
# [
|
|
242
|
-
# [
|
|
243
|
-
# [
|
|
244
|
-
# [
|
|
245
|
-
#
|
|
246
|
-
# ],
|
|
247
|
-
# "length": 10
|
|
296
|
+
# 'length': 1799.4323,
|
|
297
|
+
# 'coordinate_path': [
|
|
298
|
+
# [52.4862, -1.8904],
|
|
299
|
+
# [51.5074, -0.1278],
|
|
300
|
+
# [48.8566, 2.3522],
|
|
301
|
+
# [40.4168, -3.7038],
|
|
302
|
+
# [41.6488, -0.8891]
|
|
303
|
+
# ]
|
|
248
304
|
# }
|
|
305
|
+
|
|
249
306
|
```
|
|
250
307
|
|
|
251
308
|
## Attributions and Thanks
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|