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
24 changes: 12 additions & 12 deletions docs/source/usage_01_gap_identification.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"tags": []
},
"source": [
"**Parameters covered**: `numgaps`, `settings.radius`, `mingap`, `maxgap`"
"**Parameters covered**: `num_gaps`, `settings.radius`, `min_gap_length`, `max_gap_length`"
]
},
{
Expand Down Expand Up @@ -90,7 +90,7 @@
"tags": []
},
"source": [
"FixBikeNet is based on the idea that any shortest path between two nodes on an existing bicycle network, where the entire path goes through the non-bicycle network, is a potential gap. Out of these potentially millions of gaps in a city, FixBikeNet identifies those that are most important to close in terms of providing the most cycled meters (per gap meter). It ranks the identified gaps and selects the top `numgaps` - the first parameter for running FixBikeNet. The ranking happens through betweenness centrality, a proxy for cyclist flow. This measure assumes that cyclists move from each intersection to each other intersection in the city.\n",
"FixBikeNet is based on the idea that any shortest path between two nodes on an existing bicycle network, where the entire path goes through the non-bicycle network, is a potential gap. Out of these potentially millions of gaps in a city, FixBikeNet identifies those that are most important to close in terms of providing the most cycled meters (per gap meter). It ranks the identified gaps and selects the top `num_gaps` - the first parameter for running FixBikeNet. The ranking happens through betweenness centrality, a proxy for cyclist flow. This measure assumes that cyclists move from each intersection to each other intersection in the city.\n",
"\n",
"The assumption of such a global all-to-all flow is relaxed in FixBikeNet to only consider local betweenness flows within a `settings.radius` (that is loaded from `fbn.settings`). This relaxation is done for several reasons: \n",
"1) Typical cyclists are usually not traversing the whole city\n",
Expand Down Expand Up @@ -168,7 +168,7 @@
"source": [
"gaps_ordered = fbn.fixbikenet(\n",
" \"Frederiksberg municipality\",\n",
" numgaps=10,\n",
" num_gaps=10,\n",
")"
]
},
Expand Down Expand Up @@ -1203,7 +1203,7 @@
"tags": []
},
"source": [
"The default minimum gap length is not zero, because often very short gaps are either data artefacts of undershoots (pieces did not get connected exactly), or due to intersections or parallel lanes. However, it straightforward to change the minimum gap length to, say 0, via `mingap=0`."
"The default minimum gap length is not zero, because often very short gaps are either data artefacts of undershoots (pieces did not get connected exactly), or due to intersections or parallel lanes. However, it straightforward to change the minimum gap length to, say 0, via `min_gap_length=0`."
]
},
{
Expand Down Expand Up @@ -1265,10 +1265,10 @@
}
],
"source": [
"gaps_ordered_mingap0 = fbn.fixbikenet(\n",
"gaps_ordered_min_gap_length0 = fbn.fixbikenet(\n",
" \"Frederiksberg municipality\",\n",
" numgaps=10,\n",
" mingap=0,\n",
" num_gaps=10,\n",
" min_gap_length=0,\n",
")"
]
},
Expand All @@ -1284,7 +1284,7 @@
"tags": []
},
"source": [
"Let us add the outcome with `mingap=0` in orange to see the difference:"
"Let us add the outcome with `min_gap_length=0` in orange to see the difference:"
]
},
{
Expand Down Expand Up @@ -1787,7 +1787,7 @@
" },\n",
" overlays : {\n",
" "Existing bike network" : geo_json_d7a520d45602743edf243e6abafe845e,\n",
" "Identified gaps, mingap=0" : geo_json_647b515abac5bc1b389f265c33e24cba,\n",
" "Identified gaps, min_gap_length=0" : geo_json_647b515abac5bc1b389f265c33e24cba,\n",
" "Identified gaps" : geo_json_932fb79874d1b4d847dcb9efc3fb09ab,\n",
" },\n",
" };\n",
Expand Down Expand Up @@ -1823,10 +1823,10 @@
" style_kwds={\"weight\": 2, \"color\": \"#9999cc\"},\n",
" name=\"Existing bike network\",\n",
")\n",
"viz = gaps_ordered_mingap0.explore(\n",
"viz = gaps_ordered_min_gap_length0.explore(\n",
" m=viz, \n",
" style_kwds={\"weight\": 6, \"color\": \"#f19730\"},\n",
" name=\"Identified gaps, mingap=0\",\n",
" name=\"Identified gaps, min_gap_length=0\",\n",
")\n",
"viz = gaps_ordered.explore(\n",
" m=viz, \n",
Expand All @@ -1850,7 +1850,7 @@
"tags": []
},
"source": [
"It is also possible to consider longer gaps, for example, up to 1500m via `maxgap=1500`. This is computationally expensive and takes much longer to calculate. In the case of Frederiksberg, around 3 times as many potential gaps have to be considered, increasing the runtime by a factor of around 9. The result is however identical, which is why we are not running it here. The default maximum gap is already quite high, catching most long gaps. It can be useful to lower the `maxgap` value though, to making search faster and limiting it to only shorter gaps."
"It is also possible to consider longer gaps, for example, up to 1500m via `max_gap_length=1500`. This is computationally expensive and takes much longer to calculate. In the case of Frederiksberg, around 3 times as many potential gaps have to be considered, increasing the runtime by a factor of around 9. The result is however identical, which is why we are not running it here. The default maximum gap is already quite high, catching most long gaps. It can be useful to lower the `max_gap_length` value though, to making search faster and limiting it to only shorter gaps."
]
}
],
Expand Down
4 changes: 2 additions & 2 deletions docs/source/usage_04_data_imports_for_prioritizing_gaps.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@
"source": [
"gaps_ordered = fbn.fixbikenet(\n",
" \"Copenhagen municipality\",\n",
" numgaps = 20,\n",
" num_gaps = 20,\n",
" import_files = {\n",
" 'city_boundary': 'copenhagen_boundary.geojson',\n",
" },\n",
Expand Down Expand Up @@ -623,7 +623,7 @@
"source": [
"gaps_ordered_with_survey = fbn.fixbikenet(\n",
" \"Copenhagen municipality\",\n",
" numgaps = 20,\n",
" num_gaps = 20,\n",
" import_files = {\n",
" 'city_boundary': 'copenhagen_boundary.geojson',\n",
" 'point_data': 'copenhagen_citizen_input.geojson',\n",
Expand Down
6 changes: 3 additions & 3 deletions examples/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@

fbn.fixbikenet(
city_query="Frederiksberg Municipality",
mingap = 0,
maxgap = 100,
numgaps = 20,
min_gap_length = 0,
max_gap_length = 100,
num_gaps = 20,
import_files={
'city_boundary': "./tests/test_data/frederiksberg_boundary.geojson",
# 'street_network': "./tests/test_data/frederiksberg_streetbike_network.gpkg",
Expand Down
5 changes: 3 additions & 2 deletions fixbikenet/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
Number of random nodes to select for local betweenness calculations, for
computational reasons.
_CLUSTER_GAPS_PER_FINAL_GAP : int, default 15
Factor to multiply `numgaps` with, giving the number of gaps to consider for declustering.
Factor to multiply `num_gaps` with, giving the number of gaps to consider
for declustering.
_CRS_CALCULATIONS : str, default 'auto'
EPSG code of the coordinate reference system that is used to project OSM
data for calculations. This has to be a distance-preserving projected CRS,
Expand All @@ -28,6 +29,6 @@
_BETWEENNESS_RANDOM_NODES = 300
_CLUSTER_GAPS_PER_FINAL_GAP = 15
_CRS_CALCULATIONS = 'auto'
_PROGRESS_BAR_DESC_LENGTH = 24
_PROGRESS_BAR_DESC_LENGTH = 23
_PROGRESS_BAR_LENGTH = 23
_ROUTING_PENALTY = {0: 1.5, 1: 1}
34 changes: 17 additions & 17 deletions fixbikenet/fixbikenet.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,28 @@

def fixbikenet(
city_query,
mingap = 20,
maxgap = 800,
numgaps = 50,
min_gap_length = 20,
max_gap_length = 800,
num_gaps = 50,
export_data = True,
city_id = None,
export_plot=False,
import_files={},
):
"""Find gaps in bicycle networks and return the `numgaps` that are the most
"""Find gaps in bicycle networks and return the `num_gaps` that are the most
important to fill.

Parameters
----------
city_query : str
Name of the city that the analysis should be performed on.
mingap : int, default 20
min_gap_length : int, default 20
Minimum distance between node pairs to be considered as a potential
gap, in meters.
maxgap : int, default 800
max_gap_length : int, default 800
Maximum distance between node pairs to be considered as a potential
gap, in meters.
numgaps : int, default 50
num_gaps : int, default 50
Number of gaps to find.
export_data : bool, default True
If set to True, data will be saved to a file. The filename is
Expand Down Expand Up @@ -111,7 +111,7 @@ def fixbikenet(
Returns
-------
gaps_ordered : geopandas.geodataframe.GeoDataFrame
Ordered geodataframe with the `numgaps` most important gaps to fill.
Ordered geodataframe with the `num_gaps` most important gaps to fill.

References
----------
Expand All @@ -122,7 +122,7 @@ def fixbikenet(
starttime = time.time()
np.random.seed(settings.random_seed) # Set random number generator seed for reproducibility
setting_was_auto = _validate_settings()
import_files = _validate_parameters(city_query, mingap, maxgap, export_data, import_files)
import_files = _validate_parameters(city_query, min_gap_length, max_gap_length, export_data, import_files)
_print_header(city_query)

### Import data files
Expand Down Expand Up @@ -188,10 +188,10 @@ def fixbikenet(
contact_nodes = find_contact_nodes(G)

# finding potential gaps in network
potential_gaps = find_potential_gaps(contact_nodes, nodes_gdf, maxgap)
potential_gaps = find_potential_gaps(contact_nodes, nodes_gdf, max_gap_length)

# add routing for gaps in network
found_gaps, found_gaps_nsp = find_actual_gaps(G, potential_gaps, mingap)
found_gaps, found_gaps_nsp = find_actual_gaps(G, potential_gaps, min_gap_length)

# calculating local betweenness score dependent on radius
ebc = compute_local_betweenness_centrality(G, nodes_gdf)
Expand Down Expand Up @@ -226,17 +226,17 @@ def fixbikenet(
)
df = df.sort_values(by="benefit", ascending=False).reset_index(drop=True)

# Only keep the numgaps*constants._CLUSTER_GAPS_PER_FINAL_GAP most
# Only keep the num_gaps*constants._CLUSTER_GAPS_PER_FINAL_GAP most
# important gaps before declustering. If there are fewer than
# numgaps*constants._CLUSTER_GAPS_PER_FINAL_GAP gaps keep only those
if df.shape[0] > numgaps*constants._CLUSTER_GAPS_PER_FINAL_GAP:
df = df.iloc[:numgaps*constants._CLUSTER_GAPS_PER_FINAL_GAP]
# num_gaps*constants._CLUSTER_GAPS_PER_FINAL_GAP gaps keep only those
if df.shape[0] > num_gaps*constants._CLUSTER_GAPS_PER_FINAL_GAP:
df = df.iloc[:num_gaps*constants._CLUSTER_GAPS_PER_FINAL_GAP]

#decluster edges
gap_df = gap_declustering(df, G, ebc, contact_nodes, mingap)
gap_df = gap_declustering(df, G, ebc, contact_nodes, min_gap_length)

progress_bar = initialize_progress_bar("Postprocess data", 5)
gap_df = gap_df.nlargest(numgaps, "benefit").reset_index(drop=True)
gap_df = gap_df.nlargest(num_gaps, "benefit").reset_index(drop=True)
progress_bar.update(1)

# compute list of all edges that are part of each gap, where each edge is u,v
Expand Down
32 changes: 16 additions & 16 deletions fixbikenet/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@

def _validate_parameters(
city_query,
mingap,
maxgap,
min_gap_length,
max_gap_length,
export_data,
import_files,
):
Expand All @@ -48,10 +48,10 @@ def _validate_parameters(
"""
if type(city_query) != str:
raise TypeError("city_query must be a string")
if type(mingap) != int:
raise TypeError("mingap must be an integer")
if type(maxgap) != int:
raise TypeError("maxgap must be an integer")
if type(min_gap_length) != int:
raise TypeError("min_gap_length must be an integer")
if type(max_gap_length) != int:
raise TypeError("max_gap_length must be an integer")
if type(export_data) is not bool:
raise TypeError("export_data must be a boolean")

Expand Down Expand Up @@ -400,17 +400,17 @@ def find_contact_nodes(G):
contact_nodes.append(node)
return contact_nodes

def find_potential_gaps(contact_nodes, nodes_gdf, maxgap):
def find_potential_gaps(contact_nodes, nodes_gdf, max_gap_length):
"""Find potential gaps in protected bicycle network, corresponding to two
contact nodes that are within maxgap euclidean distance of each other.
contact nodes that are within max_gap_length euclidean distance of each other.

Parameters
----------
contact_nodes : list
list of all nodes that fulfill criteria to be a contact node.
nodes_gdf : geopandas.GeoDataFrame
All nodes in street network.
maxgap : int
max_gap_length : int
User defined maximal euclidean distance between two contact nodes.

Returns
Expand All @@ -423,7 +423,7 @@ def find_potential_gaps(contact_nodes, nodes_gdf, maxgap):
contact_nodes_gdf = nodes_gdf[nodes_gdf['osmid'].isin(contact_nodes)]

for node in contact_nodes:
node_buffer = contact_nodes_gdf.loc[node, "geometry"].buffer(maxgap)
node_buffer = contact_nodes_gdf.loc[node, "geometry"].buffer(max_gap_length)
q = contact_nodes_gdf.sindex.query(node_buffer, predicate="intersects")
neighbours = list(contact_nodes_gdf.iloc[q].index)
neighbours.remove(node)
Expand All @@ -435,7 +435,7 @@ def find_potential_gaps(contact_nodes, nodes_gdf, maxgap):
potential_gaps = list(set(potential_gaps))
return potential_gaps

def find_actual_gaps(G, potential_gaps, mingap):
def find_actual_gaps(G, potential_gaps, min_gap_length):
"""Determine which potential gaps are actual gaps by finding paths between
all contact nodes and only keeping the gaps that have no protected bike
infrastructure.
Expand All @@ -447,7 +447,7 @@ def find_actual_gaps(G, potential_gaps, mingap):
edges.
potential_gaps : list
All unique potential gaps in protected bicycle network.
mingap : int
min_gap_length : int
Minimum distance between node pairs to be considered as a potential
gap, in meters.

Expand Down Expand Up @@ -492,7 +492,7 @@ def find_actual_gaps(G, potential_gaps, mingap):
break

gaplength = nx.shortest_path_length(G, u, v, weight="length")
if valid and gaplength >= mingap:
if valid and gaplength >= min_gap_length:
found_gaps.append((u, v))
found_gaps_nsp.append(nodelist)

Expand Down Expand Up @@ -868,7 +868,7 @@ def compute_benefit_metric(comp, node_path, ebc):
B = sum(lengths * ebcs) / sum(lengths)
return B

def gap_declustering(gaps_df, G, ebc, contact_nodes, mingap):
def gap_declustering(gaps_df, G, ebc, contact_nodes, min_gap_length):
"""

Parameters
Expand All @@ -882,7 +882,7 @@ def gap_declustering(gaps_df, G, ebc, contact_nodes, mingap):
local betweenness centrality values for all edges in network.
contact_nodes : list
List of contact nodes.
mingap : int
min_gap_length : int
Minimum distance between node pairs to be considered as a potential
gap, in meters.

Expand Down Expand Up @@ -949,7 +949,7 @@ def gap_declustering(gaps_df, G, ebc, contact_nodes, mingap):
target=target,
weight="length"
)
if node_path and gaplength >= mingap:
if node_path and gaplength >= min_gap_length:
candidate_paths.append(node_path)
except nx.NetworkXNoPath:
continue
Expand Down
6 changes: 3 additions & 3 deletions tests/test_fixbikenet.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ def test_fixbikenet_case_success_offline1(validation_gdf_frederiksberg):
fbn.settings.radius = 1000
gaps_ordered = fbn.fixbikenet(
city_query="Frederiksberg",
mingap = 0,
maxgap = 100,
numgaps = 20,
min_gap_length = 0,
max_gap_length = 100,
num_gaps = 20,
import_files={
'city_boundary': "./tests/test_data/frederiksberg_boundary.geojson",
'street_network': "./tests/test_data/frederiksberg_streetbike_network.gpkg",
Expand Down
10 changes: 5 additions & 5 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ def create_test_contact_nodes():
return contact_nodes

@pytest.fixture
def create_maxgap():
maxgap = 50
return maxgap
def create_max_gap_length():
max_gap_length = 50
return max_gap_length

@pytest.fixture
def create_test_nodes_gdf():
Expand All @@ -105,8 +105,8 @@ def create_validation_gaps():
gaps = [(1,3)]
return gaps

def test_find_potential_gaps(create_test_contact_nodes, create_maxgap, create_test_nodes_gdf, create_validation_gaps):
assert find_potential_gaps(create_test_contact_nodes,create_test_nodes_gdf, create_maxgap) == create_validation_gaps
def test_find_potential_gaps(create_test_contact_nodes, create_max_gap_length, create_test_nodes_gdf, create_validation_gaps):
assert find_potential_gaps(create_test_contact_nodes,create_test_nodes_gdf, create_max_gap_length) == create_validation_gaps

@pytest.fixture
def create_graph_for_routing():
Expand Down
Loading