Last active
April 3, 2023 04:09
-
-
Save d-wasserman/2d50671b37ee46b088e155293399a90c to your computer and use it in GitHub Desktop.
Pandana Network Store from Shapefile
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This GIST provides a template for conveting shapefiles into pandana networks. | |
# Under a MIT Open Source License. https://opensource.org/licenses/MIT | |
# Researchers cite as Wasserman, D. Geopandas to Pandana Network Converter. (2019) GitHub repository, https://gist.github.com/d-wasserman/2d50671b37ee46b088e155293399a90c | |
def get_nodes_and_edges(shp_file,rounding=5): | |
"""Use geopandas to read line shapefile and compile all paths and nodes in a line file based on a rounding tolerance. | |
shp_file:path to polyline file with end to end connectivity | |
rounding: tolerance parameter for coordinate precision""" | |
edges = gpd.read_file(shp_file) | |
edges["from_x"]=edges["geometry"].apply(lambda x:round(x.coords[0][0],rounding)) | |
edges["from_y"]=edges["geometry"].apply(lambda x:round(x.coords[0][1],rounding)) | |
edges["to_x"]=edges["geometry"].apply(lambda x:round(x.coords[-1][0],rounding)) | |
edges["to_y"]=edges["geometry"].apply(lambda x:round(x.coords[-1][1],rounding)) | |
nodes_from = edges[["from_x","from_y"]].rename(index=str,columns={"from_x":"x","from_y":"y"}) | |
nodes_to = edges[["to_x","to_y"]].rename(index=str,columns={"to_x":"x","to_y":"y"}) | |
nodes = pd.concat([nodes_from,nodes_to],axis=0) | |
nodes["xy"] = list(zip(nodes["x"], nodes["y"])) | |
nodes = pd.DataFrame(nodes["xy"].unique(),columns=["xy"]) | |
nodes["x"] = nodes["xy"].apply(lambda x: x[0]) | |
nodes["y"] = nodes["xy"].apply(lambda x: x[1]) | |
nodes = nodes[["x","y"]].copy() | |
return nodes , edges | |
def generate_pandana_store_from_shp(hdf5_path, shp_file, weights=["weight"], oneway=None, overwrite_existing=True, | |
rounding=6): | |
"""Generate a pandana ready HDF5 store using geopandas (gdal required) and pandas. Function assumes shape file has | |
the appropriate topology where each edge has end points that lead to nodes. If using OSM, pre-process with Osmnx. | |
Python 3.5. | |
Parameters | |
---------- | |
hdf5_path: str | |
output path of HDF5 store holding two dataframes ["nodes","edges"] | |
shp_file: str | |
input file that geopandas reads to make a graph based on end to end connectivity | |
weights : lists | |
weights columns transfered to the store edges. Name is maintained. | |
oneway: str | |
series name where oneway streets (edges) are denoted with a 1, 0 denotes twoway. None, assumes twoway edge. | |
overwrite_existing: boolean | |
if true, the existing store is overwritten. | |
rounding:int | |
the number of digits to round line coordinates to get unique nodes (precision) | |
Returns | |
----------- | |
hdf5_path:basestring - path of output hdf5 pandana store.""" | |
if os.path.exists(hdf5_path): | |
if overwrite_existing: | |
print("Overwriting existing store...") | |
os.remove(hdf5_path) | |
else: | |
print("Existing store at path: {0}".format(hdf5_path)) | |
return hdf5_path | |
all_edges_twoway = True | |
oneway_field_list = [] | |
if oneway is not None: | |
all_edges_twoway = False | |
oneway_field_list.append(oneway) | |
print("Reading shapefile with geopandas: {0}...".format(shp_file)) | |
nodes, edges = get_nodes_and_edges(shp_file, rounding) | |
h5store = pd.HDFStore(hdf5_path) | |
print("Establishing node store...") | |
df_nodes = nodes | |
df_nodes["id"] = df_nodes.index.values | |
df_nodes.index.rename("id", True) | |
h5store['nodes'] = df_nodes | |
edge_cnt = len(edges) | |
print("Establishing edge store for {0} edges...".format(edge_cnt)) | |
df_edges = edges[['from_x', 'from_y', 'to_x', 'to_y'] + weights + oneway_field_list].copy() | |
print("Transferring nodeids to edges...") | |
df_edges = pd.merge(df_edges, df_nodes, how='left', left_on=['from_x', 'from_y'], right_on=['x', 'y']) | |
df_edges = pd.merge(df_edges, df_nodes, how='left', left_on=['to_x', 'to_y'], right_on=['x', 'y'], | |
suffixes=('_from', '_to')) | |
# nodeids are duplicated on from the joined nodes, joined first to from, suffix to on next set | |
df_edges.rename(columns={'id_from': 'from', 'id_to': 'to'}, inplace=True) | |
df_edges = df_edges[['from', 'to'] + weights + oneway_field_list] | |
if all_edges_twoway: | |
print("""Note: Edges are duplicated in this step, do not use the 'twoway' setting in the pandana network if using this | |
function""") | |
twoway_edges = df_edges.copy() | |
twoway_to = twoway_edges["to"].copy() | |
twoway_edges["to"] = twoway_edges["from"] | |
twoway_edges["from"] = twoway_to | |
df_edges = pd.concat([df_edges, twoway_edges]) | |
else: | |
print("Setting up edges based on oneway field...") | |
twoway_edges = df_edges[df_edges[oneway] == 0].copy() | |
twoway_to = twoway_edges["to"].copy() | |
twoway_edges["to"] = twoway_edges["from"] | |
twoway_edges["from"] = twoway_to | |
df_edges = pd.concat([df_edges, twoway_edges]) | |
h5store['edges'] = df_edges | |
h5store.close() | |
print("Graph store construction complete...") | |
return hdf5_path |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This makes sense! Thanks for sharing. I think I need to make a version just to take osmnx edges, but that should be straight forward.