scgraph 2.1.2__tar.gz → 2.2.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.2.0}/PKG-INFO +3 -1
- {scgraph-2.1.2 → scgraph-2.2.0}/README.md +2 -0
- {scgraph-2.1.2 → scgraph-2.2.0}/pyproject.toml +1 -1
- {scgraph-2.1.2 → scgraph-2.2.0}/scgraph/core.py +217 -9
- {scgraph-2.1.2 → scgraph-2.2.0}/scgraph/utils.py +2 -2
- {scgraph-2.1.2 → scgraph-2.2.0/scgraph.egg-info}/PKG-INFO +3 -1
- {scgraph-2.1.2 → scgraph-2.2.0}/setup.cfg +1 -1
- {scgraph-2.1.2 → scgraph-2.2.0}/LICENSE +0 -0
- {scgraph-2.1.2 → scgraph-2.2.0}/scgraph/__init__.py +0 -0
- {scgraph-2.1.2 → scgraph-2.2.0}/scgraph/geographs/__init__.py +0 -0
- {scgraph-2.1.2 → scgraph-2.2.0}/scgraph/geographs/marnet.py +0 -0
- {scgraph-2.1.2 → scgraph-2.2.0}/scgraph/geographs/north_america_rail.py +0 -0
- {scgraph-2.1.2 → scgraph-2.2.0}/scgraph/geographs/oak_ridge_maritime.py +0 -0
- {scgraph-2.1.2 → scgraph-2.2.0}/scgraph/geographs/us_freeway.py +0 -0
- {scgraph-2.1.2 → scgraph-2.2.0}/scgraph.egg-info/SOURCES.txt +0 -0
- {scgraph-2.1.2 → scgraph-2.2.0}/scgraph.egg-info/dependency_links.txt +0 -0
- {scgraph-2.1.2 → scgraph-2.2.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.2.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
|
|
@@ -165,6 +165,8 @@ output = marnet_geograph.get_shortest_path(
|
|
|
165
165
|
get_line_path(output, filename='output.geojson')
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
+
Modify an existing geograph: See the notebook [here](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/example_making_modifications.ipynb)
|
|
169
|
+
|
|
168
170
|
|
|
169
171
|
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
172
|
|
|
@@ -150,6 +150,8 @@ output = marnet_geograph.get_shortest_path(
|
|
|
150
150
|
get_line_path(output, filename='output.geojson')
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
Modify an existing geograph: See the notebook [here](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/example_making_modifications.ipynb)
|
|
154
|
+
|
|
153
155
|
|
|
154
156
|
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
157
|
|
|
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
|
|
|
12
12
|
|
|
13
13
|
[project]
|
|
14
14
|
name = "scgraph"
|
|
15
|
-
version = "2.
|
|
15
|
+
version = "2.2.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"}
|
|
@@ -315,9 +315,6 @@ class GeoGraph:
|
|
|
315
315
|
- Type: list of lists
|
|
316
316
|
- What: A list of lists where the values are coordinates (latitude then longitude)
|
|
317
317
|
- Note: The length of the nodes list must be the same as that of the graph list
|
|
318
|
-
|
|
319
|
-
Optional Arguments:
|
|
320
|
-
|
|
321
318
|
"""
|
|
322
319
|
self.graph = graph
|
|
323
320
|
self.nodes = nodes
|
|
@@ -877,13 +874,13 @@ class GeoGraph:
|
|
|
877
874
|
|
|
878
875
|
"""
|
|
879
876
|
features = []
|
|
880
|
-
for origin_idx, destinations in self.graph
|
|
877
|
+
for origin_idx, destinations in enumerate(self.graph):
|
|
881
878
|
for destination_idx, distance in destinations.items():
|
|
882
879
|
# Create an undirected graph for geojson purposes
|
|
883
880
|
if origin_idx > destination_idx:
|
|
884
881
|
continue
|
|
885
|
-
origin = self.nodes
|
|
886
|
-
destination = self.nodes
|
|
882
|
+
origin = self.nodes[origin_idx]
|
|
883
|
+
destination = self.nodes[destination_idx]
|
|
887
884
|
features.append(
|
|
888
885
|
{
|
|
889
886
|
"type": "Feature",
|
|
@@ -895,10 +892,10 @@ class GeoGraph:
|
|
|
895
892
|
"geometry": {
|
|
896
893
|
"type": "LineString",
|
|
897
894
|
"coordinates": [
|
|
898
|
-
[origin[
|
|
895
|
+
[origin[1], origin[0]],
|
|
899
896
|
[
|
|
900
|
-
destination[
|
|
901
|
-
destination[
|
|
897
|
+
destination[1],
|
|
898
|
+
destination[0],
|
|
902
899
|
],
|
|
903
900
|
],
|
|
904
901
|
},
|
|
@@ -908,3 +905,214 @@ class GeoGraph:
|
|
|
908
905
|
out_dict = {"type": "FeatureCollection", "features": features}
|
|
909
906
|
with open(filename, "w") as f:
|
|
910
907
|
json.dump(out_dict, f)
|
|
908
|
+
|
|
909
|
+
def save_as_geograph(self, name: str) -> None:
|
|
910
|
+
"""
|
|
911
|
+
Function:
|
|
912
|
+
|
|
913
|
+
- Save the current geograph as an importable python file
|
|
914
|
+
|
|
915
|
+
Required Arguments:
|
|
916
|
+
|
|
917
|
+
- `name`
|
|
918
|
+
- Type: str
|
|
919
|
+
- What: The name of the geograph and file
|
|
920
|
+
- EG: 'custom'
|
|
921
|
+
- Stored as: 'custom.py'
|
|
922
|
+
- In your current directory
|
|
923
|
+
- Import as: 'from .custom import custom_geograph'
|
|
924
|
+
"""
|
|
925
|
+
self.validate_nodes()
|
|
926
|
+
self.validate_graph(
|
|
927
|
+
check_symmetry=True, check_connected=False
|
|
928
|
+
)
|
|
929
|
+
out_string = f"""from scgraph.core import GeoGraph\ngraph={str(self.graph)}\nnodes={str(self.nodes)}\n{name}_geograph = GeoGraph(graph=graph, nodes=nodes)"""
|
|
930
|
+
with open(name+".py", "w") as f:
|
|
931
|
+
f.write(out_string)
|
|
932
|
+
|
|
933
|
+
def mod_remove_arc(self, origin_idx: int, destination_idx: int, undirected: bool = True) -> None:
|
|
934
|
+
"""
|
|
935
|
+
Function:
|
|
936
|
+
|
|
937
|
+
- Remove an arc from the graph
|
|
938
|
+
|
|
939
|
+
Required Arguments:
|
|
940
|
+
|
|
941
|
+
- `origin_idx`
|
|
942
|
+
- Type: int
|
|
943
|
+
- What: The index of the origin node
|
|
944
|
+
- `destination_idx`
|
|
945
|
+
- Type: int
|
|
946
|
+
- What: The index of the destination node
|
|
947
|
+
|
|
948
|
+
Optional Arguments:
|
|
949
|
+
|
|
950
|
+
- `undirected`
|
|
951
|
+
- Type: bool
|
|
952
|
+
- What: Whether to remove the arc in both directions
|
|
953
|
+
- Default: True
|
|
954
|
+
"""
|
|
955
|
+
assert origin_idx < len(self.graph), "Origin node does not exist"
|
|
956
|
+
assert destination_idx < len(self.graph), "Destination node does not exist"
|
|
957
|
+
assert destination_idx in self.graph[origin_idx], "Arc does not exist"
|
|
958
|
+
del self.graph[origin_idx][destination_idx]
|
|
959
|
+
if undirected:
|
|
960
|
+
if origin_idx in self.graph[destination_idx]:
|
|
961
|
+
del self.graph[destination_idx][origin_idx]
|
|
962
|
+
|
|
963
|
+
def mod_add_node(self, latitude: [float, int], longitude: [float, int]) -> int:
|
|
964
|
+
"""
|
|
965
|
+
Function:
|
|
966
|
+
|
|
967
|
+
- Add a node to the graph
|
|
968
|
+
|
|
969
|
+
Required Arguments:
|
|
970
|
+
|
|
971
|
+
- `latitude`
|
|
972
|
+
- Type: int | float
|
|
973
|
+
- What: The latitude of the node
|
|
974
|
+
- `longitude`
|
|
975
|
+
- Type: int | float
|
|
976
|
+
- What: The longitude of the node
|
|
977
|
+
|
|
978
|
+
Returns:
|
|
979
|
+
|
|
980
|
+
- The index of the new node
|
|
981
|
+
"""
|
|
982
|
+
self.nodes.append([latitude, longitude])
|
|
983
|
+
self.graph.append({})
|
|
984
|
+
return len(self.graph) - 1
|
|
985
|
+
|
|
986
|
+
def mod_add_arc(self, origin_idx: int, destination_idx: int, distance: [int, float]=0, use_haversine_distance=True, undirected: bool = True) -> None:
|
|
987
|
+
"""
|
|
988
|
+
Function:
|
|
989
|
+
|
|
990
|
+
- Add an arc to the graph
|
|
991
|
+
|
|
992
|
+
Required Arguments:
|
|
993
|
+
|
|
994
|
+
- `origin_idx`
|
|
995
|
+
- Type: int
|
|
996
|
+
- What: The index of the origin node
|
|
997
|
+
- `destination_idx`
|
|
998
|
+
- Type: int
|
|
999
|
+
- What: The index of the destination node
|
|
1000
|
+
|
|
1001
|
+
Optional Arguments:
|
|
1002
|
+
|
|
1003
|
+
- `distance`
|
|
1004
|
+
- Type: int | float
|
|
1005
|
+
- What: The distance between the origin and destination nodes in terms of the graph distance (normally km)
|
|
1006
|
+
- Default: 0
|
|
1007
|
+
- `use_haversine_distance`
|
|
1008
|
+
- Type: bool
|
|
1009
|
+
- What: Whether to calculate the haversine distance (km) between the nodes when calculating the distance
|
|
1010
|
+
- Default: True
|
|
1011
|
+
- Note: If true, overrides the `distance` argument
|
|
1012
|
+
- `undirected`
|
|
1013
|
+
- Type: bool
|
|
1014
|
+
- What: Whether to add the arc in both directions
|
|
1015
|
+
- Default: True
|
|
1016
|
+
"""
|
|
1017
|
+
assert origin_idx < len(self.graph), "Origin node does not exist"
|
|
1018
|
+
assert destination_idx < len(self.graph), "Destination node does not exist"
|
|
1019
|
+
if use_haversine_distance:
|
|
1020
|
+
distance = haversine(self.nodes[origin_idx], self.nodes[destination_idx])
|
|
1021
|
+
self.graph[origin_idx][destination_idx] = distance
|
|
1022
|
+
if undirected:
|
|
1023
|
+
self.graph[destination_idx][origin_idx] = distance
|
|
1024
|
+
|
|
1025
|
+
def load_geojson_as_geograph(geojson_filename: str) -> GeoGraph:
|
|
1026
|
+
"""
|
|
1027
|
+
Function:
|
|
1028
|
+
|
|
1029
|
+
- Create a CustomGeoGraph object loaded from a geojson file
|
|
1030
|
+
|
|
1031
|
+
Required Arguments:
|
|
1032
|
+
|
|
1033
|
+
- `geojson_filename`
|
|
1034
|
+
- Type: str
|
|
1035
|
+
- What: The filename of the geojson file to load
|
|
1036
|
+
- Note: All arcs read in will be undirected
|
|
1037
|
+
- Note: This geojson file must be formatted in a specific way
|
|
1038
|
+
- The geojson file must be a FeatureCollection
|
|
1039
|
+
- Each feature must be a LineString with two coordinate pairs
|
|
1040
|
+
- The first coordinate pair must be the origin node
|
|
1041
|
+
- The second coordinate pair must be the destination node
|
|
1042
|
+
- The properties of the feature must include the distance between the origin and destination nodes
|
|
1043
|
+
- The properties of the feature must include the origin and destination node idxs
|
|
1044
|
+
- Origin and destination node idxs must be integers between 0 and n-1 where n is the number of nodes in the graph
|
|
1045
|
+
- EG:
|
|
1046
|
+
```
|
|
1047
|
+
{
|
|
1048
|
+
"type": "FeatureCollection",
|
|
1049
|
+
"features": [
|
|
1050
|
+
{
|
|
1051
|
+
"type": "Feature",
|
|
1052
|
+
"properties": {
|
|
1053
|
+
"origin_idx": 0,
|
|
1054
|
+
"destination_idx": 1,
|
|
1055
|
+
"distance": 10
|
|
1056
|
+
},
|
|
1057
|
+
"geometry": {
|
|
1058
|
+
"type": "LineString",
|
|
1059
|
+
"coordinates": [
|
|
1060
|
+
[121.47, 31.23],
|
|
1061
|
+
[121.48, 31.24]
|
|
1062
|
+
]
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
]
|
|
1066
|
+
}
|
|
1067
|
+
```
|
|
1068
|
+
"""
|
|
1069
|
+
with open (geojson_filename, "r") as f:
|
|
1070
|
+
geojson_features = json.load(f).get("features", [])
|
|
1071
|
+
|
|
1072
|
+
nodes_dict = {}
|
|
1073
|
+
graph_dict = {}
|
|
1074
|
+
for feature in geojson_features:
|
|
1075
|
+
properties = feature.get("properties", {})
|
|
1076
|
+
origin_idx = properties.get("origin_idx")
|
|
1077
|
+
destination_idx = properties.get("destination_idx")
|
|
1078
|
+
distance = properties.get("distance")
|
|
1079
|
+
geometry = feature.get("geometry", {})
|
|
1080
|
+
coordinates = geometry.get("coordinates", [])
|
|
1081
|
+
|
|
1082
|
+
# Validations
|
|
1083
|
+
assert feature.get("type") == "Feature", "All features must be of type 'Feature'"
|
|
1084
|
+
assert geometry.get("type") == "LineString", "All geometries must be of type 'LineString'"
|
|
1085
|
+
assert len(coordinates) == 2, "All LineStrings must have exactly 2 coordinates"
|
|
1086
|
+
assert isinstance(origin_idx, int), "All features must have an 'origin_idx' property that is an integer"
|
|
1087
|
+
assert isinstance(destination_idx, int), "All features must have a 'destination_idx' property that is an integer"
|
|
1088
|
+
assert isinstance(distance, (int, float)), "All features must have a 'distance' property that is a number"
|
|
1089
|
+
assert origin_idx >= 0, "All origin_idxs must be greater than or equal to 0"
|
|
1090
|
+
assert destination_idx >= 0, "All destination_idxs must be greater than or equal to 0"
|
|
1091
|
+
assert distance >= 0, "All distances must be greater than or equal to 0"
|
|
1092
|
+
origin = coordinates[0]
|
|
1093
|
+
destination = coordinates[1]
|
|
1094
|
+
assert isinstance(origin, list), "All coordinates must be lists"
|
|
1095
|
+
assert isinstance(destination, list), "All coordinates must be lists"
|
|
1096
|
+
assert len(origin) == 2, "All coordinates must have a length of 2"
|
|
1097
|
+
assert len(destination) == 2, "All coordinates must have a length of 2"
|
|
1098
|
+
assert all([isinstance(i, (int, float)) for i in origin]), "All coordinates must be numeric"
|
|
1099
|
+
assert all([isinstance(i, (int, float)) for i in destination]), "All coordinates must be numeric"
|
|
1100
|
+
# assert all([origin[0] >= -90, origin[0] <= 90, origin[1] >= -180, origin[1] <= 180]), "All coordinates must be valid latitudes and longitudes"
|
|
1101
|
+
# assert all([destination[0] >= -90, destination[0] <= 90, destination[1] >= -180, destination[1] <= 180]), "All coordinates must be valid latitudes and longitudes"
|
|
1102
|
+
|
|
1103
|
+
# Update the data
|
|
1104
|
+
nodes_dict[origin_idx] = origin
|
|
1105
|
+
nodes_dict[destination_idx] = destination
|
|
1106
|
+
graph_dict[origin_idx] = {**graph_dict.get(origin_idx, {}), destination_idx: distance}
|
|
1107
|
+
graph_dict[destination_idx] = {**graph_dict.get(destination_idx, {}), origin_idx: distance}
|
|
1108
|
+
assert len(nodes_dict) == len(graph_dict), "All nodes must be included as origins in the graph dictionary"
|
|
1109
|
+
nodes = [[i[1][1],i[1][0]] for i in sorted(nodes_dict.items(), key=lambda x: x[0])]
|
|
1110
|
+
ordered_graph_tuple = sorted(graph_dict.items(), key=lambda x: x[0])
|
|
1111
|
+
graph_map = {i[0]: idx for idx, i in enumerate(ordered_graph_tuple)}
|
|
1112
|
+
graph = [
|
|
1113
|
+
{graph_map[k]: v for k, v in i[1].items()} for i in ordered_graph_tuple
|
|
1114
|
+
]
|
|
1115
|
+
return GeoGraph(
|
|
1116
|
+
graph=graph,
|
|
1117
|
+
nodes=nodes
|
|
1118
|
+
)
|
|
@@ -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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: scgraph
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|
|
@@ -165,6 +165,8 @@ output = marnet_geograph.get_shortest_path(
|
|
|
165
165
|
get_line_path(output, filename='output.geojson')
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
+
Modify an existing geograph: See the notebook [here](https://colab.research.google.com/github/connor-makowski/scgraph/blob/main/example_making_modifications.ipynb)
|
|
169
|
+
|
|
168
170
|
|
|
169
171
|
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
172
|
|
|
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
|