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
13 changes: 6 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
caching:
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.12", "3.14"]
os: [ ubuntu-latest ]
fail-fast: false
env:
Expand All @@ -40,7 +40,7 @@ jobs:
run: echo "WEEK=$(date +'%V')" >> $GITHUB_ENV

- name: Set up Python ${{ matrix.python-version }}
uses: mamba-org/setup-micromamba@v2
uses: mamba-org/setup-micromamba@v3
with:
create-args: python=${{ matrix.python-version }}
environment-file: environment-dev.yml
Expand All @@ -51,12 +51,11 @@ jobs:
needs: caching
strategy:
matrix:
python-version: ["3.12"]
python-version: ["3.12", "3.14"]
os: [ ubuntu-latest ]
submodule:
- {
name: "Functions",
pytest_args: "tests/test_functions.py" }
- { name: "Main", pytest_args: "tests/test_main.py" }
- { name: "Functions", pytest_args: "tests/test_functions.py" }
fail-fast: false

name: ${{ matrix.submodule.name }} (${{ matrix.python-version }} on ${{ matrix.os }})
Expand All @@ -73,7 +72,7 @@ jobs:
run: echo "WEEK=$(date +'%V')" >> $GITHUB_ENV

- name: Set up Python ${{ matrix.python-version }}
uses: mamba-org/setup-micromamba@v2
uses: mamba-org/setup-micromamba@v3
with:
create-args: python=${{ matrix.python-version }}
environment-file: environment-dev.yml
Expand Down
2 changes: 1 addition & 1 deletion environment-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ channels:
- conda-forge
dependencies:
- python>=3.12
- osmnx>=1.9.4
- osmnx>=2
- tqdm

# additional to the non-dev env
Expand Down
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ channels:
- conda-forge
dependencies:
- python>=3.12
- osmnx>=1.9.4
- osmnx>=2
- tqdm
10 changes: 6 additions & 4 deletions examples/development.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import fixbikenet as fbn

fbn.settings.import_path = '../dataexport/cities/cityexport/'
fbn.constants._BETWEENNESS_RANDOM_NODES = 300
fbn.constants._BETWEENNESS_RANDOM_NODES = 100

gaps = fbn.fixbikenet(
city_query="Frederiksberg Municipality",
export_file_format="geojson",
maxgap = 800,
city_query="Frederiksberg",
radius = 1000,
mingap = 0,
maxgap = 500,
numgaps = 20,
import_files = {
'city_boundary': 'boundaries/frederiksberg_dk.geojson',
'street_network': 'streetbike_networks/frederiksberg_dk.gpkg',
Expand Down
24 changes: 13 additions & 11 deletions fixbikenet/fixbikenet.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ def fixbikenet(
numgaps = 50,
export_data = True,
city_id = None,
export_file_format="geojson",
export_plot=False,
import_files={},
):
Expand All @@ -68,8 +67,6 @@ def fixbikenet(
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_name
city_id : str | None, 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, optional, 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.
export_plot : bool, optional, default False
If set to True, plot will be saved to a file
import_files: dict, default {}
Expand Down Expand Up @@ -100,7 +97,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, radius, mingap, maxgap, export_data, export_file_format, import_files)
import_files = _validate_parameters(city_query, radius, mingap, maxgap, export_data, import_files)
_print_header(city_query)

# Get city boundary
Expand Down Expand Up @@ -212,6 +209,8 @@ def fixbikenet(
gaps_ordered.to_crs(epsg=4326, inplace=True)
edges_pbi_gdf.to_crs(epsg=4326, inplace=True)
edges_gdf.to_crs(epsg=4326, inplace=True)
gaps_ordered = gaps_ordered[['path', 'benefit', 'name', 'edge_list', 'source', 'target', 'ordering', 'length', 'geometry']]

progress_bar.update(1)
progress_bar.close()

Expand All @@ -223,14 +222,14 @@ def fixbikenet(
else:
city_string = city_id
export_data_filename = (
slugify(city_string) + "-fixbikenet-gaps" + "." + export_file_format
slugify(city_string) + "-fixbikenet-gaps" + "." + settings.export_file_format
)

if export_data:
edges_pbi_gdf.drop(["osmid"], axis=1, inplace=True)
edges_gdf.drop(["osmid"], axis=1, inplace=True)
city_boundary.to_crs(epsg=4326, inplace=True)
if export_file_format == "geojson":
if settings.export_file_format == "geojson":
progress_bar = initialize_progress_bar("Exporting data", 4, "file")
gaps_ordered.to_file(settings.export_path + export_data_filename, driver="GeoJSON", RFC7946="YES")
progress_bar.update(1)
Expand All @@ -240,12 +239,15 @@ def fixbikenet(
progress_bar.update(1)
city_boundary.to_file(settings.export_path + slugify(city_string) + "-city_boundary.geojson", driver="GeoJSON", RFC7946="YES")
progress_bar.update(1)
elif export_file_format == "gpkg":
elif settings.export_file_format == "gpkg":
f = settings.export_path + 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.
progress_bar = initialize_progress_bar("Exporting data", 1, "file")
gaps_ordered.to_file(settings.export_path + export_data_filename, driver="GPKG", layer="Identified gaps")
edges_pbi_gdf.to_file(settings.export_path + export_data_filename, driver="GPKG", layer="Existing bike network", append=True)
edges_gdf.to_file(settings.export_path + export_data_filename, driver="GPKG", layer="Existing street network", append=True)
city_boundary.to_file(settings.export_path + export_data_filename, driver="GPKG", layer="City boundary", append=True)
gaps_ordered.to_file(f, driver="GPKG", layer="Identified gaps")
edges_pbi_gdf.to_file(f, driver="GPKG", layer="Existing bike network", append=True)
edges_gdf.to_file(f, driver="GPKG", layer="Existing street network", append=True)
city_boundary.to_file(f, driver="GPKG", layer="City boundary", append=True)
progress_bar.update(1)
progress_bar.close()

Expand Down
5 changes: 2 additions & 3 deletions fixbikenet/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ def _validate_parameters(
mingap,
maxgap,
export_data,
export_file_format,
import_files,
):
""" Check if user parameter input is valid. If not, raise an exception or
Expand Down Expand Up @@ -57,8 +56,6 @@ def _validate_parameters(
raise TypeError("maxgap must be an integer")
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":
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")
Expand All @@ -85,6 +82,8 @@ def _validate_settings():

if type(constants._CRS_CALCULATIONS) is not str:
raise TypeError("constants._CRS_CALCULATIONS must be a string")
if settings.export_file_format != "geojson" and settings.export_file_format != "gpkg":
raise ValueError("settings.export_file_format must be 'geojson' or 'gpkg'")

setting_was_auto = {'crs_calculations': False}
# Ask whether constants._CRS_CALCULATIONS was 'auto'
Expand Down
5 changes: 5 additions & 0 deletions fixbikenet/settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Global settings for `fixbikenet` that can be configured by the user.

export_file_format : {'gpkg', 'geojson'}, default 'gpkg'
File format for the data export, relevant if `export_data` is set to True.
If exporting as geojson, generates extra files for seed points, city
boundary, and existing bicycle network (if relevant). If exporting as gkpg, these are added all in one file as extra layers.
export_path : dict(str)
Paths to results folder to save data.
import_path : str
Expand All @@ -10,6 +14,7 @@
If set to True, suppresses all user feedback. Useful for batch exports.
"""

export_file_format = 'gpkg'
export_path = "./results/"
import_path = "./"

Expand Down
31 changes: 18 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,50 @@ build-backend = "setuptools.build_meta"
name = "fixbikenet"
version = "0.7.0"
authors = [
{ name = "Manuel Knepper" },
{ name = "Anastassia Vybornova" },
{ name = "Michael Szell" },
{name = "Manuel Knepper"},
{name = "Anastassia Vybornova"},
{name = "Michael Szell", email = "michael@szell.net"},
]
maintainers = [
{name = "Michael Szell", email = "michael@szell.net"},
]
maintainers = [{ name = "Manuel Knepper", email = "manuel.knepper@gmx.net" }]
license = "AGPL-3.0-or-later"
readme = "README.md"
description = "BikeNetKit Python package to detect gaps in developed bicycle networks"
keywords = ["Bicycle network planning", "Networks", "OpenStreetMap", "Urban Planning", "Urban Mobility"]
classifiers = [
"Development Status :: 3 - Alpha",
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"Intended Audience :: Other Audience",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Natural Language :: English",
"Topic :: Scientific/Engineering :: GIS",
]
requires-python = ">=3.10"
requires-python = ">=3.12"
dependencies = [
"geopandas>=0.14",
"geopandas>=1",
"matplotlib",
"osmnx>=1.9.4",
"osmnx>=2",
"geojson",
"tqdm",
"pip",
"momepy",
]

[project.urls]
Homepage = "https://bikenetkit.org/"
Documentation = "https://docs.bikenetkit.org/FixBikeNet/"
Repository = "https://github.com/BikeNetKit/FixBikeNet"
Issues = "https://github.com/BikeNetKit/FixBikeNet/issues"
Changelog = "https://docs.bikenetkit.org/FixBikeNet/changelog/"

[project.optional-dependencies]
test = ["pytest"]
doc = ["sphinx", "momepy"]
doc = ["sphinx"]

[tool.ruff.lint.extend-per-file-ignores]
"__init__.py" = ["F401"] # "Imported but unused: happens with packages
Expand Down
Binary file not shown.
8 changes: 8 additions & 0 deletions tests/test_data/frederiksberg_boundary.geojson

Large diffs are not rendered by default.

Binary file not shown.
7 changes: 5 additions & 2 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from fixbikenet import constants
from fixbikenet import settings
from fixbikenet import constants
from fixbikenet import config
import pytest
from networkx.utils.misc import graphs_equal
import geopandas as gpd
from shapely.geometry import Point
import fixbikenet as fbn

from fixbikenet.functions import *

constants._ROUTING_PENALTY = {0: 5, 1: 1}
fbn.constants._ROUTING_PENALTY = {0: 5, 1: 1}
fbn.constants._BETWEENNESS_RANDOM_NODES = 300

@pytest.fixture
def create_test_graph():
Expand Down Expand Up @@ -73,6 +75,7 @@ def create_weighted_graph():
return G

def test_weigh_edges(create_graph_to_weigh, create_weighted_graph):
fbn.constants._ROUTING_PENALTY = {0: 5, 1: 1}
assert graphs_equal(weigh_edges(create_graph_to_weigh), create_weighted_graph)

@pytest.fixture
Expand Down
36 changes: 36 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pytest
import geopandas as gpd
import osmnx as ox
import fixbikenet as fbn
from pandas.testing import assert_frame_equal

fbn.constants._CRS_CALCULATIONS = 'auto'
fbn.constants._BETWEENNESS_RANDOM_NODES = 100


@pytest.fixture
def validation_gdf_frederiksberg():
gdf = gpd.read_file("./tests/test_data/frederiksberg-fixbikenet-gaps.gpkg", layer='Identified gaps')
return gdf[['source', 'target']]


def test_fixbikenet_case_success_offline1(validation_gdf_frederiksberg):
"""Verify that the offline version of fixbikenet works as intended.
"""
fbn.constants._ROUTING_PENALTY = {0: 1.5, 1: 1}
gaps_ordered = fbn.fixbikenet(
city_query="Frederiksberg",
radius = 1000,
mingap = 0,
maxgap = 500,
numgaps = 20,
import_files={
'city_boundary': "./tests/test_data/frederiksberg_boundary.geojson",
'street_network': "./tests/test_data/frederiksberg_streetbike_network.gpkg",
},
)

assert_frame_equal(
validation_gdf_frederiksberg,
gaps_ordered[['source', 'target']]
)
Loading