Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion linkbikenet/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,4 +393,18 @@ def calculate_network_statistics(H):
for _, _, data in H.subgraph(largest_component).edges(data=True)
)

return total_length, largest_length
return total_length, largest_length

def mark_joined_component(H, component, step):
"""Mark components when they join the largest connected component
Parameters
----------
H: networkx.Graph
undirected simple graph representing the street network with weighted edges
component: networkx.Graph
undirected simple graph representing a component of the existing bike network
step: int
the step at which the component is connected to the largest connected component
"""
for u, v in H.subgraph(component).edges():
H[u][v]["lcc_step"] = step
87 changes: 73 additions & 14 deletions linkbikenet/linkbikenet.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

def linkbikenet(
city_query,
connection_strategy = "largest",
connection_strategy = "largest_to_second",
proj_crs = "3857",
export_data = True,
city_id = None,
Expand All @@ -23,7 +23,7 @@ def linkbikenet(
city_query : str
Search string for the city that the analysis should be performed on. This is the query used to fetch the data from nominatim.
connection_strategy : str, default="largest
strategy to use for connecting between components. Default is "largest", other options are "largest_closest" and "closest"
strategy to use for connecting between components. Default is "largest_to_second", other options are "largest_to_closest" and "closest_components"
proj_crs : str, default '3857'
coordinate reference system that is used to project osm data. Default is '3857' (WGS 84 / Pseudo-Mercator)
export_data : bool, optional, default True
Expand All @@ -50,8 +50,8 @@ def linkbikenet(
raise TypeError("city_name must be a string")
if type(proj_crs) != str:
raise TypeError("proj_crs must be a string")
if connection_strategy != "largest" and connection_strategy != "largest_closest" and connection_strategy != "closest":
raise TypeError("connection_strategy must be 'largest', 'largest-closest' or 'closest'")
if connection_strategy != "largest_to_second" and connection_strategy != "largest_to_closest" and connection_strategy != "closest_components":
raise TypeError("connection_strategy must be 'largest_to_second', 'largest_to_closest' or 'closest_components'")
if type(export_data) is not bool:
raise TypeError("export_data must be a boolean")
if export_file_format != "geojson" and export_file_format != "gpkg":
Expand Down Expand Up @@ -110,34 +110,87 @@ def linkbikenet(
wcc = [H.subgraph(c).copy() for c in sorted(nx.connected_components(H), key=lambda c: sum(
[l[-1] for l in H.subgraph(c).copy().edges.data('length')]), reverse=True)]

# Nodes belonging to the original largest component
main_component = set(wcc[0])

# Mark all edges in the original largest component as step 0
for u, v in H.subgraph(main_component).edges():
H[u][v]["lcc_step"] = 0

to_iterate = len(wcc) - 1
closest_pairs = []
step = 1

# check which strategy was chosen and execute the corresponding algorithm
print("Calculating links...")
if connection_strategy == "largest":
if connection_strategy == "largest_to_second":
for i in range(to_iterate):
wcc = [H.subgraph(c).copy() for c in sorted(nx.connected_components(H), key=lambda c: sum(
[l[-1] for l in H.subgraph(c).copy().edges.data('length')]), reverse=True)]
pair = pair_between_largest_components(wcc)
# Determine which components contain u and v
component_u = next(c for c in wcc if pair[0] in c)
component_v = next(c for c in wcc if pair[1] in c)
u_in_main = pair[0] in main_component
v_in_main = pair[1] in main_component
closest_pairs.append(pair)
H.add_edge(pair[0], pair[1], length=0)

elif connection_strategy == "largest_closest":
H.add_edge(pair[0], pair[1], length=0, lcc_step=None)
if u_in_main and not v_in_main:
mark_joined_component(H, component_v, step)
main_component.update(component_v)
H[pair[0]][pair[1]]["lcc_step"] = step

elif v_in_main and not u_in_main:
mark_joined_component(H, component_u, step)
main_component.update(component_u)
H[pair[0]][pair[1]]["lcc_step"] = step
step += 1

elif connection_strategy == "largest_to_closest":
for i in range(to_iterate):
wcc = [H.subgraph(c).copy() for c in sorted(nx.connected_components(H), key=lambda c: sum(
[l[-1] for l in H.subgraph(c).copy().edges.data('length')]), reverse=True)]
pair = pair_between_largest_and_closest_components(wcc)
# Determine which components contain u and v
component_u = next(c for c in wcc if pair[0] in c)
component_v = next(c for c in wcc if pair[1] in c)
u_in_main = pair[0] in main_component
v_in_main = pair[1] in main_component
closest_pairs.append(pair)
H.add_edge(pair[0], pair[1], length=0)

elif connection_strategy == "closest":
H.add_edge(pair[0], pair[1], length=0, lcc_step=None)
if u_in_main and not v_in_main:
mark_joined_component(H, component_v, step)
main_component.update(component_v)
H[pair[0]][pair[1]]["lcc_step"] = step

elif v_in_main and not u_in_main:
mark_joined_component(H, component_u, step)
main_component.update(component_u)
H[pair[0]][pair[1]]["lcc_step"] = step
step += 1

elif connection_strategy == "closest_components":
for i in range(to_iterate):
wcc = [H.subgraph(c).copy() for c in sorted(nx.connected_components(H), key=lambda c: sum(
[l[-1] for l in H.subgraph(c).copy().edges.data('length')]), reverse=True)]
pair = pair_between_closest_components(wcc)
# Determine which components contain u and v
component_u = next(c for c in wcc if pair[0] in c)
component_v = next(c for c in wcc if pair[1] in c)
u_in_main = pair[0] in main_component
v_in_main = pair[1] in main_component
closest_pairs.append(pair)
H.add_edge(pair[0], pair[1], length=0)
H.add_edge(pair[0], pair[1], length=0, lcc_step=None)
if u_in_main and not v_in_main:
mark_joined_component(H, component_v, step)
main_component.update(component_v)
H[pair[0]][pair[1]]["lcc_step"] = step

elif v_in_main and not u_in_main:
mark_joined_component(H, component_u, step)
main_component.update(component_u)
H[pair[0]][pair[1]]["lcc_step"] = step
step += 1

# find paths between node pairs so we can generate geometries
paths = []
Expand All @@ -148,6 +201,9 @@ def linkbikenet(
continue
paths.append(path)

H.remove_edges_from(closest_pairs)
edges_pbi_gdf = graph_edges_to_gdf(H)

edges_gdf = graph_edges_to_gdf(G)
df = pd.DataFrame()
df['nodelist'] = paths
Expand Down Expand Up @@ -175,7 +231,10 @@ def linkbikenet(
gdf['network_length'] = network_lengths
gdf['lcc_length'] = lcc_lengths

edges_pbi_gdf = edges_gdf[edges_gdf["pbi"] == 1]
gdf['lcc_share'] = gdf['lcc_length'] / gdf['network_length']
gdf['lcc_gain'] = gdf['lcc_length'].diff().fillna(0)

#edges_pbi_gdf = edges_gdf[edges_gdf["pbi"] == 1]

# Back to unprojected (potentially). No more calculations after here.
gdf.to_crs(epsg=4326, inplace=True)
Expand All @@ -200,7 +259,7 @@ def linkbikenet(
city_boundary.to_crs(epsg=4326, inplace=True)
if export_file_format == "geojson":
gdf.to_file(settings.export_path + export_data_filename, driver="GeoJSON", RFC7946="YES")
edges_pbi_gdf.to_file(settings.export_path + slugify(city_string) + "-existing_bike_network.geojson", driver="GeoJSON", RFC7946="YES")
edges_pbi_gdf.to_file(settings.export_path + slugify(city_string) + connection_strategy + "-existing_bike_network.geojson", driver="GeoJSON", RFC7946="YES")
city_boundary.to_file(settings.export_path + slugify(city_string) + "-city_boundary.geojson", driver="GeoJSON", RFC7946="YES")
elif export_file_format == "gpkg":
gdf.to_file(settings.export_path + export_data_filename, driver="GPKG", layer="Identified links")
Expand Down
Loading