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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
os: [ ubuntu-latest ]
submodule:
- { name: "linkbikenet", pytest_args: "tests/test_linkbikenet.py" }
- { name: "functions", pytest_args: "tests/test_functions.py" }
- { name: "utils", pytest_args: "tests/test_utils.py" }
fail-fast: false

name: ${{ matrix.submodule.name }} (${{ matrix.python-version }} on ${{ matrix.os }})
Expand Down
6 changes: 3 additions & 3 deletions docs/source/reference_developer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ linkbikenet.linkbikenet
.. automodule:: linkbikenet.linkbikenet
:members:

linkbikenet.functions
---------------------
linkbikenet.utils
-----------------

.. automodule:: linkbikenet.functions
.. automodule:: linkbikenet.utils
:members:
:private-members:

Expand Down
4 changes: 1 addition & 3 deletions docs/source/reference_user.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@ linkbikenet.linkbikenet

.. automodule:: linkbikenet.linkbikenet
:members:
:no-index:

linkbikenet.settings
--------------------

.. automodule:: linkbikenet.settings
:members:
:no-index:
:members:
1 change: 1 addition & 0 deletions environment-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dependencies:
- scipy
- geojson
- tqdm
- bikenetlib

# additional to the non-dev env
- ipykernel
Expand Down
3 changes: 2 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ dependencies:
- osmnx>=2
- scipy
- geojson
- tqdm
- tqdm
- bikenetlib
13 changes: 8 additions & 5 deletions examples/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@

lbn.settings.import_path = '/Users/mszell/Tresorit/bikenetkitshare/'
city_id = "frederiksberg_dk"

lbn.settings.import_path = './tests/test_data/'
lbn.constants.TOP_CLOSEST_COMPONENTS = 5
lbn.settings.export_file_format = "gpkg"

edges_ordered = lbn.linkbikenet(
linked_components = lbn.linkbikenet(
"Frederiksberg",
connection_strategy='largest_to_closest',
link_strategy='closest_components',
import_files={
'city_boundary': 'boundaries/'+city_id+'.geojson',
'street_network': 'streetbike_networks/'+city_id+'.gpkg',
'city_boundary': "frederiksberg_boundary.geojson",
'street_network': "frederiksberg_streetbike_network.gpkg",
},
export_file_format = "gpkg",
export_data = True,
)

# print(edges_ordered['network_length'])
2 changes: 1 addition & 1 deletion linkbikenet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from . import linkbikenet
from . import osm
from . import settings
from . import functions
from . import utils

__author__ = "LinkBikeNet contributors"
__author_email__ = "michael@szell.net"
Expand Down
10 changes: 8 additions & 2 deletions linkbikenet/constants.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
"""Global constants for `linkbikenet` that can be tweaked during development, but
should not be changed later by the user. Especially technical or internal
"""Global constants for `linkbikenet` that can be tweaked during development,
but should not be changed later by the user. Especially technical or internal
constants start with an underscore.

TOP_CLOSEST_COMPONENTS : int, default 5
The number of top closest components to try to route to, for finding the
one with shortest path distance. The higher, the more accurate, but also
more computations.
_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,
so '3857' (WGS 84 / Pseudo-Mercator) would be wrong! Option 'auto' selects
the best UTM via `estimate_utm_crs()`.
_PROGRESS_BAR_DESC_LENGTH : int, default 21
Character length of tqdm progress bar descriptions. This is the space given
to text like "Importing network data ", which is at the maximum of 23
Expand All @@ -15,5 +20,6 @@
"""

TOP_CLOSEST_COMPONENTS = 5
_CRS_CALCULATIONS = 'auto'
_PROGRESS_BAR_DESC_LENGTH = 23
_PROGRESS_BAR_LENGTH = 23
120 changes: 51 additions & 69 deletions linkbikenet/linkbikenet.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
"""Main procedure for running `linkbikenet`."""

# imports
from . import settings
from . import constants
from . import constants, osm, settings
import os
import osmnx as ox
import networkx as nx
import pandas as pd
import geopandas as gpd
import warnings
from collections import defaultdict
from tqdm import tqdm
import time
pd.set_option('display.max_columns', None) # for debugging
import sys # noqa: F401, use sys.exit() for debugging

from linkbikenet.functions import (
from linkbikenet.utils import (
_print_header,
_print_footer,
_reset_auto_settings,
_resolve_crs_calculations,
_validate_parameters,
_validate_settings,
calculate_network_statistics,
create_gdf_with_geoms,
find_edges_to_drop,
Expand All @@ -32,46 +36,41 @@
import_bike_network,
assign_pbi_to_edges,
mark_joined_component,
resolve_crs_calculations,
shortest_path_components,
shortest_path_components_from_candidates,
slugify,
)

def linkbikenet(
city_query,
connection_strategy = "largest_to_closest",
export_data = True,
city_id = None,
export_file_format = "geojson",
link_strategy='largest_to_closest',
export_data=True,
city_id=None,
import_files={},
):
"""Create links between components of bicycle networks in cities. How
components are connected depends on the chosen connection strategy.
components are connected depends on the chosen link strategy.

Parameters
----------
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_to_second", other options are "largest_to_closest" and "closest_components".
export_data : bool, optional, default True
link_strategy : {'largest_to_closest', 'largest_to_second', 'closest_components'}
Strategy for linking components. Default is 'largest_to_closest', other
options are 'largest_to_second' and 'closest_components'.
export_data : bool, default True
If set to True, data will be saved to a file. The filename is
[slug].gpkg, where slug is a string id made out of city_query. Existing
files are overwritten.
``[slug]-linkbikenet-[link_strategy].[settings.export_file_format]``,
where ``[slug]`` is a string id made out of `city_query` (or `city_id`
if set). Existing files are overwritten.
city_id : None or str, default None
If set, the slugified city_id is used in the filename of the data
export. For example, a city_id "Athens" will slugify into "athens" in
filenames. If set to None, the slugified city_query is used in the
filename of the data export. It is useful to set a city_id for cities
where the city_query is not the city name, for example to set for a
city_query "Municipality of Athens" the city_id to "Athens".
export_file_format : str, default "geojson"
File format for the data export, relevant if export_data set to True.
Default "geojson", also possible "gpkg". If exporting as geojson,
generates extra files for street network and city boundary. If
exporting as gkpg, these are added all in one file as extra layers.
import_files: dict, default {}
The following key:value entries can be set:

Expand Down Expand Up @@ -102,27 +101,12 @@ def linkbikenet(
linked_components : geopandas.GeoDataFrame
Geodataframe with the proposed links, ordered after strategy chosen.
"""

# Setup
starttime = time.time()

# check if user input is valid
if type(city_query) != str:
raise TypeError("city_name must be a string")
if connection_strategy not in ['largest_to_second', 'largest_to_closest', '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 city_id is not None:
if type(city_id) is not str:
raise TypeError("city_id must be a string")
if export_file_format != "geojson" and export_file_format != "gpkg":
raise ValueError("export_file_format must be 'geojson' or 'gpkg'")
if type(import_files) is not dict:
raise TypeError("import_files must be a dictionary")
# Prepare special case import_files. Turn it into a defaultdict where missing keys are None.
import_files = defaultdict(lambda: None, import_files)

_print_header(city_query, connection_strategy)
setting_was_auto = _validate_settings()
import_files = _validate_parameters(city_query,link_strategy,export_data,city_id,import_files)
_print_header(city_query, link_strategy)

# Get city boundary
if import_files['city_boundary']:
Expand All @@ -135,7 +119,7 @@ def linkbikenet(
progress_bar = initialize_progress_bar("Importing network data", 1, "network")
h = import_bike_network(import_files['bike_network'])
nodes_h = ox.graph_to_gdfs(h, nodes=True, edges=False, node_geometry=True)
proj_crs = resolve_crs_calculations(nodes_h, settings.crs_projected)
proj_crs = _resolve_crs_calculations(nodes_h)
h = ox.project_graph(h, to_crs=proj_crs)
h = nx.Graph(h)

Expand All @@ -147,9 +131,6 @@ def linkbikenet(
### downloading and preprocessing data from OSM
progress_bar = initialize_progress_bar("Downloading OSM data", 1, "network")

ox.settings.useful_tags_way = ["highway", "cycleway", "cycleway:right", "cycleway:left", "cycleway:both",
"cyclestreet"]

# fetch street network from OSM
g = ox.graph_from_place(
city_query, network_type='all_public', simplify=False, retain_all=True
Expand All @@ -160,12 +141,12 @@ def linkbikenet(
progress_bar = initialize_progress_bar("Processing network", 3)
g = ox.simplify_graph(
g,
edge_attrs_differ=['cycleway', 'highway', 'cycleway:right', 'cycleway:left', 'cycleway:both'],
edge_attrs_differ=['cycleway', 'highway', 'cycleway:right', 'cycleway:left', 'cycleway:both'], # To do: Align with osm module
)

# project graph for distance calculations
nodes_g = ox.graph_to_gdfs(g, nodes=True, edges=False, node_geometry=True)
proj_crs = resolve_crs_calculations(nodes_g, settings.crs_projected)
proj_crs = _resolve_crs_calculations(nodes_g)
g = ox.project_graph(g, to_crs=proj_crs)

progress_bar.update(1)
Expand All @@ -175,13 +156,6 @@ def linkbikenet(

progress_bar.update(1)

# progress_bar = initialize_progress_bar("Drop parallel edges")
# # finding parallel edges and dropping them
# edges_to_drop = find_edges_to_drop(g)
# g.remove_edges_from(edges_to_drop)
# progress_bar.update(1)
# progress_bar.close()

# Capital-G: the Graph() object we will be working with from now on
G = nx.Graph(g)

Expand All @@ -200,7 +174,7 @@ def linkbikenet(
progress_bar.update(1)
progress_bar.close()

H, paths_all, pathedges_all, num_comps_added = link_components(connection_strategy, H, G)
H, paths_all, pathedges_all, num_comps_added = link_components(link_strategy, H, G)

progress_bar = initialize_progress_bar("Postprocessing data", 3)
edges_pbi_gdf = graph_edges_to_gdf(H)
Expand Down Expand Up @@ -286,16 +260,14 @@ def linkbikenet(
edges_pbi_gdf['length'] = edges_pbi_gdf['length'].astype(int)

linked_components['ordering'] = linked_components.index


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

# Back to unprojected (potentially). No more calculations after here.
linked_components.to_crs(epsg=4326, inplace=True)
linked_components.to_crs(epsg=settings.crs_result, inplace=True)
if import_files['bike_network'] is not None:
edges_pbi_gdf.set_crs(epsg=4326, allow_override=True, inplace=True)
edges_pbi_gdf.set_crs(epsg=settings.crs_result, allow_override=True, inplace=True)
else:
edges_pbi_gdf.to_crs(epsg=4326, inplace=True)
edges_pbi_gdf.to_crs(epsg=settings.crs_result, inplace=True)

# Generate export data filename
if export_data:
Expand All @@ -305,7 +277,7 @@ def linkbikenet(
else:
city_string = city_id
export_data_filename = (
slugify(city_string) + "-linkbikenet-" + connection_strategy + "." + export_file_format
slugify(city_string) + "-linkbikenet-" + link_strategy + "." + settings.export_file_format
)

if export_data:
Expand All @@ -318,16 +290,25 @@ def linkbikenet(
if p not in keepedgedata:
del edges_pbi_gdf[p]

city_boundary.to_crs(epsg=4326, inplace=True)
if export_file_format == "geojson":
progress_bar = initialize_progress_bar("Exporting data", 3, "file")
linked_components.to_file(settings.export_path['results'] + export_data_filename, driver="GeoJSON", RFC7946="YES")
progress_bar.update(1)
edges_pbi_gdf.to_file(settings.export_path['results'] + slugify(city_string) + "-linkbikenet-" + connection_strategy + "-existing_bike_network.geojson", driver="GeoJSON", RFC7946="YES")
progress_bar.update(1)
city_boundary.to_file(settings.export_path['results'] + slugify(city_string) + "-linkbikenet-city_boundary.geojson", driver="GeoJSON", RFC7946="YES")
progress_bar.update(1)
elif export_file_format == "gpkg":
city_boundary.to_crs(epsg=settings.crs_result, inplace=True)
if settings.export_file_format == "geojson":
if settings.crs_result == '4326': # Export with RFC7946="YES"
progress_bar = initialize_progress_bar("Exporting data", 3, "file")
linked_components.to_file(settings.export_path['results'] + export_data_filename, driver="GeoJSON", RFC7946="YES")
progress_bar.update(1)
edges_pbi_gdf.to_file(settings.export_path['results'] + slugify(city_string) + "-linkbikenet-" + link_strategy + "-existing_bike_network.geojson", driver="GeoJSON", RFC7946="YES")
progress_bar.update(1)
city_boundary.to_file(settings.export_path['results'] + slugify(city_string) + "-linkbikenet-city_boundary.geojson", driver="GeoJSON", RFC7946="YES")
progress_bar.update(1)
else:
progress_bar = initialize_progress_bar("Exporting data", 3, "file")
linked_components.to_file(settings.export_path['results'] + export_data_filename, driver="GeoJSON")
progress_bar.update(1)
edges_pbi_gdf.to_file(settings.export_path['results'] + slugify(city_string) + "-linkbikenet-" + link_strategy + "-existing_bike_network.geojson", driver="GeoJSON")
progress_bar.update(1)
city_boundary.to_file(settings.export_path['results'] + slugify(city_string) + "-linkbikenet-city_boundary.geojson", driver="GeoJSON")
progress_bar.update(1)
elif settings.export_file_format == "gpkg":
f = settings.export_path['results'] + export_data_filename
if os.path.exists(f):
os.remove(f) # mode="w" does not work for to_file with gpkg. It always appends. Therefore, existing file needs to be deleted.
Expand All @@ -341,6 +322,7 @@ def linkbikenet(
progress_bar.close()

# Cleanup, finalize
_reset_auto_settings(setting_was_auto)
endtime = time.time()
_print_footer(export_data, endtime, starttime)

Expand Down
Loading
Loading