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
14 changes: 11 additions & 3 deletions examples/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

import growbikenet as gbn

edges_ranked = gbn.growbikenet("Oelde",
seed_point_type='file',
import_files={'street_network':"./tests/test_data/oelde_street_network.gpkg", 'seed_points':"./tests/test_data/oelde_seed_points.gpkg"})
gbn.settings.import_data_impact = 9
gbn.settings.import_data_trip_point_balance = 0.4 # alpha
edges_ranked = gbn.growbikenet("Turin",
export_data = True,
import_files={
'street_network':"./tests/test_data/turin_street_network.gpkg",
'point_data':"./tests/test_data/turin_crashes.gpkg",
'trip_data':"./tests/test_data/turin_trips.csv",
},
)
print(len(edges_ranked))
33 changes: 17 additions & 16 deletions growbikenet/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@

PBI_CUSTOM_FILTER : list[str]
Custom filter for protected bicycle infrastructure (pbi)
EXISTING_NETWORK_MINIMUM_COMPONENT_LENGTH : int
Minimum length a bike network component needs to have for seed points to snap, in meters
GRID_SPACING_TRIANGULATE : int
Grid spacing in meters for grid triangulation that ensures that any point in the city is always within buffer distance b=500m of the network (if seed points snap perfectly).
GRID_SPACING_QUADRANGULATE : int
Grid spacing in meters for quadrangulation that ensures that any point in the city is always within buffer distance b=500m of the network (if seed points snap perfectly).
GRID_SPACING_TRIANGLE : int
Grid spacing in meters for triangle grid that ensures that any point in the city is always within buffer distance b=500m of the network (if seed points snap perfectly).

_PRESET_TAGS : dict
Pre-defined tags to select tags as seed points
_PHI_LIMITS : list[float]
Two orientation order limits between street networks with:
1) negligible grid elements, 2) some grid elements, 3) grid.
We aimed to use the tercile limits from the paper [3]_ (Fig 2), but the values here are lower for unknown reasons, also with the unweighted version. Also, it was aimed to have Barcelona in the grid category. For these reasons, the limits were lowered.
EXISTING_NETWORK_MINIMUM_COMPONENT_LENGTH : int
Minimum length a bike network component needs to have for seed points to snap, in meters
_SEED_POINT_SNAP_DISTANCE_FACTOR : float
Factor to multiply seed_point_grid_spacing with, to determine auto value of seed_point_snap_distance
_EXISTING_NETWORK_SPACING_FACTOR : float
Factor to multiply seed_point_grid_spacing with, to determine auto value of existing_network_spacing
GRID_SPACING_TRIANGULATE : int
Grid spacing in meters for grid triangulation that ensures that any point in the city is always within buffer distance b=500m of the network (if seed points snap perfectly).
GRID_SPACING_QUADRANGULATE : int
Grid spacing in meters for quadrangulation that ensures that any point in the city is always within buffer distance b=500m of the network (if seed points snap perfectly).
GRID_SPACING_TRIANGLE : int
Grid spacing in meters for triangle grid that ensures that any point in the city is always within buffer distance b=500m of the network (if seed points snap perfectly).
_BUFFER_SEED_POINTS_EXNW_FACTOR : float
Factor to multiply existing_network_spacing with, to determine which previously determined seed points (grid or rail) to drop that are too close to the extra existing network points
_BEARING_BINS : int
Expand All @@ -40,10 +41,16 @@
]
# Populate ox.settings.useful_tags_way to make application of custom filter possible
import osmnx as ox
for custom_tag in ["cycleway", "bicycle", "cycleway:right", "cycleway:left", "cycleway:both", "cyclestreet"]:
for custom_tag in ["highway", "cycleway", "bicycle", "cycleway:right", "cycleway:left", "cycleway:both", "cyclestreet"]: # This list should contain all tags used in any custom filters
if custom_tag not in ox.settings.useful_tags_way:
ox.settings.useful_tags_way.extend(custom_tag)

EXISTING_NETWORK_MINIMUM_COMPONENT_LENGTH = 100

GRID_SPACING_TRIANGULATE = 1707 # a=2b/(2-sqrt(2))
GRID_SPACING_QUADRANGULATE = 1000 # a=2b
GRID_SPACING_TRIANGLE = 1154 # h/2=b=a*sqrt(3)/4 -> a=4b/sqrt(3)

_PRESET_TAGS = {
"rail": {"railway": ["station", "halt"]},
"school": {"amenity": ["kindergarten", "school", "college", "university"]},
Expand All @@ -52,16 +59,10 @@

_PHI_LIMITS = [0.02, 0.08] # Tercile limits in the paper: 0.033, 0.161

EXISTING_NETWORK_MINIMUM_COMPONENT_LENGTH = 100

_SEED_POINT_SNAP_DISTANCE_FACTOR = 0.25

_EXISTING_NETWORK_SPACING_FACTOR = 0.5

GRID_SPACING_TRIANGULATE = 1707 # a=2b/(2-sqrt(2))
GRID_SPACING_QUADRANGULATE = 1000 # a=2b
GRID_SPACING_TRIANGLE = 1154 # h/2=b=a*sqrt(3)/4 -> a=4b/sqrt(3)

_BUFFER_SEED_POINTS_EXNW_FACTOR = 0.5

_BEARING_BINS = 72
_BEARING_BINS = 72
61 changes: 51 additions & 10 deletions growbikenet/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def _resolve_auto_parameters(
phi,
import_files,
):
"""Resolve auto parameters and parameter inconsistencies
"""Resolve auto parameters, their inconsistencies, and settings

Parameters
----------
Expand Down Expand Up @@ -232,29 +232,35 @@ def _resolve_auto_parameters(
if existing_network_spacing == 'auto':
existing_network_spacing = int(np.ceil(seed_point_grid_spacing*constants._EXISTING_NETWORK_SPACING_FACTOR))

# Set import data balance to 0 or 1 if only one data set is imported
if import_files['point_data'] is not None and import_files['trip_data'] is None:
settings.import_data_trip_point_balance = 0
elif import_files['trip_data'] is not None and import_files['point_data'] is None:
settings.import_data_trip_point_balance = 1

return seed_point_type, seed_point_grid_spacing, seed_point_linking, existing_network_spacing


def add_trip_data_to_net(trips, A, crs_projected, matching_distance=500):
def add_trip_data_to_net(trips, A, crs_projected=settings.crs_projected, matching_distance=settings.import_trip_data_snap_distance):
"""Match trip data to network edges

First, match origin and destination points given in trips to the nodes. Only consider trips where both origins and nodes are matched within matching_distance. Then, for each trip, find the shortest path over the edges from matched origin node to matched destination node, and add 1 (or optionally "num" if column provided in trips) to the affected edges.

Parameters
----------
trips : pandas DataFrame
A df of unprojected origin-destination coordinates (columns: o_lat, o_lon, d_lat, d_lon), with each row encoding a trip. Optional with a column "num" containing an integer. This could be (number of) trip events. If "num" column is not provided, assumes 1 per trip.
A df of unprojected origin-destination coordinates (columns: o_lat, o_lon, d_lat, d_lon), with each row encoding a trip, in unprojected crs EPSG:4326. Optional with a column "num" containing an integer. This could be (number of) trip events. If "num" column is not provided, assumes 1 per trip.
A: networkx.graph
Graph created from triangulation edge list
crs_projected : str
Coordinate reference system that is used to project spatial data.
matching_distance : int
Matching distance in meters
Matching distance in meters. Set via settings.import_trip_data_snap_distance.

Returns
-------
graph_with_data : networkx.graph
The same graph created from triagulation edges list, but with a new edge attribute "num_trips" populated with the summed up "num" values of all trips where both origins and destinations could be matched to the closest network nodes within matching_distance.
The same graph created from triangulation edges list, but with a new edge attribute "num_trips" populated with the summed up "num" values of all trips where both origins and destinations could be matched to the closest network nodes within matching_distance.
"""

graph_with_data = A.copy()
Expand Down Expand Up @@ -291,7 +297,6 @@ def add_trip_data_to_net(trips, A, crs_projected, matching_distance=500):
if o_node == d_node or o_distance > matching_distance or d_distance > matching_distance:
continue


# Get shortest path between the snapped orgin and destination nodes
path = nx.shortest_path(graph_with_data, source=o_node, target=d_node, weight="distance")
path_edges = list(zip(path[:-1], path[1:]))
Expand All @@ -312,7 +317,7 @@ def add_trip_data_to_net(trips, A, crs_projected, matching_distance=500):
return graph_with_data


def add_point_data_to_net(points, edges, crs_projected, matching_distance=500):
def add_point_data_to_net(points, edges, crs_projected=settings.crs_projected, matching_distance=settings.import_point_data_snap_distance):
"""Match point data to network edges

Parameters
Expand All @@ -321,10 +326,10 @@ def add_point_data_to_net(points, edges, crs_projected, matching_distance=500):
A gdf of unprojected point geometries, optional having a column "num" containing an integer. This could be (number of) point events like crashes or citizen feedback to improve bike infrastructure. If "num" column is not provided, assumes 1 per point.
edges : geopandas.geodataframe.GeoDataFrame
A gdf of projected spatial network edges. This is the routed network of seed points.
matching_distance : int
Matching distance in meters
crs_projected : str
Coordinate reference system that is used to project osm data.
matching_distance : int
Matching distance in meters. Set via settings.import_point_data_snap_distance.

Returns
-------
Expand Down Expand Up @@ -1329,4 +1334,40 @@ def orientation_order(g_undir):
Hg = 1.386
Hmax = 3.584
phi = 1 - ((Hw-Hg)/(Hmax-Hg))**2
return phi
return phi

def _get_weighted_distances(B, num_types):
"""Get weighted distances by edge attribute num_types

The calculation follows [1]_, only without +1 in the numerator and with a small epsilon to prevent division by zero.

Parameters
----------
B : networkx.classes.multigraph.MultiGraph
The routed, grown bicycle network graph, where edges have the attribute num_types. The numerical attribute "distance" must exist for all edges.
num_types : str
Name of the attribute to weight the distances

Returns
-------
dist_weighted_by_types_dict : dict
Dictionary where keys are the edges (tuples of node ids), and values are the weighted distances following [1]_

References
----------
.. [1] P. Folco, L. Gauvin, M. Tizzoni, M. Szell, "Data-driven micromobility network planning for demand and safety", Environment and planning B: Urban analytics and city science 50(8), 2087-2102 (2023)
"""

num_types_dict = nx.get_edge_attributes(B, num_types)
dist_dict = nx.get_edge_attributes(B, "distance")
num_types_per_km_dict = {}

for k,d in dist_dict.items():
num_types_per_km_dict[k] = 1000*num_types_dict[k]/d
max_n = max(num_types_per_km_dict.values())+1e-10

dist_weighted_by_types_dict = {}
for k,d in dist_dict.items():
dist_weighted_by_types_dict[k] = d/(1+settings.import_data_impact*(num_types_per_km_dict[k]/max_n))

return dist_weighted_by_types_dict
Loading
Loading