scgraph 2.1.1__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.1/scgraph.egg-info → scgraph-2.2.0}/PKG-INFO +3 -1
- {scgraph-2.1.1 → scgraph-2.2.0}/README.md +2 -0
- {scgraph-2.1.1 → scgraph-2.2.0}/pyproject.toml +2 -2
- {scgraph-2.1.1 → scgraph-2.2.0}/scgraph/core.py +218 -11
- {scgraph-2.1.1 → scgraph-2.2.0}/scgraph/utils.py +2 -2
- {scgraph-2.1.1 → scgraph-2.2.0/scgraph.egg-info}/PKG-INFO +3 -1
- {scgraph-2.1.1 → scgraph-2.2.0}/setup.cfg +1 -1
- {scgraph-2.1.1 → scgraph-2.2.0}/LICENSE +0 -0
- {scgraph-2.1.1 → scgraph-2.2.0}/scgraph/__init__.py +0 -0
- {scgraph-2.1.1 → scgraph-2.2.0}/scgraph/geographs/__init__.py +0 -0
- {scgraph-2.1.1 → scgraph-2.2.0}/scgraph/geographs/marnet.py +0 -0
- {scgraph-2.1.1 → scgraph-2.2.0}/scgraph/geographs/north_america_rail.py +0 -0
- {scgraph-2.1.1 → scgraph-2.2.0}/scgraph/geographs/oak_ridge_maritime.py +0 -0
- {scgraph-2.1.1 → scgraph-2.2.0}/scgraph/geographs/us_freeway.py +0 -0
- {scgraph-2.1.1 → scgraph-2.2.0}/scgraph.egg-info/SOURCES.txt +0 -0
- {scgraph-2.1.1 → scgraph-2.2.0}/scgraph.egg-info/dependency_links.txt +0 -0
- {scgraph-2.1.1 → 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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.black]
|
|
2
2
|
line-length = 80
|
|
3
|
-
target-version = ['
|
|
3
|
+
target-version = ['py312']
|
|
4
4
|
exclude = '/.*(migrations|__pycache__|geographs).*/'
|
|
5
5
|
|
|
6
6
|
[tool.setuptools]
|
|
@@ -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
|
|
@@ -727,7 +724,7 @@ class GeoGraph:
|
|
|
727
724
|
if len(nodes) == 0:
|
|
728
725
|
# Default to all if the lat_lon_bound fails to find any nodes
|
|
729
726
|
return self.get_node_distances(
|
|
730
|
-
node=
|
|
727
|
+
node=node,
|
|
731
728
|
circuity=circuity,
|
|
732
729
|
lat_lon_bound=180,
|
|
733
730
|
node_addition_type=node_addition_type,
|
|
@@ -843,7 +840,6 @@ class GeoGraph:
|
|
|
843
840
|
lat_lon_bound, (int, float)
|
|
844
841
|
), "Lat_lon_bound must be a number"
|
|
845
842
|
assert lat_lon_bound > 0, "Lat_lon_bound must be greater than 0"
|
|
846
|
-
|
|
847
843
|
node = [node["latitude"], node["longitude"]]
|
|
848
844
|
# Get the distances to all other nodes
|
|
849
845
|
distances = self.get_node_distances(
|
|
@@ -878,13 +874,13 @@ class GeoGraph:
|
|
|
878
874
|
|
|
879
875
|
"""
|
|
880
876
|
features = []
|
|
881
|
-
for origin_idx, destinations in self.graph
|
|
877
|
+
for origin_idx, destinations in enumerate(self.graph):
|
|
882
878
|
for destination_idx, distance in destinations.items():
|
|
883
879
|
# Create an undirected graph for geojson purposes
|
|
884
880
|
if origin_idx > destination_idx:
|
|
885
881
|
continue
|
|
886
|
-
origin = self.nodes
|
|
887
|
-
destination = self.nodes
|
|
882
|
+
origin = self.nodes[origin_idx]
|
|
883
|
+
destination = self.nodes[destination_idx]
|
|
888
884
|
features.append(
|
|
889
885
|
{
|
|
890
886
|
"type": "Feature",
|
|
@@ -896,10 +892,10 @@ class GeoGraph:
|
|
|
896
892
|
"geometry": {
|
|
897
893
|
"type": "LineString",
|
|
898
894
|
"coordinates": [
|
|
899
|
-
[origin[
|
|
895
|
+
[origin[1], origin[0]],
|
|
900
896
|
[
|
|
901
|
-
destination[
|
|
902
|
-
destination[
|
|
897
|
+
destination[1],
|
|
898
|
+
destination[0],
|
|
903
899
|
],
|
|
904
900
|
],
|
|
905
901
|
},
|
|
@@ -909,3 +905,214 @@ class GeoGraph:
|
|
|
909
905
|
out_dict = {"type": "FeatureCollection", "features": features}
|
|
910
906
|
with open(filename, "w") as f:
|
|
911
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
|