diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 59317d9cd..fdef420a3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -2,7 +2,6 @@ name: Report a bug description: Report a bug in OSMnx's code labels: bug body: - - type: markdown attributes: value: "Thanks for using OSMnx!" diff --git a/.github/ISSUE_TEMPLATE/feature_proposal.yml b/.github/ISSUE_TEMPLATE/feature_proposal.yml index eb7aad422..5e0d91a38 100644 --- a/.github/ISSUE_TEMPLATE/feature_proposal.yml +++ b/.github/ISSUE_TEMPLATE/feature_proposal.yml @@ -2,7 +2,6 @@ name: Propose a feature description: Propose an enhancement before opening a pull request to add it labels: enh body: - - type: markdown attributes: value: "Thanks for using OSMnx!" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8ea8cc1b5..2f10f0100 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,6 +4,6 @@ Before you proceed, review the contributing guidelines in the CONTRIBUTING.md fi In this pull request, please include: - - a reference to related issue(s) - - a description of the changes proposed in the pull request - - an example code snippet illustrating usage of the new functionality +- a reference to related issue(s) +- a description of the changes proposed in the pull request +- an example code snippet illustrating usage of the new functionality diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a7398d0f..0c07cc7eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,18 +6,16 @@ on: pull_request: branches: [main] schedule: - - cron: '0 6 * * 1' # Every Monday at 06:00 UTC + - cron: "0 6 * * 1" # Every Monday at 06:00 UTC jobs: - build: - name: Python ${{ matrix.python-version }} ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ["3.8", "3.9", "3.10", "3.11"] os: [ubuntu-latest] defaults: @@ -25,7 +23,6 @@ jobs: shell: bash -elo pipefail {0} steps: - - name: Checkout repo uses: actions/checkout@v3 with: diff --git a/.github/workflows/test-minimal.yml b/.github/workflows/test-minimal.yml index ef8b01cbb..866fef65e 100644 --- a/.github/workflows/test-minimal.yml +++ b/.github/workflows/test-minimal.yml @@ -2,13 +2,11 @@ name: Test minimal versions on: schedule: - - cron: '0 7 * * 1' # Every Monday at 07:00 UTC + - cron: "0 7 * * 1" # Every Monday at 07:00 UTC workflow_dispatch: jobs: - build: - name: ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: @@ -21,7 +19,6 @@ jobs: shell: bash -elo pipefail {0} steps: - - name: Checkout repo uses: actions/checkout@v3 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4aa5b3912..795b1a8e8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: "v4.4.0" hooks: - id: check-added-large-files args: [--maxkb=50] @@ -23,13 +23,18 @@ repos: args: [--branch, main] - id: trailing-whitespace + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.0.0" + hooks: + - id: prettier + types_or: [markdown, yaml] + - repo: https://github.com/psf/black - rev: 23.3.0 + rev: "23.7.0" hooks: - id: black - args: ["--check"] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.277 + rev: "v0.0.278" hooks: - id: ruff diff --git a/CHANGELOG.md b/CHANGELOG.md index 58287c5df..a3f3f8650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,507 +2,507 @@ ## Unreleased - - adopt NEP 29 policy for minimum required Python and NumPy versions +- adopt NEP 29 policy for minimum required Python and NumPy versions ## 1.5.1 (2023-07-08) - - improve memory efficiency during graph creation - - improve log messaging - - add version number to XML generator attribute in save_graph_xml - - warn user if loading a .osm XML file generated by OSMnx itself - - add style keyword argument to citation function +- improve memory efficiency during graph creation +- improve log messaging +- add version number to XML generator attribute in save_graph_xml +- warn user if loading a .osm XML file generated by OSMnx itself +- add style keyword argument to citation function ## 1.5.0 (2023-06-28) - - fix bug in save_graph_xml due to roundabout ways - - fix GeoPandas future warning - - make API key properly optional in elevation.add_node_elevations_google function - - rename geometries module as features module and deprecate geometries module - - remove private \_polygon_features module and move its data to features module - - make the internal downloader module private - - deprecate interpolate parameter in distance.nearest_edges function - - move save_graph_xml function to io module with deprecation warning in osm_xml module - - migrate from setup.py, setup.cfg, and requirements.txt to pyproject.toml - - pin optional dependencies to minimum required versions - - expand and reorganize the documentation +- fix bug in save_graph_xml due to roundabout ways +- fix GeoPandas future warning +- make API key properly optional in elevation.add_node_elevations_google function +- rename geometries module as features module and deprecate geometries module +- remove private \_polygon_features module and move its data to features module +- make the internal downloader module private +- deprecate interpolate parameter in distance.nearest_edges function +- move save_graph_xml function to io module with deprecation warning in osm_xml module +- migrate from setup.py, setup.cfg, and requirements.txt to pyproject.toml +- pin optional dependencies to minimum required versions +- expand and reorganize the documentation ## 1.4.0 (2023-06-11) - - verify edge weight attribute values before solving shortest paths - - provide consistent error when no data elements are returned from Overpass - - add route_to_gdf function to utils_graph module to return a GeoDataFrame of the edges in a path - - deprecate the get_route_edge_attributes function in favor of the new route_to_gdf function - - deprecate folium module in favor of using geopandas.GeoDataFrame.explore directly - - deprecate precision parameter in bearing, distance, elevation, and speed modules' functions - - deprecate utils_geo.round_geometry_coords function - - move plot_orientation function from bearing module to plot module - - make matplotlib an optional dependency required only for the plot module - - drop pyproj package dependency +- verify edge weight attribute values before solving shortest paths +- provide consistent error when no data elements are returned from Overpass +- add route_to_gdf function to utils_graph module to return a GeoDataFrame of the edges in a path +- deprecate the get_route_edge_attributes function in favor of the new route_to_gdf function +- deprecate folium module in favor of using geopandas.GeoDataFrame.explore directly +- deprecate precision parameter in bearing, distance, elevation, and speed modules' functions +- deprecate utils_geo.round_geometry_coords function +- move plot_orientation function from bearing module to plot module +- make matplotlib an optional dependency required only for the plot module +- drop pyproj package dependency ## 1.3.1.post0 (2023-05-26) - - restore Python 3.8 compatibility +- restore Python 3.8 compatibility ## 1.3.1 (2023-05-24) - - improve DNS resolution when using proxies or on networks blocking DNS-over-HTTPS - - improve processing of per-lane values when adding edge speeds - - improve file writing in save_graph_xml function - - ensure node coordinates are non-null and convertible to float in the add_edge_lengths function - - ignore ways tagged highway=no or highway=razed in built-in filters - - do not assume an edge with key=0 exists between each node pair when simplifying graph - - drop dateutil package dependency +- improve DNS resolution when using proxies or on networks blocking DNS-over-HTTPS +- improve processing of per-lane values when adding edge speeds +- improve file writing in save_graph_xml function +- ensure node coordinates are non-null and convertible to float in the add_edge_lengths function +- ignore ways tagged highway=no or highway=razed in built-in filters +- do not assume an edge with key=0 exists between each node pair when simplifying graph +- drop dateutil package dependency ## 1.3.0 (2023-01-01) - - fully support Shapely 2.0 and drop support for Shapely 1.x - - drop RTree package dependency - - much faster nearest edges search using STRTree index - - allow using alternative Google Maps compatible elevation APIs, such as Open Topo Data - - optionally track merged_edges as a new edge attribute in simplify_graph function +- fully support Shapely 2.0 and drop support for Shapely 1.x +- drop RTree package dependency +- much faster nearest edges search using STRTree index +- allow using alternative Google Maps compatible elevation APIs, such as Open Topo Data +- optionally track merged_edges as a new edge attribute in simplify_graph function ## 1.2.3 (2022-12-14) - - fix bug that added unsimplified edge geometry attributes when projecting - - hard code Google DNS IP address - - resolve matplotlib deprecation warning - - deprecate save_graph_shapefile function +- fix bug that added unsimplified edge geometry attributes when projecting +- hard code Google DNS IP address +- resolve matplotlib deprecation warning +- deprecate save_graph_shapefile function ## 1.2.2 (2022-08-05) - - fix compatibility with rasterio 1.3 - - fix API version when saving OSM XML - - resolve shapely deprecation warning +- fix compatibility with rasterio 1.3 +- fix API version when saving OSM XML +- resolve shapely deprecation warning ## 1.2.1 (2022-06-16) - - fix rate limit checking and pausing on newest versions of Overpass API - - allow add_edge_lengths function to be run on a subset of edges - - resolve pandas deprecation warning +- fix rate limit checking and pausing on newest versions of Overpass API +- allow add_edge_lengths function to be run on a subset of edges +- resolve pandas deprecation warning ## 1.2.0 (2022-05-23) - - add ability to load GraphML string data to the load_graphml function - - add "reversed" edge attribute to support node-order-dependent edge attributes - - add new edge_color and edge_linewidth arguments to plot_footprints function - - fix nearest_edges function selecting arbitrary edge when bounding boxes overlap - - fix get_digraph function's parallel edge handling - - fix pandas and geopandas version compatibility - - fix log output appearing in Jupyter notebooks on Unix-like systems - - remove old functions and arguments previously deprecated in v1.1 - - deprecate utils.config function in favor of using settings module directly +- add ability to load GraphML string data to the load_graphml function +- add "reversed" edge attribute to support node-order-dependent edge attributes +- add new edge_color and edge_linewidth arguments to plot_footprints function +- fix nearest_edges function selecting arbitrary edge when bounding boxes overlap +- fix get_digraph function's parallel edge handling +- fix pandas and geopandas version compatibility +- fix log output appearing in Jupyter notebooks on Unix-like systems +- remove old functions and arguments previously deprecated in v1.1 +- deprecate utils.config function in favor of using settings module directly ## 1.1.2 (2021-11-17) - - fix geocoding when no geojson is returned - - fix graph simplification to properly handle travel_time edge attributes - - fix streets per node not being calculated when clean_periphery=False - - allow user-defined aggregation function when imputing missing edge speeds - - allow user to configure requests package keyword arguments when connecting to APIs - - faster graph projection by calculating UTM zone number with a computationally cheaper method - - improve efficiency of quadrat-based geometry cutting - - fall back on google dns resolution when necessary if using a proxy - - move count_streets_per_node function to stats module - - resolve shapely and geopandas deprecation warnings +- fix geocoding when no geojson is returned +- fix graph simplification to properly handle travel_time edge attributes +- fix streets per node not being calculated when clean_periphery=False +- allow user-defined aggregation function when imputing missing edge speeds +- allow user to configure requests package keyword arguments when connecting to APIs +- faster graph projection by calculating UTM zone number with a computationally cheaper method +- improve efficiency of quadrat-based geometry cutting +- fall back on google dns resolution when necessary if using a proxy +- move count_streets_per_node function to stats module +- resolve shapely and geopandas deprecation warnings ## 1.1.1 (2021-05-19) - - fix overpass status endpoint checks with explicit IP address resolution - - fix slot management on local overpass instances by optionally disabling rate limiting - - parallelize shortest_path calculation for multiple origins/destinations +- fix overpass status endpoint checks with explicit IP address resolution +- fix slot management on local overpass instances by optionally disabling rate limiting +- parallelize shortest_path calculation for multiple origins/destinations ## 1.1.0 (2021-05-01) - - add graph-constrained spatial sampling function - - add add_node_elevations_raster function to add node elevations from local raster file(s) - - add add_node_elevations_google function and deprecate old add_node_elevations function - - add faster streamlined nearest_nodes and nearest_edges functions to distance module - - deprecate old get_nearest_node, get_nearest_nodes, get_nearest_edge, and get_nearest_edges - - add utils_geo.interpolate_points function and deprecate redistribute_vertices in favor of it - - add vectorized calculate_bearing function and deprecate get_bearing in favor of it - - expose individual street network stats functions in stats module - - deprecate the extended_stats function in stats module - - add network orientation and entropy stats functions to bearing module - - add plot_orientation function to bearing module to polar histograms of graph edge bearings - - add route_linewidths parameter to plot_graph_routes function - - handle relations of type "boundary" in geometries module - - multi-index GeoDataFrames returned from geometries module by element type and osmid - - ensure all nodes have integer IDs after graph intersection consolidation - - vectorize add_edge_lengths, add_edge_grades, and add_edge_bearings functions - - improve save_graph_xml speed - - improve geocoder module error messages - - improve handling of node geometry when converting graph to/from GeoDataFrames - - fix network_type filters allowing ways tagged "bus_guideway" - - fix handling of boolean type conversion in load_graphml - - fix truncate_graph_dist retaining unreachable nodes - - fix bug in consolidate_intersections when pygeos is installed - - move add_edge_lengths function from utils_graph to distance module - - remove descartes dependency in line with geopandas +- add graph-constrained spatial sampling function +- add add_node_elevations_raster function to add node elevations from local raster file(s) +- add add_node_elevations_google function and deprecate old add_node_elevations function +- add faster streamlined nearest_nodes and nearest_edges functions to distance module +- deprecate old get_nearest_node, get_nearest_nodes, get_nearest_edge, and get_nearest_edges +- add utils_geo.interpolate_points function and deprecate redistribute_vertices in favor of it +- add vectorized calculate_bearing function and deprecate get_bearing in favor of it +- expose individual street network stats functions in stats module +- deprecate the extended_stats function in stats module +- add network orientation and entropy stats functions to bearing module +- add plot_orientation function to bearing module to polar histograms of graph edge bearings +- add route_linewidths parameter to plot_graph_routes function +- handle relations of type "boundary" in geometries module +- multi-index GeoDataFrames returned from geometries module by element type and osmid +- ensure all nodes have integer IDs after graph intersection consolidation +- vectorize add_edge_lengths, add_edge_grades, and add_edge_bearings functions +- improve save_graph_xml speed +- improve geocoder module error messages +- improve handling of node geometry when converting graph to/from GeoDataFrames +- fix network_type filters allowing ways tagged "bus_guideway" +- fix handling of boolean type conversion in load_graphml +- fix truncate_graph_dist retaining unreachable nodes +- fix bug in consolidate_intersections when pygeos is installed +- move add_edge_lengths function from utils_graph to distance module +- remove descartes dependency in line with geopandas ## 1.0.1 (2021-01-13) - - fix network_type filters allowing ways tagged "planned" - - fix "drive" network_type allowing some alleys - - fix intersection consolidation for compatibility with v1.0 node ids/indexing - - fix python 3.6 compatibility - - deprecate folium polyline styling arguments +- fix network_type filters allowing ways tagged "planned" +- fix "drive" network_type allowing some alleys +- fix intersection consolidation for compatibility with v1.0 node ids/indexing +- fix python 3.6 compatibility +- deprecate folium polyline styling arguments ## 1.0.0 (2021-01-01) - - set use_cache=True by default - - add ability to query a place by OSM ID in geocoder.geocode_to_gdf function - - add optional setting for download/cache-only mode - - replace md5 with sha1 for cache filename hashing - - replace streets_per_node graph attribute with equivalent street_count node attribute - - remove redundant osmid node attribute - - make graph_to_gdfs multi-index the edges GeoDataFrame by u, v, key - - refactor consolidate_intersections function for better speed and efficiency - - refactor count_streets_per_node function for better speed and efficiency - - refactor folium module for better speed and efficiency - - refactor get_undirected functionality for better speed and efficiency - - extract all private/internal .osm XML functionality into new osm_xml module - - deprecate io.save_graph_xml with warning (function moved to osm_xml module) - - remove internal \_is_simplified function - - remove deprecated pois module - - remove deprecated footprints module - - remove deprecated utils_graph.induce_subgraph function - - remove deprecated node_type parameter from io.load_graphml function +- set use_cache=True by default +- add ability to query a place by OSM ID in geocoder.geocode_to_gdf function +- add optional setting for download/cache-only mode +- replace md5 with sha1 for cache filename hashing +- replace streets_per_node graph attribute with equivalent street_count node attribute +- remove redundant osmid node attribute +- make graph_to_gdfs multi-index the edges GeoDataFrame by u, v, key +- refactor consolidate_intersections function for better speed and efficiency +- refactor count_streets_per_node function for better speed and efficiency +- refactor folium module for better speed and efficiency +- refactor get_undirected functionality for better speed and efficiency +- extract all private/internal .osm XML functionality into new osm_xml module +- deprecate io.save_graph_xml with warning (function moved to osm_xml module) +- remove internal \_is_simplified function +- remove deprecated pois module +- remove deprecated footprints module +- remove deprecated utils_graph.induce_subgraph function +- remove deprecated node_type parameter from io.load_graphml function ## 0.16.2 (2020-11-17) - - improve graph_from_gdfs speed and efficiency - - improve plot_route_folium speed and efficiency - - fix remove_isolated_nodes function mutating the passed-in graph - - fix gephi compatibility in save_graphml - - add customizable node/edge attribute data type arguments to load_graphml - - deprecate old node_type argument in load_graphml - - expose bidirectional_network_types via config function +- improve graph_from_gdfs speed and efficiency +- improve plot_route_folium speed and efficiency +- fix remove_isolated_nodes function mutating the passed-in graph +- fix gephi compatibility in save_graphml +- add customizable node/edge attribute data type arguments to load_graphml +- deprecate old node_type argument in load_graphml +- expose bidirectional_network_types via config function ## 0.16.1 (2020-10-05) - - fix handling graphs with no intersections in consolidate_intersections - - fix consolidate_intersections returning GeoSeries without CRS attribute - - fix response caching to save only when status code is 200 - - fix elevation module's grade absolute value calculation when grade is null - - move shortest path functions from utils_graph module to distance module +- fix handling graphs with no intersections in consolidate_intersections +- fix consolidate_intersections returning GeoSeries without CRS attribute +- fix response caching to save only when status code is 200 +- fix elevation module's grade absolute value calculation when grade is null +- move shortest path functions from utils_graph module to distance module ## 0.16.0 (2020-09-07) - - new geometries module for creating GeoDataFrames from tag/value queries - - deprecate old pois and footprints modules (replaced by geometries module) - - auto-select first Polygon/MultiPolygon when geocoding with which_result=None - - new k_shortest_paths function to solve *k* shortest paths from origin to destination - - new shortest_path convenience function - - new get_digraph function to correctly convert MultiDiGraph to DiGraph - - miscellaneous performance improvements and optimizations - - deprecate induce_subgraph function - - remove deprecated boundaries module (replaced by geocoder module in v0.15.0) - - remove deprecated utils_geo.geocode function (replaced by geocoder.geocode function in v0.15.0) +- new geometries module for creating GeoDataFrames from tag/value queries +- deprecate old pois and footprints modules (replaced by geometries module) +- auto-select first Polygon/MultiPolygon when geocoding with which_result=None +- new k*shortest_paths function to solve \_k* shortest paths from origin to destination +- new shortest_path convenience function +- new get_digraph function to correctly convert MultiDiGraph to DiGraph +- miscellaneous performance improvements and optimizations +- deprecate induce_subgraph function +- remove deprecated boundaries module (replaced by geocoder module in v0.15.0) +- remove deprecated utils_geo.geocode function (replaced by geocoder.geocode function in v0.15.0) ## 0.15.1 (2020-07-03) - - fix geopandas future warnings +- fix geopandas future warnings ## 0.15.0 (2020-06-30) - - improve plotting defaults and streamline plot module speed and efficiency - - improve color handling in plot module - - improve route plotting - - plot_graph_routes function now accepts multiple route colors - - allow multiple elevation API providers - - consolidate_intersections replaces update_edge_lengths param with reconnect_edges param - - fix geopackage file saving after consolidating intersections - - add new geocoder module and move utils_geo.geocode function into it - - replace gdf_from_place/s functions with geocoder.geocode_to_gdf - - deprecate boundaries module - - remove deprecated timeout, memory, custom_settings, and max_query_area_size function params - - remove deprecated plotting params and plot_shape function +- improve plotting defaults and streamline plot module speed and efficiency +- improve color handling in plot module +- improve route plotting +- plot_graph_routes function now accepts multiple route colors +- allow multiple elevation API providers +- consolidate_intersections replaces update_edge_lengths param with reconnect_edges param +- fix geopackage file saving after consolidating intersections +- add new geocoder module and move utils_geo.geocode function into it +- replace gdf_from_place/s functions with geocoder.geocode_to_gdf +- deprecate boundaries module +- remove deprecated timeout, memory, custom_settings, and max_query_area_size function params +- remove deprecated plotting params and plot_shape function ## 0.14.1 (2020-06-09) - - fix simplification of graphs with long rural roads - - reduce memory footprint of graph simplification - - remove disconnected self-contained rings from graph by default when simplifying - - improve speed and efficiency of project_graph, graph_to_gdfs, and graph_from_gdfs - - improve attribute value conversion in load_graphml - - expose precision parameter for adding bearings, elevations, speeds, and travel times - - fix config function clobber behavior - - fix graph periphery cleaning when clean_periphery=True but simplify=False - - rename settings useful_tags_path to the more appropriate useful_tags_way - - deprecate the timeout, memory, custom_settings, and max_query_area_size function params - - the params above are now accessible via config function and settings module - - deprecate old plot params and plot_shape function - - remove previously deprecated infrastructure parameter in favor of custom_filter +- fix simplification of graphs with long rural roads +- reduce memory footprint of graph simplification +- remove disconnected self-contained rings from graph by default when simplifying +- improve speed and efficiency of project_graph, graph_to_gdfs, and graph_from_gdfs +- improve attribute value conversion in load_graphml +- expose precision parameter for adding bearings, elevations, speeds, and travel times +- fix config function clobber behavior +- fix graph periphery cleaning when clean_periphery=True but simplify=False +- rename settings useful_tags_path to the more appropriate useful_tags_way +- deprecate the timeout, memory, custom_settings, and max_query_area_size function params +- the params above are now accessible via config function and settings module +- deprecate old plot params and plot_shape function +- remove previously deprecated infrastructure parameter in favor of custom_filter ## 0.14.0 (2020-06-03) - - better geometry subdividing for huge OSM queries - - better handling of maxspeed list values for simplified graphs - - downloader only retrieves url response from cache if no server remark - - deprecate graph creation infrastructure parameter in favor of flexible custom_filter - - remove deprecated functions: graph_from_file, clean_intersections, gdfs_to_graph +- better geometry subdividing for huge OSM queries +- better handling of maxspeed list values for simplified graphs +- downloader only retrieves url response from cache if no server remark +- deprecate graph creation infrastructure parameter in favor of flexible custom_filter +- remove deprecated functions: graph_from_file, clean_intersections, gdfs_to_graph ## 0.13.0 (2020-05-25) - - major refactor of entire package - - clean up API and namespace - - new consolidate_intersections function with topological option - - new speed module to calculate graph edge speeds and travel times - - generalize POIs module to query with a flexible tags dict - - allow folium functions to accept FeatureGroup and kwargs - - all graph saving functions now take a filepath argument instead of folder/filename - - save shapefiles in single folder containing both nodes and edges - - optionally return distance and/or geometry in nearest edge search - - expose timeout and memory parameters in pois and footprints modules - - define default crs via epsg code instead of proj4 string - - update and simplify logging with timestamps - - graph metadata: add creation date and version, remove name - - replace inconsistent distance parameters with consistent dist parameters - - deprecate old clean_intersections function in favor of new consolidate_intersections - - deprecate old gdfs_to_graph function in favor of graph_from_gdfs - - deprecate old graph_from_file function in favor of graph_from_xml - - rename save_as_osm function -> save_graph_xml for consistency - - rename save_load module -> io - - remove old save_gdf_shapefile function - - drop support for python 3.5 and lower +- major refactor of entire package +- clean up API and namespace +- new consolidate_intersections function with topological option +- new speed module to calculate graph edge speeds and travel times +- generalize POIs module to query with a flexible tags dict +- allow folium functions to accept FeatureGroup and kwargs +- all graph saving functions now take a filepath argument instead of folder/filename +- save shapefiles in single folder containing both nodes and edges +- optionally return distance and/or geometry in nearest edge search +- expose timeout and memory parameters in pois and footprints modules +- define default crs via epsg code instead of proj4 string +- update and simplify logging with timestamps +- graph metadata: add creation date and version, remove name +- replace inconsistent distance parameters with consistent dist parameters +- deprecate old clean_intersections function in favor of new consolidate_intersections +- deprecate old gdfs_to_graph function in favor of graph_from_gdfs +- deprecate old graph_from_file function in favor of graph_from_xml +- rename save_as_osm function -> save_graph_xml for consistency +- rename save_load module -> io +- remove old save_gdf_shapefile function +- drop support for python 3.5 and lower ## 0.12.1 (2020-05-01) - - fix handling relations with missing type tag - - fix save_graph_geopackage handling numeric attributes - - fix load_graphml handling elevation and grade attributes - - improve edge finding algorithms to return edge key - - more informative graph_from_file data load error message - - refactor url-in-cache checking - - add timestamp helper function - - documentation improvements +- fix handling relations with missing type tag +- fix save_graph_geopackage handling numeric attributes +- fix load_graphml handling elevation and grade attributes +- improve edge finding algorithms to return edge key +- more informative graph_from_file data load error message +- refactor url-in-cache checking +- add timestamp helper function +- documentation improvements ## 0.12 (2020-04-10) - - add ability to save graph as geopackage file - - add truncate_by_edge implementation in truncate_graph_polygon - - allow flexible overpass settings (e.g., to query by date) - - better handling of invalid footprint geometries - - geocode function now uses nominatim_request function - - improve .osm xml output - - improve one-way street handling - - fix graph projection overwriting original lat/lng - - fix redistribute_vertices function for MultiLineStrings +- add ability to save graph as geopackage file +- add truncate_by_edge implementation in truncate_graph_polygon +- allow flexible overpass settings (e.g., to query by date) +- better handling of invalid footprint geometries +- geocode function now uses nominatim_request function +- improve .osm xml output +- improve one-way street handling +- fix graph projection overwriting original lat/lng +- fix redistribute_vertices function for MultiLineStrings ## 0.11.4 (2020-01-31) - - fix .osm xml output - - fix for pandas 1.0 +- fix .osm xml output +- fix for pandas 1.0 ## 0.11.3 (2020-01-09) - - fix errant print statement +- fix errant print statement ## 0.11.2 (2020-01-07) - - fix .osm xml output - - fix geopandas future compatibility +- fix .osm xml output +- fix geopandas future compatibility ## 0.11.1 (2020-01-01) - - fix get_nearest_edges search when not using a spatial index +- fix get_nearest_edges search when not using a spatial index ## 0.11 (2019-12-04) - - drop formal python 2 support - - refactor all modules for cleaner package organization - - make stats betweenness centrality compatible with networkx>=2.4 - - allow configurable overpass and nominatim endpoints - - allow gdf_from_places to take a which_result list argument - - handle zero-division in street grade calculation - - better footprint relation handling - - improve network type queries for better filtering - - fix pois_from_polygon returning points outside polygon +- drop formal python 2 support +- refactor all modules for cleaner package organization +- make stats betweenness centrality compatible with networkx>=2.4 +- allow configurable overpass and nominatim endpoints +- allow gdf_from_places to take a which_result list argument +- handle zero-division in street grade calculation +- better footprint relation handling +- improve network type queries for better filtering +- fix pois_from_polygon returning points outside polygon ## 0.10 (2019-05-08) - - remove deprecated buildings module - - filter steps ways out of bike queries - - convert CRS-handling to proj4 strings - - save graph to xml-formatted .osm file - - minor refactoring +- remove deprecated buildings module +- filter steps ways out of bike queries +- convert CRS-handling to proj4 strings +- save graph to xml-formatted .osm file +- minor refactoring ## 0.9 (2019-01-28) - - deprecate buildings module and replace with generalized footprints module - - improve handling of multipolygon footprints - - new function to find nearest edge(s), given coordinates - - add "search," "reverse," and "lookup" nominatim queries - - use unprojected graphs for figure-ground plotting functions - - allow non-integer osmid values for custom data - - improve get_route_edge_attributes function - - improve color mapping by node/edge attribute value - - make bidirectional network types explicit - - networkx compatibility fixes to resolve warnings +- deprecate buildings module and replace with generalized footprints module +- improve handling of multipolygon footprints +- new function to find nearest edge(s), given coordinates +- add "search," "reverse," and "lookup" nominatim queries +- use unprojected graphs for figure-ground plotting functions +- allow non-integer osmid values for custom data +- improve get_route_edge_attributes function +- improve color mapping by node/edge attribute value +- make bidirectional network types explicit +- networkx compatibility fixes to resolve warnings ## 0.8.2 (2018-09-19) - - add python 3.7 compatibility - - add convenience function to plot several routes over the same map - - optimize graph truncation to bounding box - - give self-loops a null bearing when calculating edge bearings - - make accept-language http header explicit and configurable - - add citation function - - refactor POI module +- add python 3.7 compatibility +- add convenience function to plot several routes over the same map +- optimize graph truncation to bounding box +- give self-loops a null bearing when calculating edge bearings +- make accept-language http header explicit and configurable +- add citation function +- refactor POI module ## 0.8.1 (2018-05-17) - - add Gephi compatibility argument for saving GraphML - - handle square bracket encapsulated strings when loading GraphML +- add Gephi compatibility argument for saving GraphML +- handle square bracket encapsulated strings when loading GraphML ## 0.8 (2018-05-05) - - add ability to retrieve points of interest - - improve performance for retrieving huge geographies' street networks - - fix building footprint retrieval query syntax - - minor bug fixes +- add ability to retrieve points of interest +- improve performance for retrieving huge geographies' street networks +- fix building footprint retrieval query syntax +- minor bug fixes ## 0.7.4 (2018-04-05) - - add fast nearest-nodes search - - allow custom network query filters - - allow create_graph to return graph with no edges - - improve figure_ground joint smoothing - - fix handling of parallel edges when making multidigraph undirected - - generalize same-geometry checker - - improve detection of prior topology simplification - - custom error types for finer-grained handling +- add fast nearest-nodes search +- allow custom network query filters +- allow create_graph to return graph with no edges +- improve figure_ground joint smoothing +- fix handling of parallel edges when making multidigraph undirected +- generalize same-geometry checker +- improve detection of prior topology simplification +- custom error types for finer-grained handling ## 0.7.3 (2018-03-12) - - turn off x- and y-axes to improve plotting appearance - - make floating-point precision and rounding more sensible - - improve OS path handling cross-platform - - replace great-circle distance calculator with haversine - - add access filter as configurable setting - - improve performance of inducing subgraphs - - fix utils.get_largest_component for networkx 2.2 compatibility - - fix config settings namespacing +- turn off x- and y-axes to improve plotting appearance +- make floating-point precision and rounding more sensible +- improve OS path handling cross-platform +- replace great-circle distance calculator with haversine +- add access filter as configurable setting +- improve performance of inducing subgraphs +- fix utils.get_largest_component for networkx 2.2 compatibility +- fix config settings namespacing ## 0.7.2 (2018-02-15) - - compatibility with networkx 2.1 +- compatibility with networkx 2.1 ## 0.7.1 (2018-02-04) - - fix documentation build - - ignore ways marked access=no +- fix documentation build +- ignore ways marked access=no ## 0.7 (2018-02-01) - - ability to load a graph from a .osm file - - change datum from NAD83 to WGS84 - - make roundabouts one-way - - conformal plotting for unprojected graphs - - fix folium web maps rendering +- ability to load a graph from a .osm file +- change datum from NAD83 to WGS84 +- make roundabouts one-way +- conformal plotting for unprojected graphs +- fix folium web maps rendering ## 0.6 (2017-10-02) - - migrate to the networkx 2.0 API +- migrate to the networkx 2.0 API ## 0.5.4 (2017-09-16) - - add optional cleaned intersections count to basic stats - - allow circuity to be calculated for projected or unprojected networks - - various code clean-up and refactoring +- add optional cleaned intersections count to basic stats +- allow circuity to be calculated for projected or unprojected networks +- various code clean-up and refactoring ## 0.5.3 (2017-07-22) - - add requirements files to distribution +- add requirements files to distribution ## 0.5.2 (2017-07-22) - - add ability to download other infrastructures besides just roads/paths (e.g., rail lines, power lines, etc.) - - calculate graph edges' bearings - - add ability to get nearest node by great circle or euclidean distance - - move examples/demo notebooks to new repo: osmnx-examples - - fix docstrings - - fix building footprint downloads that require multiple calls for large areas - - fix missing MultiPolygon import in buildings module +- add ability to download other infrastructures besides just roads/paths (e.g., rail lines, power lines, etc.) +- calculate graph edges' bearings +- add ability to get nearest node by great circle or euclidean distance +- move examples/demo notebooks to new repo: osmnx-examples +- fix docstrings +- fix building footprint downloads that require multiple calls for large areas +- fix missing MultiPolygon import in buildings module ## 0.5.1 (2017-05-12) - - functionality to clean-up and consolidate complex intersections - - let save_gdf_shapefile save building footprint GeoDataFrames - - set node color correctly in figure-ground diagrams +- functionality to clean-up and consolidate complex intersections +- let save_gdf_shapefile save building footprint GeoDataFrames +- set node color correctly in figure-ground diagrams ## 0.5 (2017-04-25) - - add elevation module to get node elevations and street grades - - new color sequence creation and conversion functions in plot module - - new function to get a path's edge attribute values - - gracefully handle subpolygons that are invalid or have zero area - - make truncate_graph_polygon work on projected graphs - - plot_shape accepts a color or a list of colors - - make all requests to Overpass API set custom user-agent and referer - - rewrite algorithms to convert multidigraphs to multigraphs +- add elevation module to get node elevations and street grades +- new color sequence creation and conversion functions in plot module +- new function to get a path's edge attribute values +- gracefully handle subpolygons that are invalid or have zero area +- make truncate_graph_polygon work on projected graphs +- plot_shape accepts a color or a list of colors +- make all requests to Overpass API set custom user-agent and referer +- rewrite algorithms to convert multidigraphs to multigraphs ## 0.4.1 (2017-04-01) - - fix load_graphml so we can save a graph again after loading it - - fix load_graphml so edge oneway attribute is not always set to True - - buildings module gets buildings stored in OSM as relations as well as ways - - fix figure-ground diagram saving to make perfect square and smooth joints - - add optional graph argument to plot_figure_ground - - suppress jupyter notebook deprecation warnings +- fix load_graphml so we can save a graph again after loading it +- fix load_graphml so edge oneway attribute is not always set to True +- buildings module gets buildings stored in OSM as relations as well as ways +- fix figure-ground diagram saving to make perfect square and smooth joints +- add optional graph argument to plot_figure_ground +- suppress jupyter notebook deprecation warnings ## 0.4 (2017-03-01) - - plot entire networks with folium - - plot routes on top of networks with folium - - vectorize all great circle calculations - - new geocode function in utils - - remove geopy dependency - - refactor modules - - simplify before truncating by distance when getting graph by point and network distance - - project geometries, GeoDataFrames, and graphs to a passed-in CRS +- plot entire networks with folium +- plot routes on top of networks with folium +- vectorize all great circle calculations +- new geocode function in utils +- remove geopy dependency +- refactor modules +- simplify before truncating by distance when getting graph by point and network distance +- project geometries, GeoDataFrames, and graphs to a passed-in CRS ## 0.3.1 (2017-02-15) - - clean up docstrings throughout - - remove network code vestiges from buildings.py +- clean up docstrings throughout +- remove network code vestiges from buildings.py ## 0.3 (2017-01-29) - - add route plotting with folium - - add downloading and visualization of building footprints - - updates for compatibility with matplotlib 2.0 +- add route plotting with folium +- add downloading and visualization of building footprints +- updates for compatibility with matplotlib 2.0 ## 0.2.2 (2017-01-20) - - fixes for compatibility with networkx 2.0's new API - - make png default image save format - - figure-ground plots collect street network from a wider area +- fixes for compatibility with networkx 2.0's new API +- make png default image save format +- figure-ground plots collect street network from a wider area ## 0.2.1 (2017-01-11) - - add license file to dist package +- add license file to dist package ## 0.2 (2017-01-10) - - refactor modules - - add graph to GDF and GDF to graph functions - - add encoding argument to save_graph_shapefile - - add unit tests and continuous integration +- refactor modules +- add graph to GDF and GDF to graph functions +- add encoding argument to save_graph_shapefile +- add unit tests and continuous integration ## 0.1 (2016-12-19) - - add street width attribute for ways from OSM +- add street width attribute for ways from OSM ## 0.1b2 (2016-11-29) - - make simplification error messages explicit +- make simplification error messages explicit ## 0.1b1 (2016-11-28) - - process land use and area tags from OSM - - make intersection error messages clear +- process land use and area tags from OSM +- make intersection error messages clear ## 0.1a1 (2016-11-07) - - first pre-release +- first pre-release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d23042e87..43c595f37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,29 +4,29 @@ Thanks for using OSMnx and for considering contributing to it by opening an issu #### If you have a "how-to" or usage question: - - please ask your question on [StackOverflow](https://stackoverflow.com/search?q=osmnx), as we reserve the issue tracker for bug reports and new feature development. Any such questions asked in the issue tracker will be automatically closed. +- please ask your question on [StackOverflow](https://stackoverflow.com/search?q=osmnx), as we reserve the issue tracker for bug reports and new feature development. Any such questions asked in the issue tracker will be automatically closed. #### If you're having an installation problem: - - make sure you've followed the installation instructions in the [documentation](https://osmnx.readthedocs.io/) - - if you installed OSMnx via conda-forge, please open an issue at its [feedstock](https://github.com/conda-forge/osmnx-feedstock/issues) +- make sure you've followed the installation instructions in the [documentation](https://osmnx.readthedocs.io/) +- if you installed OSMnx via conda-forge, please open an issue at its [feedstock](https://github.com/conda-forge/osmnx-feedstock/issues) #### If you've found a bug: - - read the error message, then review the [documentation](https://osmnx.readthedocs.io/) and [OSMnx examples](https://github.com/gboeing/osmnx-examples) gallery, which cover key concepts, installation, and package usage - - search through the [open issues](https://github.com/gboeing/osmnx/issues?q=is%3Aopen+is%3Aissue) and [closed issues](https://github.com/gboeing/osmnx/issues?q=is%3Aissue+is%3Aclosed) to see if the problem has already been reported - - if the problem is with a dependency of OSMnx, open an issue in the dependency's repo - - if the problem is with OSMnx itself and you can fix it simply, please open a pull request - - if the problem persists, please open an issue in the [issue tracker](https://github.com/gboeing/osmnx/issues), and *provide all the information requested in the template*, including a minimal working example so others can independently and completely reproduce the problem +- read the error message, then review the [documentation](https://osmnx.readthedocs.io/) and [OSMnx examples](https://github.com/gboeing/osmnx-examples) gallery, which cover key concepts, installation, and package usage +- search through the [open issues](https://github.com/gboeing/osmnx/issues?q=is%3Aopen+is%3Aissue) and [closed issues](https://github.com/gboeing/osmnx/issues?q=is%3Aissue+is%3Aclosed) to see if the problem has already been reported +- if the problem is with a dependency of OSMnx, open an issue in the dependency's repo +- if the problem is with OSMnx itself and you can fix it simply, please open a pull request +- if the problem persists, please open an issue in the [issue tracker](https://github.com/gboeing/osmnx/issues), and _provide all the information requested in the template_, including a minimal working example so others can independently and completely reproduce the problem #### If you have a feature proposal or want to contribute: - - post your proposal on the [issue tracker](https://github.com/gboeing/osmnx/issues), and *provide all the information requested in the template*, so we can review it together (some proposals may not be a good fit for the project) - - fork the repo, make your change, [test it](./tests), and submit a PR - - respond to code review - - adhere to the following project standards - - `black` code style with max line length of 100 - - `isort` sorted imports - - `numpy` style docstrings +- post your proposal on the [issue tracker](https://github.com/gboeing/osmnx/issues), and _provide all the information requested in the template_, so we can review it together (some proposals may not be a good fit for the project) +- fork the repo, make your change, [test it](./tests), and submit a PR +- respond to code review +- adhere to the following project standards + - `black` code style with max line length of 100 + - `isort` sorted imports + - `numpy` style docstrings This project requires minimum Python and NumPy versions in accordance with [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html). diff --git a/README.md b/README.md index ec67642d4..10cb6a185 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ If you use OSMnx in your work, please cite the journal article: -Boeing, G. 2017. "[OSMnx: New Methods for Acquiring, Constructing, Analyzing, and Visualizing Complex Street Networks](https://geoffboeing.com/publications/osmnx-complex-street-networks/)." *Computers, Environment and Urban Systems* 65, 126–139. +Boeing, G. 2017. "[OSMnx: New Methods for Acquiring, Constructing, Analyzing, and Visualizing Complex Street Networks](https://geoffboeing.com/publications/osmnx-complex-street-networks/)." _Computers, Environment and Urban Systems_ 65, 126–139. ## Getting Started diff --git a/docs/.readthedocs.yaml b/docs/.readthedocs.yaml index c542bb9d9..d633de03c 100644 --- a/docs/.readthedocs.yaml +++ b/docs/.readthedocs.yaml @@ -11,8 +11,8 @@ build: formats: all python: - install: - - requirements: docs/requirements.txt + install: + - requirements: docs/requirements.txt sphinx: configuration: docs/conf.py diff --git a/docs/conf.py b/docs/conf.py index d537ee467..78ec9aec4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ project = "OSMnx" # dynamically load version from /osmnx/_version.py -with open("../osmnx/_version.py") as f: +with Path.open("../osmnx/_version.py") as f: version = release = f.read().split(" = ")[1].replace('"', "") # mock import all required + optional dependency packages because readthedocs diff --git a/docs/user-reference.rst b/docs/user-reference.rst index bc796e042..4312bdfe9 100644 --- a/docs/user-reference.rst +++ b/docs/user-reference.rst @@ -3,7 +3,7 @@ User Reference User Reference for the OSMnx package. -This guide describes the usage of OSMnx's public API. Every function can be accessed via `ox.module_name.function_name()` and many can also be accessed directly via `ox.function_name()` as a shortcut. +This guide describes the usage of OSMnx's public API. Every function can be accessed via ``ox.module_name.function_name()`` and many can also be accessed directly via ``ox.function_name()`` as a shortcut. If you are looking for an introduction to OSMnx, read the :doc:`getting-started` guide. diff --git a/environments/linux/create-environment.sh b/environments/linux/create-environment.sh index 3daa11696..602e50589 100644 --- a/environments/linux/create-environment.sh +++ b/environments/linux/create-environment.sh @@ -3,7 +3,7 @@ set -e ENV=ox PACKAGE=osmnx eval "$(conda shell.bash hook)" -conda activate base +conda deactivate mamba env remove -n $ENV --yes mamba clean --all --yes --quiet --no-banner mamba create -c conda-forge --strict-channel-priority -n $ENV --file "../docker/requirements.txt" --yes --no-banner diff --git a/environments/windows/create-environment.bat b/environments/windows/create-environment.bat index 0103ec09f..42b86138c 100644 --- a/environments/windows/create-environment.bat +++ b/environments/windows/create-environment.bat @@ -1,7 +1,7 @@ SET ENV="ox" SET PACKAGE="osmnx" CALL %USERPROFILE%\mambaforge\Scripts\activate.bat && ^ -conda activate base && ^ +conda deactivate && ^ mamba env remove -n %ENV% --yes && ^ mamba clean --all --yes --quiet --no-banner && ^ mamba create -c conda-forge --strict-channel-priority -n %ENV% --file "../docker/requirements.txt" --yes --no-banner && ^ diff --git a/environments/windows/environment.yml b/environments/windows/environment.yml index 3d8a2884e..61cd02a43 100644 --- a/environments/windows/environment.yml +++ b/environments/windows/environment.yml @@ -1,411 +1,411 @@ -name: ox -channels: - - conda-forge -dependencies: - - access=1.1.8=pyhd8ed1ab_0 - - affine=2.4.0=pyhd8ed1ab_0 - - alabaster=0.7.13=pyhd8ed1ab_0 - - amply=0.1.6=pyhd8ed1ab_0 - - anyio=3.7.0=pyhd8ed1ab_1 - - argon2-cffi=21.3.0=pyhd8ed1ab_0 - - argon2-cffi-bindings=21.2.0=py311ha68e1ae_3 - - asttokens=2.2.1=pyhd8ed1ab_0 - - async-lru=2.0.2=pyhd8ed1ab_0 - - attrs=23.1.0=pyh71513ae_1 - - autopep8=2.0.2=pyhd8ed1ab_0 - - babel=2.12.1=pyhd8ed1ab_1 - - backcall=0.2.0=pyh9f0ad1d_0 - - backports=1.0=pyhd8ed1ab_3 - - backports.functools_lru_cache=1.6.5=pyhd8ed1ab_0 - - beautifulsoup4=4.12.2=pyha770c72_0 - - black=23.3.0=py311h1ea47a8_1 - - bleach=6.0.0=pyhd8ed1ab_0 - - blinker=1.6.2=pyhd8ed1ab_0 - - blosc=1.21.4=hdccc3a2_0 - - bokeh=3.2.0=pyhd8ed1ab_0 - - boltons=23.0.0=pyhd8ed1ab_0 - - boolean.py=3.7=py_0 - - boost-cpp=1.78.0=h9f4b32c_3 - - bottleneck=1.3.7=py311h59ca53f_0 - - branca=0.6.0=pyhd8ed1ab_0 - - brotli=1.0.9=hcfcfb64_9 - - brotli-bin=1.0.9=hcfcfb64_9 - - bzip2=1.0.8=h8ffe710_4 - - ca-certificates=2023.5.7=h56e8100_0 - - cairo=1.16.0=hdecc03f_1016 - - cartopy=0.21.1=py311h178a126_1 - - certifi=2023.5.7=pyhd8ed1ab_0 - - cffi=1.15.1=py311h7d9ee11_3 - - cfgv=3.3.1=pyhd8ed1ab_0 - - cfitsio=4.2.0=h9ebe7e4_0 - - chardet=5.1.0=py311h1ea47a8_0 - - charset-normalizer=3.1.0=pyhd8ed1ab_0 - - click=8.1.3=win_pyhd8ed1ab_2 - - click-plugins=1.1.1=py_0 - - cligj=0.7.2=pyhd8ed1ab_1 - - cmarkgfm=0.8.0=py311ha68e1ae_2 - - colorama=0.4.6=pyhd8ed1ab_0 - - comm=0.1.3=pyhd8ed1ab_0 - - conda=23.5.0=py311h1ea47a8_1 - - conda-build=3.25.0=py311h1ea47a8_0 - - conda-forge-pinning=2023.06.28.15.07.21=hd8ed1ab_0 - - conda-index=0.2.3=pyhd8ed1ab_0 - - conda-package-handling=2.0.2=pyh38be061_0 - - conda-package-streaming=0.8.0=pyhd8ed1ab_0 - - conda-smithy=3.23.1=pyhd8ed1ab_0 - - contextily=1.3.0=pyhd8ed1ab_0 - - contourpy=1.1.0=py311h005e61a_0 - - coverage=7.2.7=py311ha68e1ae_0 - - cryptography=41.0.1=py311h28e9c30_0 - - curl=8.1.2=h68f0423_0 - - cycler=0.11.0=pyhd8ed1ab_0 - - cython=0.29.35=py311h12c1d0e_0 - - debugpy=1.6.7=py311h12c1d0e_0 - - decorator=5.1.1=pyhd8ed1ab_0 - - defusedxml=0.7.1=pyhd8ed1ab_0 - - deprecated=1.2.14=pyh1a96a4e_0 - - deprecation=2.1.0=pyh9f0ad1d_0 - - descartes=1.1.0=py_4 - - distlib=0.3.6=pyhd8ed1ab_0 - - docutils=0.19=py311h1ea47a8_1 - - editables=0.3=pyhd8ed1ab_0 - - entrypoints=0.4=pyhd8ed1ab_0 - - esda=2.4.3=pyhd8ed1ab_0 - - exceptiongroup=1.1.1=pyhd8ed1ab_0 - - executing=1.2.0=pyhd8ed1ab_0 - - expat=2.5.0=h63175ca_1 - - filelock=3.12.2=pyhd8ed1ab_0 - - fiona=1.9.4=py311h4e4dc46_0 - - flit-core=3.9.0=pyhd8ed1ab_0 - - folium=0.14.0=pyhd8ed1ab_0 - - font-ttf-dejavu-sans-mono=2.37=hab24e00_0 - - font-ttf-inconsolata=3.000=h77eed37_0 - - font-ttf-source-code-pro=2.038=h77eed37_0 - - font-ttf-ubuntu=0.83=hab24e00_0 - - fontconfig=2.14.2=hbde0cde_0 - - fonts-conda-ecosystem=1=0 - - fonts-conda-forge=1=0 - - fonttools=4.40.0=py311ha68e1ae_0 - - freetype=2.12.1=h546665d_1 - - freexl=1.0.6=h67ca5e6_1 - - furo=2023.5.20=pyhd8ed1ab_1 - - gdal=3.7.0=py311heaf1029_0 - - geographiclib=1.52=pyhd8ed1ab_0 - - geopandas=0.13.2=pyhd8ed1ab_1 - - geopandas-base=0.13.2=pyha770c72_1 - - geopy=2.3.0=pyhd8ed1ab_0 - - geos=3.11.2=h1537add_0 - - geotiff=1.7.1=h7222e44_8 - - gettext=0.21.1=h5728263_0 - - giddy=2.3.4=pyhd8ed1ab_0 - - git=2.41.0=h57928b3_0 - - gitdb=4.0.10=pyhd8ed1ab_0 - - gitpython=3.1.31=pyhd8ed1ab_0 - - glob2=0.7=py_0 - - glpk=5.0=h8ffe710_0 - - h11=0.14.0=pyhd8ed1ab_0 - - h2=4.1.0=pyhd8ed1ab_0 - - hatch=1.7.0=pyhd8ed1ab_0 - - hatchling=1.18.0=pyhd8ed1ab_0 - - hdf4=4.2.15=h1334946_6 - - hdf5=1.14.0=nompi_h918d9b7_103 - - hpack=4.0.0=pyh9f0ad1d_0 - - httpcore=0.17.2=pyhd8ed1ab_0 - - httpx=0.24.1=pyhd8ed1ab_0 - - hyperframe=6.0.1=pyhd8ed1ab_0 - - hyperlink=21.0.0=pyhd3deb0d_0 - - icu=72.1=h63175ca_0 - - identify=2.5.24=pyhd8ed1ab_0 - - idna=3.4=pyhd8ed1ab_0 - - imagesize=1.4.1=pyhd8ed1ab_0 - - importlib-metadata=6.7.0=pyha770c72_0 - - importlib_metadata=6.7.0=hd8ed1ab_0 - - importlib_resources=5.12.0=pyhd8ed1ab_0 - - inequality=1.0.0=py_0 - - iniconfig=2.0.0=pyhd8ed1ab_0 - - ipykernel=6.23.3=pyh6817e22_0 - - ipython=8.14.0=pyh08f2357_0 - - ipywidgets=8.0.6=pyhd8ed1ab_0 - - isodate=0.6.1=pyhd8ed1ab_0 - - jaraco.classes=3.2.3=pyhd8ed1ab_0 - - jedi=0.18.2=pyhd8ed1ab_0 - - jinja2=3.1.2=pyhd8ed1ab_1 - - joblib=1.3.0=pyhd8ed1ab_0 - - json5=0.9.5=pyh9f0ad1d_0 - - jsonpatch=1.32=pyhd8ed1ab_0 - - jsonpointer=2.0=py_0 - - jsonschema=4.17.3=pyhd8ed1ab_0 - - jupyter-lsp=2.2.0=pyhd8ed1ab_0 - - jupyter-server-mathjax=0.2.6=pyh5bfe37b_1 - - jupyter_client=8.3.0=pyhd8ed1ab_0 - - jupyter_core=5.3.1=py311h1ea47a8_0 - - jupyter_events=0.6.3=pyhd8ed1ab_0 - - jupyter_server=2.7.0=pyhd8ed1ab_0 - - jupyter_server_terminals=0.4.4=pyhd8ed1ab_1 - - jupyterlab=4.0.2=pyhd8ed1ab_0 - - jupyterlab_pygments=0.2.2=pyhd8ed1ab_0 - - jupyterlab_server=2.23.0=pyhd8ed1ab_0 - - jupyterlab_widgets=3.0.7=pyhd8ed1ab_1 - - kealib=1.5.1=hc8369a0_3 - - keyring=24.2.0=py311h1ea47a8_0 - - kiwisolver=1.4.4=py311h005e61a_1 - - krb5=1.20.1=heb0366b_0 - - lcms2=2.15=h3e3b177_1 - - lerc=4.0.0=h63175ca_0 - - libaec=1.0.6=h63175ca_1 - - libarchive=3.6.2=h27c7867_0 - - libblas=3.9.0=17_win64_openblas - - libbrotlicommon=1.0.9=hcfcfb64_9 - - libbrotlidec=1.0.9=hcfcfb64_9 - - libbrotlienc=1.0.9=hcfcfb64_9 - - libcblas=3.9.0=17_win64_openblas - - libcurl=8.1.2=h68f0423_0 - - libdeflate=1.18=hcfcfb64_0 - - libexpat=2.5.0=h63175ca_1 - - libffi=3.4.2=h8ffe710_5 - - libflang=5.0.0=h6538335_20180525 - - libgdal=3.7.0=hdf18f8d_0 - - libglib=2.76.3=he8f3873_0 - - libiconv=1.17=h8ffe710_0 - - libjpeg-turbo=2.1.5.1=hcfcfb64_0 - - libkml=1.3.0=hf2ab4e4_1015 - - liblapack=3.9.0=17_win64_openblas - - liblapacke=3.9.0=17_win64_openblas - - liblief=0.12.3=h63175ca_0 - - libnetcdf=4.9.2=nompi_hc664c2b_104 - - libopenblas=0.3.23=pthreads_hc140b1d_0 - - libpng=1.6.39=h19919ed_0 - - libpq=15.3=ha9684e8_0 - - libpysal=4.7.0=pyhd8ed1ab_0 - - librttopo=1.1.0=he1da8c1_13 - - libsodium=1.0.18=h8d14728_1 - - libspatialindex=1.9.3=h39d44d4_4 - - libspatialite=5.0.1=h50a8ebb_25 - - libsqlite=3.42.0=hcfcfb64_0 - - libssh2=1.11.0=h7dfc565_0 - - libtiff=4.5.1=h6c8260b_0 - - libwebp-base=1.3.0=hcfcfb64_0 - - libxcb=1.15=hcd874cb_0 - - libxml2=2.10.4=hc3477c8_0 - - libzip=1.9.2=h519de47_1 - - libzlib=1.2.13=hcfcfb64_5 - - license-expression=1.2=py_0 - - llvm-meta=5.0.0=0 - - llvmlite=0.40.1=py311h5bc0dda_0 - - lz4-c=1.9.4=hcfcfb64_0 - - lzo=2.10=he774522_1000 - - m2-msys2-runtime=2.5.0.17080.65c939c=3 - - m2-patch=2.7.5=2 - - m2w64-gcc-libgfortran=5.3.0=6 - - m2w64-gcc-libs=5.3.0=7 - - m2w64-gcc-libs-core=5.3.0=7 - - m2w64-gmp=6.1.0=2 - - m2w64-libwinpthread-git=5.0.0.4634.697f757=2 - - mapclassify=2.5.0=pyhd8ed1ab_1 - - markdown-it-py=3.0.0=pyhd8ed1ab_0 - - markupsafe=2.1.3=py311ha68e1ae_0 - - matplotlib-base=3.7.1=py311h6e989c2_0 - - matplotlib-inline=0.1.6=pyhd8ed1ab_0 - - mdurl=0.1.0=pyhd8ed1ab_0 - - menuinst=1.4.19=py311h1ea47a8_1 - - mercantile=1.2.1=pyhd8ed1ab_0 - - mgwr=2.1.2=py_0 - - mistune=3.0.0=pyhd8ed1ab_0 - - momepy=0.6.0=pyhd8ed1ab_1 - - more-itertools=9.1.0=pyhd8ed1ab_0 - - mpir=3.0.0=he025d50_1002 - - mpmath=1.3.0=pyhd8ed1ab_0 - - msrest=0.6.21=pyh44b312d_0 - - msys2-conda-epoch=20160418=1 - - munch=3.0.0=pyhd8ed1ab_0 - - munkres=1.1.4=pyh9f0ad1d_0 - - mypy_extensions=1.0.0=pyha770c72_0 - - nbclient=0.8.0=pyhd8ed1ab_0 - - nbconvert-core=7.6.0=pyhd8ed1ab_0 - - nbdime=3.2.1=pyhd8ed1ab_0 - - nbformat=5.9.0=pyhd8ed1ab_0 - - nbqa=1.7.0=pyhd8ed1ab_1 - - nest-asyncio=1.5.6=pyhd8ed1ab_0 - - networkx=3.1=pyhd8ed1ab_0 - - nodeenv=1.8.0=pyhd8ed1ab_0 - - nomkl=1.0=h5ca1d4c_0 - - notebook-shim=0.2.3=pyhd8ed1ab_0 - - numba=0.57.1=py311h2c0921f_0 - - numexpr=2.8.4=py311h0aebda5_100 - - numpy=1.24.4=py311h0b4df5a_0 - - oauthlib=3.2.2=pyhd8ed1ab_0 - - openjpeg=2.5.0=ha2aaf27_2 - - openmp=5.0.0=vc14_1 - - openssl=3.1.1=hcfcfb64_1 - - osmnx=1.5.0=pyhd8ed1ab_0 - - overrides=7.3.1=pyhd8ed1ab_0 - - packaging=23.1=pyhd8ed1ab_0 - - pandas=2.0.2=py311hf63dbb6_0 - - pandocfilters=1.5.0=pyhd8ed1ab_0 - - parso=0.8.3=pyhd8ed1ab_0 - - pathspec=0.11.1=pyhd8ed1ab_0 - - patsy=0.5.3=pyhd8ed1ab_0 - - pcre2=10.40=h17e33f8_0 - - pexpect=4.8.0=pyh1a96a4e_2 - - pickleshare=0.7.5=py_1003 - - pillow=9.5.0=py311hde623f7_1 - - pip=23.1.2=pyhd8ed1ab_0 - - pixman=0.40.0=h8ffe710_0 - - pkginfo=1.9.6=pyhd8ed1ab_0 - - pkgutil-resolve-name=1.3.10=pyhd8ed1ab_0 - - platformdirs=3.8.0=pyhd8ed1ab_0 - - pluggy=1.2.0=pyhd8ed1ab_0 - - pointpats=2.3.0=pyhd8ed1ab_0 - - pooch=1.7.0=pyha770c72_3 - - poppler=23.05.0=h45d20d0_1 - - poppler-data=0.4.12=hd8ed1ab_0 - - postgresql=15.3=hd87cd2b_0 - - pre-commit=3.3.3=pyha770c72_0 - - proj=9.2.0=heca977f_0 - - prometheus_client=0.17.0=pyhd8ed1ab_0 - - prompt-toolkit=3.0.38=pyha770c72_0 - - prompt_toolkit=3.0.38=hd8ed1ab_0 - - psutil=5.9.5=py311ha68e1ae_0 - - psycopg2=2.9.3=py311hb12f294_2 - - pthread-stubs=0.4=hcd874cb_1001 - - ptyprocess=0.7.0=pyhd3deb0d_0 - - pulp=2.7.0=py311h1ea47a8_0 - - pure_eval=0.2.2=pyhd8ed1ab_0 - - py-lief=0.12.3=py311h12c1d0e_0 - - pycodestyle=2.10.0=pyhd8ed1ab_0 - - pycosat=0.6.4=py311ha68e1ae_1 - - pycparser=2.21=pyhd8ed1ab_0 - - pycryptodome=3.18.0=py311ha68e1ae_0 - - pygithub=1.58.0=pyh1a96a4e_0 - - pygments=2.15.1=pyhd8ed1ab_0 - - pyjwt=2.7.0=pyhd8ed1ab_0 - - pynacl=1.5.0=py311hd53affc_2 - - pyopenssl=23.2.0=pyhd8ed1ab_1 - - pyparsing=3.1.0=pyhd8ed1ab_0 - - pyperclip=1.8.2=pyhd8ed1ab_2 - - pyproj=3.6.0=py311h095e9de_0 - - pyrsistent=0.19.3=py311ha68e1ae_0 - - pysal=23.1=pyhd8ed1ab_0 - - pyshp=2.3.1=pyhd8ed1ab_0 - - pysocks=1.7.1=pyh0701188_6 - - pytest=7.4.0=pyhd8ed1ab_0 - - pytest-cov=4.1.0=pyhd8ed1ab_0 - - python=3.11.4=h2628c8c_0_cpython - - python-dateutil=2.8.2=pyhd8ed1ab_0 - - python-fastjsonschema=2.17.1=pyhd8ed1ab_0 - - python-igraph=0.10.4=py311h8db4363_0 - - python-json-logger=2.0.7=pyhd8ed1ab_0 - - python-libarchive-c=4.0=py311h1ea47a8_2 - - python-tzdata=2023.3=pyhd8ed1ab_0 - - python_abi=3.11=3_cp311 - - pytz=2023.3=pyhd8ed1ab_0 - - pywin32=304=py311h12c1d0e_2 - - pywin32-ctypes=0.2.2=py311h1ea47a8_0 - - pywinpty=2.0.10=py311h12c1d0e_0 - - pyyaml=6.0=py311ha68e1ae_5 - - pyzmq=25.1.0=py311h7b3f143_0 - - quantecon=0.5.3=pyhd8ed1ab_0 - - rasterio=1.3.7=py311h826718d_1 - - rasterstats=0.19.0=pyhd8ed1ab_0 - - readme_renderer=40.0=pyhd8ed1ab_0 - - requests=2.31.0=pyhd8ed1ab_0 - - requests-oauthlib=1.3.1=pyhd8ed1ab_0 - - requests-toolbelt=1.0.0=pyhd8ed1ab_0 - - rfc3339-validator=0.1.4=pyhd8ed1ab_0 - - rfc3986=2.0.0=pyhd8ed1ab_0 - - rfc3986-validator=0.1.1=pyh9f0ad1d_0 - - rich=13.4.2=pyhd8ed1ab_0 - - ripgrep=13.0.0=h7f3b576_2 - - rtree=1.0.1=py311hcacb13a_1 - - ruamel.yaml=0.17.32=py311ha68e1ae_0 - - ruamel.yaml.clib=0.2.7=py311ha68e1ae_1 - - ruff=0.0.275=py311hc14472d_0 - - scikit-learn=1.2.2=py311h142b183_2 - - scipy=1.11.0=py311h37ff6ca_0 - - scrypt=0.8.18=py311h4be8fce_4 - - seaborn=0.12.2=hd8ed1ab_0 - - seaborn-base=0.12.2=pyhd8ed1ab_0 - - segregation=2.4.2=pyhd8ed1ab_0 - - send2trash=1.8.2=pyh08f2357_0 - - setuptools=68.0.0=pyhd8ed1ab_0 - - shapely=2.0.1=py311h343093d_1 - - shellingham=1.5.1=pyhd8ed1ab_0 - - simplejson=3.19.1=py311ha68e1ae_0 - - six=1.16.0=pyh6c4a22f_0 - - smmap=3.0.5=pyh44b312d_0 - - snappy=1.1.10=hfb803bf_0 - - sniffio=1.3.0=pyhd8ed1ab_0 - - snowballstemmer=2.2.0=pyhd8ed1ab_0 - - snuggs=1.4.7=py_0 - - soupsieve=2.3.2.post1=pyhd8ed1ab_0 - - spaghetti=1.7.4=pyhd8ed1ab_0 - - spglm=1.0.8=py_0 - - sphinx=6.2.1=pyhd8ed1ab_0 - - sphinx-basic-ng=1.0.0b1=pyhd8ed1ab_1 - - sphinxcontrib-applehelp=1.0.4=pyhd8ed1ab_0 - - sphinxcontrib-devhelp=1.0.2=py_0 - - sphinxcontrib-htmlhelp=2.0.1=pyhd8ed1ab_0 - - sphinxcontrib-jsmath=1.0.1=py_0 - - sphinxcontrib-qthelp=1.0.3=py_0 - - sphinxcontrib-serializinghtml=1.1.5=pyhd8ed1ab_2 - - spint=1.0.7=pyhd8ed1ab_0 - - splot=1.1.5.post1=pyhd8ed1ab_0 - - spopt=0.5.0=pyhd8ed1ab_0 - - spreg=1.3.2=pyhd8ed1ab_0 - - spvcm=0.3.0=py_0 - - sqlite=3.42.0=hcfcfb64_0 - - stack_data=0.6.2=pyhd8ed1ab_0 - - statsmodels=0.14.0=py311h59ca53f_1 - - suitesparse=5.4.0=h5d0cbe0_1 - - sympy=1.12=pyh04b8f61_3 - - terminado=0.17.0=pyh08f2357_0 - - texttable=1.6.7=pyhd8ed1ab_0 - - threadpoolctl=3.1.0=pyh8a188c0_0 - - tiledb=2.13.2=h3132609_0 - - tinycss2=1.2.1=pyhd8ed1ab_0 - - tk=8.6.12=h8ffe710_0 - - tobler=0.10=pyhd8ed1ab_0 - - tokenize-rt=5.1.0=pyhd8ed1ab_0 - - toml=0.10.2=pyhd8ed1ab_0 - - tomli=2.0.1=pyhd8ed1ab_0 - - tomli-w=1.0.0=pyhd8ed1ab_0 - - tomlkit=0.11.8=pyha770c72_0 - - toolz=0.12.0=pyhd8ed1ab_0 - - tornado=6.3.2=py311ha68e1ae_0 - - tqdm=4.65.0=pyhd8ed1ab_1 - - traitlets=5.9.0=pyhd8ed1ab_0 - - trove-classifiers=2023.5.24=pyhd8ed1ab_0 - - twine=4.0.2=pyhd8ed1ab_0 - - typing-extensions=4.6.3=hd8ed1ab_0 - - typing_extensions=4.6.3=pyha770c72_0 - - typing_utils=0.1.0=pyhd8ed1ab_0 - - tzdata=2023c=h71feb2d_0 - - ucrt=10.0.22621.0=h57928b3_0 - - ukkonen=1.0.1=py311h005e61a_3 - - urllib3=2.0.3=pyhd8ed1ab_0 - - userpath=1.7.0=pyhd8ed1ab_0 - - vc=14.3=h64f974e_16 - - vc14_runtime=14.36.32532=hfdfe4a8_16 - - virtualenv=20.23.1=pyhd8ed1ab_0 - - vs2015_runtime=14.36.32532=h05e6639_16 - - vsts-python-api=0.1.25=pyhd8ed1ab_1 - - wcwidth=0.2.6=pyhd8ed1ab_0 - - webencodings=0.5.1=py_1 - - websocket-client=1.6.1=pyhd8ed1ab_0 - - wheel=0.40.0=pyhd8ed1ab_0 - - widgetsnbextension=4.0.7=pyhd8ed1ab_0 - - win_inet_pton=1.1.0=pyhd8ed1ab_6 - - winpty=0.4.3=4 - - wrapt=1.15.0=py311ha68e1ae_0 - - xerces-c=3.2.4=h63175ca_2 - - xorg-libxau=1.0.11=hcd874cb_0 - - xorg-libxdmcp=1.1.3=hcd874cb_0 - - xyzservices=2023.5.0=pyhd8ed1ab_1 - - xz=5.2.6=h8d14728_0 - - yaml=0.2.5=h8ffe710_2 - - zeromq=4.3.4=h0e60522_1 - - zipp=3.15.0=pyhd8ed1ab_0 - - zlib=1.2.13=hcfcfb64_5 - - zstandard=0.19.0=py311he5d195f_2 - - zstd=1.5.2=h12be248_6 -prefix: C:\Users\Geoff\mambaforge\envs\ox +name: ox +channels: + - conda-forge +dependencies: + - access=1.1.8=pyhd8ed1ab_0 + - affine=2.4.0=pyhd8ed1ab_0 + - alabaster=0.7.13=pyhd8ed1ab_0 + - amply=0.1.6=pyhd8ed1ab_0 + - anyio=3.7.0=pyhd8ed1ab_1 + - argon2-cffi=21.3.0=pyhd8ed1ab_0 + - argon2-cffi-bindings=21.2.0=py311ha68e1ae_3 + - asttokens=2.2.1=pyhd8ed1ab_0 + - async-lru=2.0.2=pyhd8ed1ab_0 + - attrs=23.1.0=pyh71513ae_1 + - autopep8=2.0.2=pyhd8ed1ab_0 + - babel=2.12.1=pyhd8ed1ab_1 + - backcall=0.2.0=pyh9f0ad1d_0 + - backports=1.0=pyhd8ed1ab_3 + - backports.functools_lru_cache=1.6.5=pyhd8ed1ab_0 + - beautifulsoup4=4.12.2=pyha770c72_0 + - black=23.3.0=py311h1ea47a8_1 + - bleach=6.0.0=pyhd8ed1ab_0 + - blinker=1.6.2=pyhd8ed1ab_0 + - blosc=1.21.4=hdccc3a2_0 + - bokeh=3.2.0=pyhd8ed1ab_0 + - boltons=23.0.0=pyhd8ed1ab_0 + - boolean.py=3.7=py_0 + - boost-cpp=1.78.0=h9f4b32c_3 + - bottleneck=1.3.7=py311h59ca53f_0 + - branca=0.6.0=pyhd8ed1ab_0 + - brotli=1.0.9=hcfcfb64_9 + - brotli-bin=1.0.9=hcfcfb64_9 + - bzip2=1.0.8=h8ffe710_4 + - ca-certificates=2023.5.7=h56e8100_0 + - cairo=1.16.0=hdecc03f_1016 + - cartopy=0.21.1=py311h178a126_1 + - certifi=2023.5.7=pyhd8ed1ab_0 + - cffi=1.15.1=py311h7d9ee11_3 + - cfgv=3.3.1=pyhd8ed1ab_0 + - cfitsio=4.2.0=h9ebe7e4_0 + - chardet=5.1.0=py311h1ea47a8_0 + - charset-normalizer=3.1.0=pyhd8ed1ab_0 + - click=8.1.3=win_pyhd8ed1ab_2 + - click-plugins=1.1.1=py_0 + - cligj=0.7.2=pyhd8ed1ab_1 + - cmarkgfm=0.8.0=py311ha68e1ae_2 + - colorama=0.4.6=pyhd8ed1ab_0 + - comm=0.1.3=pyhd8ed1ab_0 + - conda=23.5.0=py311h1ea47a8_1 + - conda-build=3.25.0=py311h1ea47a8_0 + - conda-forge-pinning=2023.06.28.15.07.21=hd8ed1ab_0 + - conda-index=0.2.3=pyhd8ed1ab_0 + - conda-package-handling=2.0.2=pyh38be061_0 + - conda-package-streaming=0.8.0=pyhd8ed1ab_0 + - conda-smithy=3.23.1=pyhd8ed1ab_0 + - contextily=1.3.0=pyhd8ed1ab_0 + - contourpy=1.1.0=py311h005e61a_0 + - coverage=7.2.7=py311ha68e1ae_0 + - cryptography=41.0.1=py311h28e9c30_0 + - curl=8.1.2=h68f0423_0 + - cycler=0.11.0=pyhd8ed1ab_0 + - cython=0.29.35=py311h12c1d0e_0 + - debugpy=1.6.7=py311h12c1d0e_0 + - decorator=5.1.1=pyhd8ed1ab_0 + - defusedxml=0.7.1=pyhd8ed1ab_0 + - deprecated=1.2.14=pyh1a96a4e_0 + - deprecation=2.1.0=pyh9f0ad1d_0 + - descartes=1.1.0=py_4 + - distlib=0.3.6=pyhd8ed1ab_0 + - docutils=0.19=py311h1ea47a8_1 + - editables=0.3=pyhd8ed1ab_0 + - entrypoints=0.4=pyhd8ed1ab_0 + - esda=2.4.3=pyhd8ed1ab_0 + - exceptiongroup=1.1.1=pyhd8ed1ab_0 + - executing=1.2.0=pyhd8ed1ab_0 + - expat=2.5.0=h63175ca_1 + - filelock=3.12.2=pyhd8ed1ab_0 + - fiona=1.9.4=py311h4e4dc46_0 + - flit-core=3.9.0=pyhd8ed1ab_0 + - folium=0.14.0=pyhd8ed1ab_0 + - font-ttf-dejavu-sans-mono=2.37=hab24e00_0 + - font-ttf-inconsolata=3.000=h77eed37_0 + - font-ttf-source-code-pro=2.038=h77eed37_0 + - font-ttf-ubuntu=0.83=hab24e00_0 + - fontconfig=2.14.2=hbde0cde_0 + - fonts-conda-ecosystem=1=0 + - fonts-conda-forge=1=0 + - fonttools=4.40.0=py311ha68e1ae_0 + - freetype=2.12.1=h546665d_1 + - freexl=1.0.6=h67ca5e6_1 + - furo=2023.5.20=pyhd8ed1ab_1 + - gdal=3.7.0=py311heaf1029_0 + - geographiclib=1.52=pyhd8ed1ab_0 + - geopandas=0.13.2=pyhd8ed1ab_1 + - geopandas-base=0.13.2=pyha770c72_1 + - geopy=2.3.0=pyhd8ed1ab_0 + - geos=3.11.2=h1537add_0 + - geotiff=1.7.1=h7222e44_8 + - gettext=0.21.1=h5728263_0 + - giddy=2.3.4=pyhd8ed1ab_0 + - git=2.41.0=h57928b3_0 + - gitdb=4.0.10=pyhd8ed1ab_0 + - gitpython=3.1.31=pyhd8ed1ab_0 + - glob2=0.7=py_0 + - glpk=5.0=h8ffe710_0 + - h11=0.14.0=pyhd8ed1ab_0 + - h2=4.1.0=pyhd8ed1ab_0 + - hatch=1.7.0=pyhd8ed1ab_0 + - hatchling=1.18.0=pyhd8ed1ab_0 + - hdf4=4.2.15=h1334946_6 + - hdf5=1.14.0=nompi_h918d9b7_103 + - hpack=4.0.0=pyh9f0ad1d_0 + - httpcore=0.17.2=pyhd8ed1ab_0 + - httpx=0.24.1=pyhd8ed1ab_0 + - hyperframe=6.0.1=pyhd8ed1ab_0 + - hyperlink=21.0.0=pyhd3deb0d_0 + - icu=72.1=h63175ca_0 + - identify=2.5.24=pyhd8ed1ab_0 + - idna=3.4=pyhd8ed1ab_0 + - imagesize=1.4.1=pyhd8ed1ab_0 + - importlib-metadata=6.7.0=pyha770c72_0 + - importlib_metadata=6.7.0=hd8ed1ab_0 + - importlib_resources=5.12.0=pyhd8ed1ab_0 + - inequality=1.0.0=py_0 + - iniconfig=2.0.0=pyhd8ed1ab_0 + - ipykernel=6.23.3=pyh6817e22_0 + - ipython=8.14.0=pyh08f2357_0 + - ipywidgets=8.0.6=pyhd8ed1ab_0 + - isodate=0.6.1=pyhd8ed1ab_0 + - jaraco.classes=3.2.3=pyhd8ed1ab_0 + - jedi=0.18.2=pyhd8ed1ab_0 + - jinja2=3.1.2=pyhd8ed1ab_1 + - joblib=1.3.0=pyhd8ed1ab_0 + - json5=0.9.5=pyh9f0ad1d_0 + - jsonpatch=1.32=pyhd8ed1ab_0 + - jsonpointer=2.0=py_0 + - jsonschema=4.17.3=pyhd8ed1ab_0 + - jupyter-lsp=2.2.0=pyhd8ed1ab_0 + - jupyter-server-mathjax=0.2.6=pyh5bfe37b_1 + - jupyter_client=8.3.0=pyhd8ed1ab_0 + - jupyter_core=5.3.1=py311h1ea47a8_0 + - jupyter_events=0.6.3=pyhd8ed1ab_0 + - jupyter_server=2.7.0=pyhd8ed1ab_0 + - jupyter_server_terminals=0.4.4=pyhd8ed1ab_1 + - jupyterlab=4.0.2=pyhd8ed1ab_0 + - jupyterlab_pygments=0.2.2=pyhd8ed1ab_0 + - jupyterlab_server=2.23.0=pyhd8ed1ab_0 + - jupyterlab_widgets=3.0.7=pyhd8ed1ab_1 + - kealib=1.5.1=hc8369a0_3 + - keyring=24.2.0=py311h1ea47a8_0 + - kiwisolver=1.4.4=py311h005e61a_1 + - krb5=1.20.1=heb0366b_0 + - lcms2=2.15=h3e3b177_1 + - lerc=4.0.0=h63175ca_0 + - libaec=1.0.6=h63175ca_1 + - libarchive=3.6.2=h27c7867_0 + - libblas=3.9.0=17_win64_openblas + - libbrotlicommon=1.0.9=hcfcfb64_9 + - libbrotlidec=1.0.9=hcfcfb64_9 + - libbrotlienc=1.0.9=hcfcfb64_9 + - libcblas=3.9.0=17_win64_openblas + - libcurl=8.1.2=h68f0423_0 + - libdeflate=1.18=hcfcfb64_0 + - libexpat=2.5.0=h63175ca_1 + - libffi=3.4.2=h8ffe710_5 + - libflang=5.0.0=h6538335_20180525 + - libgdal=3.7.0=hdf18f8d_0 + - libglib=2.76.3=he8f3873_0 + - libiconv=1.17=h8ffe710_0 + - libjpeg-turbo=2.1.5.1=hcfcfb64_0 + - libkml=1.3.0=hf2ab4e4_1015 + - liblapack=3.9.0=17_win64_openblas + - liblapacke=3.9.0=17_win64_openblas + - liblief=0.12.3=h63175ca_0 + - libnetcdf=4.9.2=nompi_hc664c2b_104 + - libopenblas=0.3.23=pthreads_hc140b1d_0 + - libpng=1.6.39=h19919ed_0 + - libpq=15.3=ha9684e8_0 + - libpysal=4.7.0=pyhd8ed1ab_0 + - librttopo=1.1.0=he1da8c1_13 + - libsodium=1.0.18=h8d14728_1 + - libspatialindex=1.9.3=h39d44d4_4 + - libspatialite=5.0.1=h50a8ebb_25 + - libsqlite=3.42.0=hcfcfb64_0 + - libssh2=1.11.0=h7dfc565_0 + - libtiff=4.5.1=h6c8260b_0 + - libwebp-base=1.3.0=hcfcfb64_0 + - libxcb=1.15=hcd874cb_0 + - libxml2=2.10.4=hc3477c8_0 + - libzip=1.9.2=h519de47_1 + - libzlib=1.2.13=hcfcfb64_5 + - license-expression=1.2=py_0 + - llvm-meta=5.0.0=0 + - llvmlite=0.40.1=py311h5bc0dda_0 + - lz4-c=1.9.4=hcfcfb64_0 + - lzo=2.10=he774522_1000 + - m2-msys2-runtime=2.5.0.17080.65c939c=3 + - m2-patch=2.7.5=2 + - m2w64-gcc-libgfortran=5.3.0=6 + - m2w64-gcc-libs=5.3.0=7 + - m2w64-gcc-libs-core=5.3.0=7 + - m2w64-gmp=6.1.0=2 + - m2w64-libwinpthread-git=5.0.0.4634.697f757=2 + - mapclassify=2.5.0=pyhd8ed1ab_1 + - markdown-it-py=3.0.0=pyhd8ed1ab_0 + - markupsafe=2.1.3=py311ha68e1ae_0 + - matplotlib-base=3.7.1=py311h6e989c2_0 + - matplotlib-inline=0.1.6=pyhd8ed1ab_0 + - mdurl=0.1.0=pyhd8ed1ab_0 + - menuinst=1.4.19=py311h1ea47a8_1 + - mercantile=1.2.1=pyhd8ed1ab_0 + - mgwr=2.1.2=py_0 + - mistune=3.0.0=pyhd8ed1ab_0 + - momepy=0.6.0=pyhd8ed1ab_1 + - more-itertools=9.1.0=pyhd8ed1ab_0 + - mpir=3.0.0=he025d50_1002 + - mpmath=1.3.0=pyhd8ed1ab_0 + - msrest=0.6.21=pyh44b312d_0 + - msys2-conda-epoch=20160418=1 + - munch=3.0.0=pyhd8ed1ab_0 + - munkres=1.1.4=pyh9f0ad1d_0 + - mypy_extensions=1.0.0=pyha770c72_0 + - nbclient=0.8.0=pyhd8ed1ab_0 + - nbconvert-core=7.6.0=pyhd8ed1ab_0 + - nbdime=3.2.1=pyhd8ed1ab_0 + - nbformat=5.9.0=pyhd8ed1ab_0 + - nbqa=1.7.0=pyhd8ed1ab_1 + - nest-asyncio=1.5.6=pyhd8ed1ab_0 + - networkx=3.1=pyhd8ed1ab_0 + - nodeenv=1.8.0=pyhd8ed1ab_0 + - nomkl=1.0=h5ca1d4c_0 + - notebook-shim=0.2.3=pyhd8ed1ab_0 + - numba=0.57.1=py311h2c0921f_0 + - numexpr=2.8.4=py311h0aebda5_100 + - numpy=1.24.4=py311h0b4df5a_0 + - oauthlib=3.2.2=pyhd8ed1ab_0 + - openjpeg=2.5.0=ha2aaf27_2 + - openmp=5.0.0=vc14_1 + - openssl=3.1.1=hcfcfb64_1 + - osmnx=1.5.0=pyhd8ed1ab_0 + - overrides=7.3.1=pyhd8ed1ab_0 + - packaging=23.1=pyhd8ed1ab_0 + - pandas=2.0.2=py311hf63dbb6_0 + - pandocfilters=1.5.0=pyhd8ed1ab_0 + - parso=0.8.3=pyhd8ed1ab_0 + - pathspec=0.11.1=pyhd8ed1ab_0 + - patsy=0.5.3=pyhd8ed1ab_0 + - pcre2=10.40=h17e33f8_0 + - pexpect=4.8.0=pyh1a96a4e_2 + - pickleshare=0.7.5=py_1003 + - pillow=9.5.0=py311hde623f7_1 + - pip=23.1.2=pyhd8ed1ab_0 + - pixman=0.40.0=h8ffe710_0 + - pkginfo=1.9.6=pyhd8ed1ab_0 + - pkgutil-resolve-name=1.3.10=pyhd8ed1ab_0 + - platformdirs=3.8.0=pyhd8ed1ab_0 + - pluggy=1.2.0=pyhd8ed1ab_0 + - pointpats=2.3.0=pyhd8ed1ab_0 + - pooch=1.7.0=pyha770c72_3 + - poppler=23.05.0=h45d20d0_1 + - poppler-data=0.4.12=hd8ed1ab_0 + - postgresql=15.3=hd87cd2b_0 + - pre-commit=3.3.3=pyha770c72_0 + - proj=9.2.0=heca977f_0 + - prometheus_client=0.17.0=pyhd8ed1ab_0 + - prompt-toolkit=3.0.38=pyha770c72_0 + - prompt_toolkit=3.0.38=hd8ed1ab_0 + - psutil=5.9.5=py311ha68e1ae_0 + - psycopg2=2.9.3=py311hb12f294_2 + - pthread-stubs=0.4=hcd874cb_1001 + - ptyprocess=0.7.0=pyhd3deb0d_0 + - pulp=2.7.0=py311h1ea47a8_0 + - pure_eval=0.2.2=pyhd8ed1ab_0 + - py-lief=0.12.3=py311h12c1d0e_0 + - pycodestyle=2.10.0=pyhd8ed1ab_0 + - pycosat=0.6.4=py311ha68e1ae_1 + - pycparser=2.21=pyhd8ed1ab_0 + - pycryptodome=3.18.0=py311ha68e1ae_0 + - pygithub=1.58.0=pyh1a96a4e_0 + - pygments=2.15.1=pyhd8ed1ab_0 + - pyjwt=2.7.0=pyhd8ed1ab_0 + - pynacl=1.5.0=py311hd53affc_2 + - pyopenssl=23.2.0=pyhd8ed1ab_1 + - pyparsing=3.1.0=pyhd8ed1ab_0 + - pyperclip=1.8.2=pyhd8ed1ab_2 + - pyproj=3.6.0=py311h095e9de_0 + - pyrsistent=0.19.3=py311ha68e1ae_0 + - pysal=23.1=pyhd8ed1ab_0 + - pyshp=2.3.1=pyhd8ed1ab_0 + - pysocks=1.7.1=pyh0701188_6 + - pytest=7.4.0=pyhd8ed1ab_0 + - pytest-cov=4.1.0=pyhd8ed1ab_0 + - python=3.11.4=h2628c8c_0_cpython + - python-dateutil=2.8.2=pyhd8ed1ab_0 + - python-fastjsonschema=2.17.1=pyhd8ed1ab_0 + - python-igraph=0.10.4=py311h8db4363_0 + - python-json-logger=2.0.7=pyhd8ed1ab_0 + - python-libarchive-c=4.0=py311h1ea47a8_2 + - python-tzdata=2023.3=pyhd8ed1ab_0 + - python_abi=3.11=3_cp311 + - pytz=2023.3=pyhd8ed1ab_0 + - pywin32=304=py311h12c1d0e_2 + - pywin32-ctypes=0.2.2=py311h1ea47a8_0 + - pywinpty=2.0.10=py311h12c1d0e_0 + - pyyaml=6.0=py311ha68e1ae_5 + - pyzmq=25.1.0=py311h7b3f143_0 + - quantecon=0.5.3=pyhd8ed1ab_0 + - rasterio=1.3.7=py311h826718d_1 + - rasterstats=0.19.0=pyhd8ed1ab_0 + - readme_renderer=40.0=pyhd8ed1ab_0 + - requests=2.31.0=pyhd8ed1ab_0 + - requests-oauthlib=1.3.1=pyhd8ed1ab_0 + - requests-toolbelt=1.0.0=pyhd8ed1ab_0 + - rfc3339-validator=0.1.4=pyhd8ed1ab_0 + - rfc3986=2.0.0=pyhd8ed1ab_0 + - rfc3986-validator=0.1.1=pyh9f0ad1d_0 + - rich=13.4.2=pyhd8ed1ab_0 + - ripgrep=13.0.0=h7f3b576_2 + - rtree=1.0.1=py311hcacb13a_1 + - ruamel.yaml=0.17.32=py311ha68e1ae_0 + - ruamel.yaml.clib=0.2.7=py311ha68e1ae_1 + - ruff=0.0.275=py311hc14472d_0 + - scikit-learn=1.2.2=py311h142b183_2 + - scipy=1.11.0=py311h37ff6ca_0 + - scrypt=0.8.18=py311h4be8fce_4 + - seaborn=0.12.2=hd8ed1ab_0 + - seaborn-base=0.12.2=pyhd8ed1ab_0 + - segregation=2.4.2=pyhd8ed1ab_0 + - send2trash=1.8.2=pyh08f2357_0 + - setuptools=68.0.0=pyhd8ed1ab_0 + - shapely=2.0.1=py311h343093d_1 + - shellingham=1.5.1=pyhd8ed1ab_0 + - simplejson=3.19.1=py311ha68e1ae_0 + - six=1.16.0=pyh6c4a22f_0 + - smmap=3.0.5=pyh44b312d_0 + - snappy=1.1.10=hfb803bf_0 + - sniffio=1.3.0=pyhd8ed1ab_0 + - snowballstemmer=2.2.0=pyhd8ed1ab_0 + - snuggs=1.4.7=py_0 + - soupsieve=2.3.2.post1=pyhd8ed1ab_0 + - spaghetti=1.7.4=pyhd8ed1ab_0 + - spglm=1.0.8=py_0 + - sphinx=6.2.1=pyhd8ed1ab_0 + - sphinx-basic-ng=1.0.0b1=pyhd8ed1ab_1 + - sphinxcontrib-applehelp=1.0.4=pyhd8ed1ab_0 + - sphinxcontrib-devhelp=1.0.2=py_0 + - sphinxcontrib-htmlhelp=2.0.1=pyhd8ed1ab_0 + - sphinxcontrib-jsmath=1.0.1=py_0 + - sphinxcontrib-qthelp=1.0.3=py_0 + - sphinxcontrib-serializinghtml=1.1.5=pyhd8ed1ab_2 + - spint=1.0.7=pyhd8ed1ab_0 + - splot=1.1.5.post1=pyhd8ed1ab_0 + - spopt=0.5.0=pyhd8ed1ab_0 + - spreg=1.3.2=pyhd8ed1ab_0 + - spvcm=0.3.0=py_0 + - sqlite=3.42.0=hcfcfb64_0 + - stack_data=0.6.2=pyhd8ed1ab_0 + - statsmodels=0.14.0=py311h59ca53f_1 + - suitesparse=5.4.0=h5d0cbe0_1 + - sympy=1.12=pyh04b8f61_3 + - terminado=0.17.0=pyh08f2357_0 + - texttable=1.6.7=pyhd8ed1ab_0 + - threadpoolctl=3.1.0=pyh8a188c0_0 + - tiledb=2.13.2=h3132609_0 + - tinycss2=1.2.1=pyhd8ed1ab_0 + - tk=8.6.12=h8ffe710_0 + - tobler=0.10=pyhd8ed1ab_0 + - tokenize-rt=5.1.0=pyhd8ed1ab_0 + - toml=0.10.2=pyhd8ed1ab_0 + - tomli=2.0.1=pyhd8ed1ab_0 + - tomli-w=1.0.0=pyhd8ed1ab_0 + - tomlkit=0.11.8=pyha770c72_0 + - toolz=0.12.0=pyhd8ed1ab_0 + - tornado=6.3.2=py311ha68e1ae_0 + - tqdm=4.65.0=pyhd8ed1ab_1 + - traitlets=5.9.0=pyhd8ed1ab_0 + - trove-classifiers=2023.5.24=pyhd8ed1ab_0 + - twine=4.0.2=pyhd8ed1ab_0 + - typing-extensions=4.6.3=hd8ed1ab_0 + - typing_extensions=4.6.3=pyha770c72_0 + - typing_utils=0.1.0=pyhd8ed1ab_0 + - tzdata=2023c=h71feb2d_0 + - ucrt=10.0.22621.0=h57928b3_0 + - ukkonen=1.0.1=py311h005e61a_3 + - urllib3=2.0.3=pyhd8ed1ab_0 + - userpath=1.7.0=pyhd8ed1ab_0 + - vc=14.3=h64f974e_16 + - vc14_runtime=14.36.32532=hfdfe4a8_16 + - virtualenv=20.23.1=pyhd8ed1ab_0 + - vs2015_runtime=14.36.32532=h05e6639_16 + - vsts-python-api=0.1.25=pyhd8ed1ab_1 + - wcwidth=0.2.6=pyhd8ed1ab_0 + - webencodings=0.5.1=py_1 + - websocket-client=1.6.1=pyhd8ed1ab_0 + - wheel=0.40.0=pyhd8ed1ab_0 + - widgetsnbextension=4.0.7=pyhd8ed1ab_0 + - win_inet_pton=1.1.0=pyhd8ed1ab_6 + - winpty=0.4.3=4 + - wrapt=1.15.0=py311ha68e1ae_0 + - xerces-c=3.2.4=h63175ca_2 + - xorg-libxau=1.0.11=hcd874cb_0 + - xorg-libxdmcp=1.1.3=hcd874cb_0 + - xyzservices=2023.5.0=pyhd8ed1ab_1 + - xz=5.2.6=h8d14728_0 + - yaml=0.2.5=h8ffe710_2 + - zeromq=4.3.4=h0e60522_1 + - zipp=3.15.0=pyhd8ed1ab_0 + - zlib=1.2.13=hcfcfb64_5 + - zstandard=0.19.0=py311he5d195f_2 + - zstd=1.5.2=h12be248_6 +prefix: C:\Users\Geoff\mambaforge\envs\ox diff --git a/osmnx/_downloader.py b/osmnx/_downloader.py index f7c8f8655..76630442c 100644 --- a/osmnx/_downloader.py +++ b/osmnx/_downloader.py @@ -102,7 +102,8 @@ def _get_osm_filter(network_type): if network_type in filters: osm_filter = filters[network_type] else: # pragma: no cover - raise ValueError(f"Unrecognized network_type {network_type!r}") + msg = f"Unrecognized network_type {network_type!r}" + raise ValueError(msg) return osm_filter @@ -136,8 +137,9 @@ def _save_to_cache(url, response_json, sc): ------- None """ + HTTP_OK = 200 if settings.use_cache: - if sc != 200: + if sc != HTTP_OK: utils.log(f"Did not save to cache because status code is {sc}") elif response_json is None: @@ -636,7 +638,8 @@ def _retrieve_osm_element(query, by_osmid=False, limit=1, polygon_geojson=1): for key in sorted(query): params[key] = query[key] else: # pragma: no cover - raise TypeError("query must be a dict or a string") + msg = "query must be a dict or a string" + raise TypeError(msg) # request the URL, return the JSON response_json = _nominatim_request(params=params, request_type=request_type) @@ -664,7 +667,8 @@ def _nominatim_request(params, request_type="search", pause=1, error_pause=60): response_json : dict """ if request_type not in {"search", "reverse", "lookup"}: # pragma: no cover - raise ValueError('Nominatim request_type must be "search", "reverse", or "lookup"') + msg = 'Nominatim request_type must be "search", "reverse", or "lookup"' + raise ValueError(msg) # resolve url to same IP even if there is server round-robin redirecting _config_dns(settings.nominatim_endpoint.rstrip("/")) @@ -720,9 +724,8 @@ def _nominatim_request(params, request_type="search", pause=1, error_pause=60): else: # else, this was an unhandled status code, throw an exception utils.log(f"{domain} returned {sc}", level=lg.ERROR) - raise Exception( - f"Server returned:\n{response} {response.reason}\n{response.text}" - ) from e + msg = f"Server returned:\n{response} {response.reason}\n{response.text}" + raise Exception(msg) from e _save_to_cache(prepared_url, response_json, sc) return response_json @@ -800,9 +803,8 @@ def _overpass_request(data, pause=None, error_pause=60): else: # else, this was an unhandled status code, throw an exception utils.log(f"{domain} returned {sc}", level=lg.ERROR) - raise Exception( - f"Server returned\n{response} {response.reason}\n{response.text}" - ) from e + msg = f"Server returned\n{response} {response.reason}\n{response.text}" + raise Exception(msg) from e _save_to_cache(prepared_url, response_json, sc) return response_json diff --git a/osmnx/bearing.py b/osmnx/bearing.py index 97ed05f0b..fd5aeb25a 100644 --- a/osmnx/bearing.py +++ b/osmnx/bearing.py @@ -86,7 +86,8 @@ def add_edge_bearings(G, precision=None): ) if projection.is_projected(G.graph["crs"]): # pragma: no cover - raise ValueError("graph must be unprojected to add edge bearings") + msg = "graph must be unprojected to add edge bearings" + raise ValueError(msg) # extract edge IDs and corresponding coordinates from their nodes uvk = [(u, v, k) for u, v, k in G.edges if u != v] @@ -137,7 +138,8 @@ def orientation_entropy(Gu, num_bins=36, min_length=0, weight=None): """ # check if we were able to import scipy if scipy is None: # pragma: no cover - raise ImportError("scipy must be installed to calculate entropy") + msg = "scipy must be installed to calculate entropy" + raise ImportError(msg) bin_counts, _ = _bearings_distribution(Gu, num_bins, min_length, weight) return scipy.stats.entropy(bin_counts) @@ -168,7 +170,8 @@ def _extract_edge_bearings(Gu, min_length=0, weight=None): the graph's bidirectional edge bearings """ if nx.is_directed(Gu) or projection.is_projected(Gu.graph["crs"]): # pragma: no cover - raise ValueError("graph must be undirected and unprojected to analyze edge bearings") + msg = "graph must be undirected and unprojected to analyze edge bearings" + raise ValueError(msg) bearings = [] for u, v, data in Gu.edges(data=True): # ignore self-loops and any edges below min_length diff --git a/osmnx/distance.py b/osmnx/distance.py index 9709cae13..1f60e8d2e 100644 --- a/osmnx/distance.py +++ b/osmnx/distance.py @@ -159,9 +159,8 @@ def add_edge_lengths(G, precision=None, edges=None): # ensure all coordinates can be converted to float and are non-null assert not np.isnan(c.astype(float)).any() except (AssertionError, KeyError) as e: # pragma: no cover - raise ValueError( - "some edges missing nodes, possibly due to input data clipping issue" - ) from e + msg = "some edges missing nodes, possibly due to input data clipping issue" + raise ValueError(msg) from e # calculate great circle distances, round, and fill nulls with zeros dists = great_circle_vec(c[:, 0], c[:, 1], c[:, 2], c[:, 3]).round(precision) @@ -213,20 +212,23 @@ def nearest_nodes(G, X, Y, return_dist=False): Y = np.array([Y]) if np.isnan(X).any() or np.isnan(Y).any(): # pragma: no cover - raise ValueError("`X` and `Y` cannot contain nulls") + msg = "`X` and `Y` cannot contain nulls" + raise ValueError(msg) nodes = utils_graph.graph_to_gdfs(G, edges=False, node_geometry=False)[["x", "y"]] if projection.is_projected(G.graph["crs"]): # if projected, use k-d tree for euclidean nearest-neighbor search if cKDTree is None: # pragma: no cover - raise ImportError("scipy must be installed to search a projected graph") + msg = "scipy must be installed to search a projected graph" + raise ImportError(msg) dist, pos = cKDTree(nodes).query(np.array([X, Y]).T, k=1) nn = nodes.index[pos] else: # if unprojected, use ball tree for haversine nearest-neighbor search if BallTree is None: # pragma: no cover - raise ImportError("scikit-learn must be installed to search an unprojected graph") + msg = "scikit-learn must be installed to search an unprojected graph" + raise ImportError(msg) # haversine requires lat, lng coords in radians nodes_rad = np.deg2rad(nodes[["y", "x"]]) points_rad = np.deg2rad(np.array([Y, X]).T) @@ -286,7 +288,8 @@ def nearest_edges(G, X, Y, interpolate=None, return_dist=False): Y = np.array([Y]) if np.isnan(X).any() or np.isnan(Y).any(): # pragma: no cover - raise ValueError("`X` and `Y` cannot contain nulls") + msg = "`X` and `Y` cannot contain nulls" + raise ValueError(msg) geoms = utils_graph.graph_to_gdfs(G, nodes=False)["geometry"] # if no interpolation distance was provided @@ -320,14 +323,16 @@ def nearest_edges(G, X, Y, interpolate=None, return_dist=False): if projection.is_projected(G.graph["crs"]): # if projected, use k-d tree for euclidean nearest-neighbor search if cKDTree is None: # pragma: no cover - raise ImportError("scipy must be installed to search a projected graph") + msg = "scipy must be installed to search a projected graph" + raise ImportError(msg) dist, pos = cKDTree(vertices).query(np.array([X, Y]).T, k=1) ne = vertices.index[pos] else: # if unprojected, use ball tree for haversine nearest-neighbor search if BallTree is None: # pragma: no cover - raise ImportError("scikit-learn must be installed to search an unprojected graph") + msg = "scikit-learn must be installed to search an unprojected graph" + raise ImportError(msg) # haversine requires lat, lng coords in radians vertices_rad = np.deg2rad(vertices[["y", "x"]]) points_rad = np.deg2rad(np.array([Y, X]).T) @@ -422,7 +427,8 @@ def shortest_path(G, orig, dest, weight="length", cpus=1): # if both orig and dest are iterables, ensure they have same lengths elif hasattr(orig, "__iter__") and hasattr(dest, "__iter__"): if len(orig) != len(dest): # pragma: no cover - raise ValueError("orig and dest must contain same number of elements") + msg = "orig and dest must contain same number of elements" + raise ValueError(msg) if cpus is None: cpus = mp.cpu_count() @@ -446,7 +452,8 @@ def shortest_path(G, orig, dest, weight="length", cpus=1): # if only one of orig or dest is iterable and the other is not else: # pragma: no cover - raise ValueError("orig and dest must either both be iterable or neither must be iterable") + msg = "orig and dest must either both be iterable or neither must be iterable" + raise ValueError(msg) def k_shortest_paths(G, orig, dest, k, weight="length"): @@ -505,4 +512,5 @@ def _verify_edge_attribute(G, attr): if np.isnan(values_float).any(): warn(f"The attribute {attr!r} is missing or null on some edges.", stacklevel=2) except ValueError as e: - raise ValueError(f"The edge attribute {attr!r} contains non-numeric values.") from e + msg = f"The edge attribute {attr!r} contains non-numeric values." + raise ValueError(msg) from e diff --git a/osmnx/elevation.py b/osmnx/elevation.py index da7c7a096..dfdd21896 100644 --- a/osmnx/elevation.py +++ b/osmnx/elevation.py @@ -74,7 +74,8 @@ def add_node_elevations_raster(G, filepath, band=1, cpus=None): graph with node elevation attributes """ if rasterio is None or gdal is None: # pragma: no cover - raise ImportError("gdal and rasterio must be installed to query raster files") + msg = "gdal and rasterio must be installed to query raster files" + raise ImportError(msg) if cpus is None: cpus = mp.cpu_count() @@ -160,6 +161,7 @@ def add_node_elevations_google( "the `precision` parameter is deprecated and will be removed in a future release", stacklevel=2, ) + HTTP_OK = 200 # make a pandas series of all the nodes' coordinates as 'lat,lng' # round coordinates to 5 decimal places (approx 1 meter) to be able to fit @@ -187,22 +189,20 @@ def add_node_elevations_google( utils.log(f"Requesting node elevations: {url}") time.sleep(pause_duration) response = requests.get(url) - if response.status_code == 200: + if response.status_code == HTTP_OK: response_json = response.json() _downloader._save_to_cache(url, response_json, response.status_code) else: - raise Exception( - f"Server responded with {response.status_code}: {response.reason} \n{response.json()}" - ) + msg = f"Server responded with {response.status_code}: {response.reason} \n{response.json()}" + raise Exception(msg) # append these elevation results to the list of all results results.extend(response_json["results"]) # sanity check that all our vectors have the same number of elements if not (len(results) == len(G) == len(node_points)): - raise Exception( - f"Graph has {len(G)} nodes but we received {len(results)} results. \n{response_json}" - ) + msg = f"Graph has {len(G)} nodes but we received {len(results)} results. \n{response_json}" + raise Exception(msg) else: utils.log(f"Graph has {len(G)} nodes and we received {len(results)} results.") diff --git a/osmnx/features.py b/osmnx/features.py index e6cb14f00..2e0a0c33c 100644 --- a/osmnx/features.py +++ b/osmnx/features.py @@ -269,7 +269,8 @@ def features_from_place(query, tags, which_result=None, buffer_dist=None): # if it is a list, it contains multiple places to get gdf_place = geocoder.geocode_to_gdf(query, buffer_dist=buffer_dist) else: # pragma: no cover - raise TypeError("query must be dict, string, or list of strings") + msg = "query must be dict, string, or list of strings" + raise TypeError(msg) # extract the geometry from the GeoDataFrame to use in API query polygon = gdf_place["geometry"].unary_union @@ -314,14 +315,16 @@ def features_from_polygon(polygon, tags): """ # verify that the geometry is valid and a Polygon/MultiPolygon if not polygon.is_valid: - raise ValueError("The geometry of `polygon` is invalid") + msg = "The geometry of `polygon` is invalid" + raise ValueError(msg) if not isinstance(polygon, (Polygon, MultiPolygon)): - raise TypeError( - "Boundaries must be a shapely Polygon or MultiPolygon. If you requested " - "features from place name, make sure your query resolves to a Polygon or " - "MultiPolygon, and not some other geometry, like a Point. See OSMnx " - "documentation for details." + msg = ( + "Boundaries must be a shapely Polygon or MultiPolygon. If you " + "requested features from place name, make sure your query resolves " + "to a Polygon or MultiPolygon, and not some other geometry, like a " + "Point. See OSMnx documentation for details." ) + raise TypeError(msg) # download the data from OSM response_jsons = _downloader._osm_features_download(polygon, tags) @@ -549,7 +552,7 @@ def _parse_node_to_point(element): return point -def _parse_way_to_linestring_or_polygon(element, coords, polygon_features=_POLYGON_FEATURES): +def _parse_way_to_linestring_or_polygon(element, coords): """ Parse open LineString, closed LineString or Polygon from OSM 'way'. @@ -562,8 +565,6 @@ def _parse_way_to_linestring_or_polygon(element, coords, polygon_features=_POLYG element type "way" from overpass response JSON coords : dict dict of node IDs and their latitude/longitude coordinates - polygon_features : dict - dict for determining whether closed ways are LineStrings or Polygons Returns ------- @@ -883,24 +884,25 @@ def _subtract_inner_polygons_from_outer_polygons(element, outer_polygons, inner_ # loop through the outer polygons subtracting the inner polygons and # appending to the list for outer_polygon in outer_polygons: + outer_polygon_diff = outer_polygon for inner_polygon in inner_polygons: if inner_polygon.within(outer_polygon): try: - outer_polygon = outer_polygon.difference(inner_polygon) + outer_polygon_diff = outer_polygon.difference(inner_polygon) except TopologicalError: # pragma: no cover utils.log( - f"relation https://www.openstreetmap.org/relation/{element['id']} caused" - " a TopologicalError, trying with zero buffer." + f"relation https://www.openstreetmap.org/relation/{element['id']} " + "caused a TopologicalError, trying with zero buffer." ) - outer_polygon = outer_polygon.buffer(0).difference(inner_polygon.buffer(0)) + outer_polygon_diff = outer_polygon.buffer(0).difference(inner_polygon.buffer(0)) # note: .buffer(0) can return either a Polygon or MultiPolygon # if it returns a MultiPolygon we need to extract the component # sub Polygons to add to outer_polygons_with_holes - if outer_polygon.geom_type == "Polygon": - outer_polygons_with_holes.append(outer_polygon) - elif outer_polygon.geom_type == "MultiPolygon": - outer_polygons_with_holes.extend(list(outer_polygon.geoms)) + if outer_polygon_diff.geom_type == "Polygon": + outer_polygons_with_holes.append(outer_polygon_diff) + elif outer_polygon_diff.geom_type == "MultiPolygon": + outer_polygons_with_holes.extend(list(outer_polygon_diff.geoms)) # if only one polygon with holes was created, return that single polygon if len(outer_polygons_with_holes) == 1: diff --git a/osmnx/folium.py b/osmnx/folium.py index 05bb30733..96bef8dd5 100644 --- a/osmnx/folium.py +++ b/osmnx/folium.py @@ -153,7 +153,8 @@ def _plot_folium(gdf, m, popup_attribute, tiles, zoom, fit_bounds, **kwargs): """ # check if we were able to import folium successfully if folium is None: # pragma: no cover - raise ImportError("folium must be installed to use this optional feature") + msg = "folium must be installed to use this optional feature" + raise ImportError(msg) # get centroid x, y = gdf.unary_union.centroid.xy diff --git a/osmnx/geocoder.py b/osmnx/geocoder.py index 5421c7287..20b741714 100644 --- a/osmnx/geocoder.py +++ b/osmnx/geocoder.py @@ -50,7 +50,8 @@ def geocode(query): utils.log(f"Geocoded {query!r} to {point}") return point else: - raise ValueError(f"Nominatim could not geocode query {query!r}") + msg = f"Nominatim could not geocode query {query!r}" + raise ValueError(msg) def geocode_to_gdf(query, which_result=None, by_osmid=False, buffer_dist=None): @@ -95,7 +96,8 @@ def geocode_to_gdf(query, which_result=None, by_osmid=False, buffer_dist=None): a GeoDataFrame with one row for each query """ if not isinstance(query, (str, dict, list)): # pragma: no cover - raise ValueError("query must be a string or dict or list") + msg = "query must be a string or dict or list" + raise ValueError(msg) # if caller passed a list of queries but a scalar which_result value, then # turn which_result into a list with same length as query list @@ -110,12 +112,14 @@ def geocode_to_gdf(query, which_result=None, by_osmid=False, buffer_dist=None): # ensure same length if len(query) != len(which_result): # pragma: no cover - raise ValueError("which_result length must equal query length") + msg = "which_result length must equal query length" + raise ValueError(msg) # ensure query type of each item for q in query: if not isinstance(q, (str, dict)): # pragma: no cover - raise ValueError("each query must be a dict or a string") + msg = "each query must be a dict or a string" + raise ValueError(msg) # geocode each query and add to GeoDataFrame as a new row gdf = gpd.GeoDataFrame() @@ -169,7 +173,8 @@ def _geocode_query_to_gdf(query, which_result, by_osmid): # choose the right result from the JSON response if not results: # if no results were returned, raise error - raise ValueError(f"Nominatim geocoder returned 0 results for query {query!r}") + msg = f"Nominatim geocoder returned 0 results for query {query!r}" + raise ValueError(msg) elif by_osmid: # if searching by OSM ID, always take the first (ie, only) result @@ -241,4 +246,5 @@ def _get_first_polygon(results, query): return result # if we never found a polygon, throw an error - raise ValueError(f"Nominatim could not geocode query {query!r} to polygonal boundaries") + msg = f"Nominatim could not geocode query {query!r} to polygonal boundaries" + raise ValueError(msg) diff --git a/osmnx/graph.py b/osmnx/graph.py index 0f03684af..3b446d890 100644 --- a/osmnx/graph.py +++ b/osmnx/graph.py @@ -159,7 +159,8 @@ def graph_from_point( documentation for caveats. """ if dist_type not in {"bbox", "network"}: # pragma: no cover - raise ValueError('dist_type must be "bbox" or "network"') + msg = 'dist_type must be "bbox" or "network"' + raise ValueError(msg) # create bounding box from center point and distance in each direction north, south, east, west = utils_geo.bbox_from_point(center_point, dist) @@ -352,7 +353,8 @@ def graph_from_place( # if it is a list, it contains multiple places to get gdf_place = geocoder.geocode_to_gdf(query, buffer_dist=buffer_dist) else: # pragma: no cover - raise TypeError("query must be dict, string, or list of strings") + msg = "query must be dict, string, or list of strings" + raise TypeError(msg) # extract the geometry from the GeoDataFrame to use in API query polygon = gdf_place["geometry"].unary_union @@ -426,14 +428,16 @@ def graph_from_polygon( # verify that the geometry is valid and is a shapely Polygon/MultiPolygon # before proceeding if not polygon.is_valid: # pragma: no cover - raise ValueError("The geometry to query within is invalid") + msg = "The geometry to query within is invalid" + raise ValueError(msg) if not isinstance(polygon, (Polygon, MultiPolygon)): # pragma: no cover - raise TypeError( - "Geometry must be a shapely Polygon or MultiPolygon. If you requested " - "graph from place name, make sure your query resolves to a Polygon or " - "MultiPolygon, and not some other geometry, like a Point. See OSMnx " - "documentation for details." + msg = ( + "Geometry must be a shapely Polygon or MultiPolygon. If you " + "requested graph from place name, make sure your query resolves " + "to a Polygon or MultiPolygon, and not some other geometry, like " + "a Point. See OSMnx documentation for details." ) + raise TypeError(msg) if clean_periphery: # create a new buffered polygon 0.5km around the desired one @@ -585,13 +589,13 @@ def _create_graph(response_jsons, retain_all=False, bidirectional=False): utils.log(f"Retrieved all data from API in {response_count} request(s)") if settings.cache_only_mode: # pragma: no cover # after consuming all response_jsons in loop, raise exception to catch - raise CacheOnlyModeInterrupt("Interrupted because `settings.cache_only_mode=True`") + msg = "Interrupted because `settings.cache_only_mode=True`" + raise CacheOnlyModeInterrupt(msg) # ensure we got some node/way data back from the server request(s) if (len(nodes) == 0) and (len(paths) == 0): # pragma: no cover - raise EmptyOverpassResponse( - "No data elements in server response. Check query location/filters and log." - ) + msg = "No data elements in server response. Check query location/filters and log." + raise EmptyOverpassResponse(msg) # create the MultiDiGraph and set its graph-level attributes metadata = { diff --git a/osmnx/io.py b/osmnx/io.py index ee0ff881d..1cf6303b5 100644 --- a/osmnx/io.py +++ b/osmnx/io.py @@ -220,7 +220,8 @@ def load_graphml( if (filepath is None and graphml_str is None) or ( filepath is not None and graphml_str is not None ): # pragma: no cover - raise ValueError("You must pass one and only one of `filepath` or `graphml_str`.") + msg = "You must pass one and only one of `filepath` or `graphml_str`." + raise ValueError(msg) # specify default graph/node/edge attribute values' data types default_graph_dtypes = {"simplified": _convert_bool_string} @@ -494,7 +495,8 @@ def _convert_bool_string(value): elif isinstance(value, bool): return value else: # pragma: no cover - raise ValueError(f"invalid literal for boolean: {value!r}") + msg = f"invalid literal for boolean: {value!r}" + raise ValueError(msg) def _stringify_nonnumeric_cols(gdf): diff --git a/osmnx/osm_xml.py b/osmnx/osm_xml.py index 97cf1674a..c04fbabcd 100644 --- a/osmnx/osm_xml.py +++ b/osmnx/osm_xml.py @@ -315,12 +315,12 @@ def _append_nodes_xml_tree(root, gdf_nodes, node_attrs, node_tags): xml tree with nodes appended """ for _, row in gdf_nodes.iterrows(): - row = row.dropna().astype(str) - node = ET.SubElement(root, "node", attrib=row[node_attrs].to_dict()) + row_str = row.dropna().astype(str) + node = ET.SubElement(root, "node", attrib=row_str[node_attrs].to_dict()) for tag in node_tags: - if tag in row: - ET.SubElement(node, "tag", attrib={"k": tag, "v": row[tag]}) + if tag in row_str: + ET.SubElement(node, "tag", attrib={"k": tag, "v": row_str[tag]}) return root @@ -346,14 +346,13 @@ def _create_way_for_each_edge(root, gdf_edges, edge_attrs, edge_tags): osm way tags to include in output OSM XML """ for _, row in gdf_edges.iterrows(): - row = row.dropna().astype(str) - edge = ET.SubElement(root, "way", attrib=row[edge_attrs].to_dict()) - ET.SubElement(edge, "nd", attrib={"ref": row["u"]}) - ET.SubElement(edge, "nd", attrib={"ref": row["v"]}) + row_str = row.dropna().astype(str) + edge = ET.SubElement(root, "way", attrib=row_str[edge_attrs].to_dict()) + ET.SubElement(edge, "nd", attrib={"ref": row_str["u"]}) + ET.SubElement(edge, "nd", attrib={"ref": row_str["v"]}) for tag in edge_tags: - if tag in row: - ET.SubElement(edge, "tag", attrib={"k": tag, "v": row[tag]}) - return + if tag in row_str: + ET.SubElement(edge, "tag", attrib={"k": tag, "v": row_str[tag]}) def _append_merged_edge_attrs(xml_edge, sample_edge, all_edges_df, edge_tags, edge_tag_aggs): @@ -400,7 +399,6 @@ def _append_merged_edge_attrs(xml_edge, sample_edge, all_edges_df, edge_tags, ed "v": str(all_edges_df[tag].aggregate(agg)), }, ) - return def _append_nodes_as_edge_attrs(xml_edge, sample_edge, all_edges_df): @@ -429,7 +427,6 @@ def _append_nodes_as_edge_attrs(xml_edge, sample_edge, all_edges_df): ordered_nodes = [first_node] + ordered_nodes for node in ordered_nodes: ET.SubElement(xml_edge, "nd", attrib={"ref": str(node)}) - return def _append_edges_xml_tree(root, gdf_edges, edge_attrs, edge_tags, edge_tag_aggs, merge_edges): diff --git a/osmnx/plot.py b/osmnx/plot.py index 26857f118..656c3d7b8 100644 --- a/osmnx/plot.py +++ b/osmnx/plot.py @@ -17,10 +17,10 @@ # matplotlib is an optional dependency needed for visualization try: - import matplotlib.cm as cm - import matplotlib.colors as colors import matplotlib.pyplot as plt + from matplotlib import cm from matplotlib import colormaps + from matplotlib import colors except ImportError: # pragma: no cover cm = colors = plt = colormaps = None @@ -208,7 +208,8 @@ def plot_graph( max_node_size = max(node_size) if hasattr(node_size, "__iter__") else node_size max_edge_lw = max(edge_linewidth) if hasattr(edge_linewidth, "__iter__") else edge_linewidth if max_node_size <= 0 and max_edge_lw <= 0: # pragma: no cover - raise ValueError("Either node_size or edge_linewidth must be > 0 to plot something.") + msg = "Either node_size or edge_linewidth must be > 0 to plot something." + raise ValueError(msg) # create fig, ax as needed utils.log("Begin plotting the graph...") @@ -357,17 +358,21 @@ def plot_graph_routes(G, routes, route_colors="r", route_linewidths=4, **pgr_kwa # check for valid arguments if not all(isinstance(r, list) for r in routes): # pragma: no cover - raise ValueError("routes must be a list of route lists") - if len(routes) < 2: # pragma: no cover - raise ValueError("You must pass more than 1 route") + msg = "routes must be a list of route lists" + raise ValueError(msg) + if len(routes) <= 1: # pragma: no cover + msg = "You must pass more than 1 route" + raise ValueError(msg) if isinstance(route_colors, str): route_colors = [route_colors] * len(routes) if len(routes) != len(route_colors): # pragma: no cover - raise ValueError("route_colors list must have same length as routes") + msg = "route_colors list must have same length as routes" + raise ValueError(msg) if isinstance(route_linewidths, int): route_linewidths = [route_linewidths] * len(routes) if len(routes) != len(route_linewidths): # pragma: no cover - raise ValueError("route_linewidths list must have same length as routes") + msg = "route_linewidths list must have same length as routes" + raise ValueError(msg) # plot the graph and the first route override = {"route", "route_color", "route_linewidth", "show", "save", "close"} @@ -500,7 +505,8 @@ def plot_figure_ground( ) G = simplification.simplify_graph(G, strict=False) else: # pragma: no cover - raise ValueError("You must pass an address or lat-lng point or graph.") + msg = "You must pass an address or lat-lng point or graph." + raise ValueError(msg) # we need an undirected graph to find every edge incident on a node Gu = utils_graph.get_undirected(G) @@ -819,7 +825,8 @@ def _get_colors_by_value(vals, num_bins, cmap, start, stop, na_color, equal_size series labels are node/edge IDs and values are colors """ if len(vals) == 0: - raise ValueError("There are no attribute values.") + msg = "There are no attribute values." + raise ValueError(msg) if num_bins is None: # calculate min/max values based on start/stop and data range @@ -974,6 +981,5 @@ def _verify_mpl(): None """ if cm is None or colors is None or plt is None or colormaps is None: # pragma: no cover - raise ImportError( - "matplotlib must be installed as an optional dependency for visualization" - ) + msg = "matplotlib must be installed as an optional dependency for visualization" + raise ImportError(msg) diff --git a/osmnx/projection.py b/osmnx/projection.py index 5bd325b6b..4d553b217 100644 --- a/osmnx/projection.py +++ b/osmnx/projection.py @@ -88,7 +88,8 @@ def project_gdf(gdf, to_crs=None, to_latlong=False): the projected GeoDataFrame """ if gdf.crs is None or len(gdf) < 1: # pragma: no cover - raise ValueError("GeoDataFrame must have a valid CRS and cannot be empty") + msg = "GeoDataFrame must have a valid CRS and cannot be empty" + raise ValueError(msg) # if to_latlong is True, project the gdf to latlong if to_latlong: @@ -103,7 +104,8 @@ def project_gdf(gdf, to_crs=None, to_latlong=False): # otherwise, automatically project the gdf to UTM else: if is_projected(gdf.crs): # pragma: no cover - raise ValueError("Geometry must be unprojected to calculate UTM zone") + msg = "Geometry must be unprojected to calculate UTM zone" + raise ValueError(msg) # calculate approximate longitude of centroid of union of all geometries in gdf avg_lng = gdf["geometry"].representative_point().x.mean() diff --git a/osmnx/simplification.py b/osmnx/simplification.py index 872034e91..02ccbe4d4 100644 --- a/osmnx/simplification.py +++ b/osmnx/simplification.py @@ -57,7 +57,7 @@ def _is_endpoint(G, node, strict=True): return True # rule 3 - elif not (n == 2 and (d == 2 or d == 4)): + elif not (n == 2 and (d == 2 or d == 4)): # noqa: PLR2004 # else, if it does NOT have 2 neighbors AND either 2 or 4 directed # edges, it is an endpoint. either it has 1 or 3+ neighbors, in which # case it is a dead-end or an intersection of multiple streets or it has @@ -128,8 +128,7 @@ def _build_path(G, endpoint, endpoint_successor, endpoints): # 99%+ of the time there will be only 1 successor: add to path if len(successors) == 1: - successor = successors[0] - path.append(successor) + path.append(successors[0]) # handle relatively rare cases or OSM digitization quirks elif len(successors) == 0: @@ -149,7 +148,8 @@ def _build_path(G, endpoint, endpoint_successor, endpoints): # if successor has >1 successors, then successor must have # been an endpoint because you can go in 2 new directions. # this should never occur in practice - raise Exception(f"Unexpected simplify pattern failed near {successor}") + msg = f"Unexpected simplify pattern failed near {successor}" + raise Exception(msg) # if this successor is an endpoint, we've completed the path return path @@ -254,7 +254,8 @@ def simplify_graph(G, strict=True, remove_rings=True, track_merged=False): each simplified edge """ if "simplified" in G.graph and G.graph["simplified"]: # pragma: no cover - raise Exception("This graph has already been simplified, cannot simplify it again.") + msg = "This graph has already been simplified, cannot simplify it again." + raise Exception(msg) utils.log("Begin topologically simplifying the graph...") @@ -429,14 +430,12 @@ def consolidate_intersections( return G else: return _consolidate_intersections_rebuild_graph(G, tolerance, reconnect_edges) - + elif not G: + # if graph has no nodes, just return empty GeoSeries + return gpd.GeoSeries(crs=G.graph["crs"]) else: - if not G: - # if graph has no nodes, just return empty GeoSeries - return gpd.GeoSeries(crs=G.graph["crs"]) - else: - # return the centroids of the merged intersection polygons - return _merge_nodes_geometric(G, tolerance).centroid + # return the centroids of the merged intersection polygons + return _merge_nodes_geometric(G, tolerance).centroid def _merge_nodes_geometric(G, tolerance): diff --git a/osmnx/speed.py b/osmnx/speed.py index e4095c196..76fde32dc 100644 --- a/osmnx/speed.py +++ b/osmnx/speed.py @@ -115,11 +115,11 @@ def add_edge_speeds(G, hwy_speeds=None, fallback=None, precision=None, agg=np.me # all speeds will be null if edges had no preexisting maxspeed data and # caller did not pass in hwy_speeds or fallback arguments if pd.isnull(speed_kph).all(): - raise ValueError( - "this graph's edges have no preexisting `maxspeed` " - "attribute values so you must pass `hwy_speeds` or " - "`fallback` arguments." + msg = ( + "this graph's edges have no preexisting `maxspeed` attribute " + "values so you must pass `hwy_speeds` or `fallback` arguments." ) + raise ValueError(msg) # add speed kph attribute to graph edges edges["speed_kph"] = speed_kph.round(precision).values @@ -161,12 +161,13 @@ def add_edge_travel_times(G, precision=None): # verify edge length and speed_kph attributes exist and contain no nulls if not ("length" in edges.columns and "speed_kph" in edges.columns): # pragma: no cover - raise KeyError("all edges must have `length` and `speed_kph` attributes.") - else: - if ( - pd.isnull(edges["length"]).any() or pd.isnull(edges["speed_kph"]).any() - ): # pragma: no cover - raise ValueError("edge `length` and `speed_kph` values must be non-null.") + msg = "all edges must have `length` and `speed_kph` attributes." + raise KeyError(msg) + elif ( + pd.isnull(edges["length"]).any() or pd.isnull(edges["speed_kph"]).any() + ): # pragma: no cover + msg = "edge `length` and `speed_kph` values must be non-null." + raise ValueError(msg) # convert distance meters to km, and speed km per hour to km per second distance_km = edges["length"] / 1000 diff --git a/osmnx/stats.py b/osmnx/stats.py index ac5fe83f1..7fa05efdf 100644 --- a/osmnx/stats.py +++ b/osmnx/stats.py @@ -143,7 +143,8 @@ def street_segment_count(Gu): count of street segments in graph """ if nx.is_directed(Gu): # pragma: no cover - raise ValueError("`Gu` must be undirected") + msg = "`Gu` must be undirected" + raise ValueError(msg) return len(Gu.edges) @@ -162,7 +163,8 @@ def street_length_total(Gu): total length (meters) of streets in graph """ if nx.is_directed(Gu): # pragma: no cover - raise ValueError("`Gu` must be undirected") + msg = "`Gu` must be undirected" + raise ValueError(msg) return sum(d["length"] for u, v, d in Gu.edges(data=True)) @@ -200,7 +202,8 @@ def self_loop_proportion(Gu): proportion of graph edges that are self-loops """ if nx.is_directed(Gu): # pragma: no cover - raise ValueError("`Gu` must be undirected") + msg = "`Gu` must be undirected" + raise ValueError(msg) return sum(u == v for u, v, k in Gu.edges) / len(Gu.edges) @@ -223,7 +226,8 @@ def circuity_avg(Gu): the graph's average undirected edge circuity """ if nx.is_directed(Gu): # pragma: no cover - raise ValueError("`Gu` must be undirected") + msg = "`Gu` must be undirected" + raise ValueError(msg) # extract the edges' endpoint nodes' coordinates coords = np.array( diff --git a/osmnx/truncate.py b/osmnx/truncate.py index cbaa284f8..8eb7b1640 100644 --- a/osmnx/truncate.py +++ b/osmnx/truncate.py @@ -158,7 +158,8 @@ def truncate_graph_polygon( if not to_keep: # no graph nodes within the polygon: can't create a graph from that - raise ValueError("Found no graph nodes within the requested polygon") + msg = "Found no graph nodes within the requested polygon" + raise ValueError(msg) # now identify all nodes whose point geometries lie outside the polygon gs_nodes_outside_poly = gs_nodes[~gs_nodes.index.isin(to_keep)] diff --git a/osmnx/utils.py b/osmnx/utils.py index 24f00ba7d..7d30e90ed 100644 --- a/osmnx/utils.py +++ b/osmnx/utils.py @@ -31,14 +31,14 @@ def citation(style="bibtex"): None """ if style == "apa": - print( + msg = ( "Boeing, G. (2017). OSMnx: New Methods for Acquiring, Constructing, " "Analyzing, and Visualizing Complex Street Networks. Computers, " "Environment and Urban Systems, 65, 126-139. " "https://doi.org/10.1016/j.compenvurbsys.2017.05.004" ) elif style == "bibtex": - print( + msg = ( "@article{boeing_osmnx_2017,\n" " title = {{OSMnx}: {New} {Methods} for {Acquiring}, " "{Constructing}, {Analyzing}, and {Visualizing} {Complex} " @@ -53,14 +53,17 @@ def citation(style="bibtex"): "}" ) elif style == "ieee": - print( + msg = ( 'G. Boeing, "OSMnx: New Methods for Acquiring, Constructing, ' 'Analyzing, and Visualizing Complex Street Networks," Computers, ' "Environment and Urban Systems, vol. 65, pp. 126-139, 2017, " "doi: 10.1016/j.compenvurbsys.2017.05.004." ) else: # pragma: no cover - raise ValueError(f"unrecognized citation style {style!r}") + err_msg = f"unrecognized citation style {style!r}" + raise ValueError(err_msg) + + print(msg) # noqa: T201 def ts(style="datetime", template=None): @@ -88,7 +91,8 @@ def ts(style="datetime", template=None): elif style == "time": template = "{:%H:%M:%S}" else: # pragma: no cover - raise ValueError(f"unrecognized timestamp style {style!r}") + msg = f"unrecognized timestamp style {style!r}" + raise ValueError(msg) ts = template.format(dt.datetime.now()) return ts diff --git a/osmnx/utils_geo.py b/osmnx/utils_geo.py index 33818cc30..194ccd1e4 100644 --- a/osmnx/utils_geo.py +++ b/osmnx/utils_geo.py @@ -76,7 +76,8 @@ def interpolate_points(geom, dist): point = geom.interpolate(n / num_vert, normalized=True) yield point.x, point.y else: # pragma: no cover - raise TypeError(f"unhandled geometry type {geom.geom_type}") + msg = f"unhandled geometry type {geom.geom_type}" + raise TypeError(msg) def _round_polygon_coords(p, precision): @@ -234,7 +235,8 @@ def round_geometry_coords(geom, precision): return _round_multipolygon_coords(geom, precision) else: # pragma: no cover - raise TypeError(f"cannot round coordinates of unhandled geometry type: {type(geom)}") + msg = f"cannot round coordinates of unhandled geometry type: {type(geom)}" + raise TypeError(msg) def _consolidate_subdivide_geometry(geometry, max_query_area_size=None): @@ -271,7 +273,8 @@ def _consolidate_subdivide_geometry(geometry, max_query_area_size=None): quadrat_width = np.sqrt(max_query_area_size) if not isinstance(geometry, (Polygon, MultiPolygon)): # pragma: no cover - raise TypeError("Geometry must be a shapely Polygon or MultiPolygon") + msg = "Geometry must be a shapely Polygon or MultiPolygon" + raise TypeError(msg) # if geometry is either 1) a Polygon whose area exceeds the max size, or # 2) a MultiPolygon, then get the convex hull around the geometry @@ -306,7 +309,8 @@ def _get_polygons_coordinates(geometry): polygon_coord_strs : list """ if not isinstance(geometry, MultiPolygon): # pragma: no cover - raise TypeError("Geometry must be a shapely MultiPolygon") + msg = "Geometry must be a shapely MultiPolygon" + raise TypeError(msg) # extract geometry's exterior coords polygons_coords = [] @@ -409,11 +413,11 @@ def _intersect_index_quadrats(geometries, polygon, quadrat_width=0.05, min_num=3 for poly in multipoly.geoms: # first find approximate matches with spatial index, then precise # matches from those approximate ones - poly = poly.buffer(0) - if poly.is_valid and poly.area > 0: - possible_matches_iloc = sindex.intersection(poly.bounds) + poly_buff = poly.buffer(0) + if poly_buff.is_valid and poly_buff.area > 0: + possible_matches_iloc = sindex.intersection(poly_buff.bounds) possible_matches = geometries.iloc[list(possible_matches_iloc)] - precise_matches = possible_matches[possible_matches.intersects(poly)] + precise_matches = possible_matches[possible_matches.intersects(poly_buff)] geoms_in_poly.update(precise_matches.index) utils.log(f"Identified {len(geoms_in_poly):,} geometries inside polygon") diff --git a/osmnx/utils_graph.py b/osmnx/utils_graph.py index b156f79c5..22e0c4166 100644 --- a/osmnx/utils_graph.py +++ b/osmnx/utils_graph.py @@ -42,7 +42,8 @@ def graph_to_gdfs(G, nodes=True, edges=True, node_geometry=True, fill_edge_geome if nodes: if not G.nodes: # pragma: no cover - raise ValueError("graph contains no nodes") + msg = "graph contains no nodes" + raise ValueError(msg) nodes, data = zip(*G.nodes(data=True)) @@ -58,7 +59,8 @@ def graph_to_gdfs(G, nodes=True, edges=True, node_geometry=True, fill_edge_geome if edges: if not G.edges: # pragma: no cover - raise ValueError("graph contains no edges") + msg = "graph contains no edges" + raise ValueError(msg) u, v, k, data = zip(*G.edges(keys=True, data=True)) @@ -99,7 +101,8 @@ def _make_geom(u, v, data, x=x_lookup, y=y_lookup): elif edges: return gdf_edges else: # pragma: no cover - raise ValueError("you must request nodes or edges or both") + msg = "you must request nodes or edges or both" + raise ValueError(msg) def graph_from_gdfs(gdf_nodes, gdf_edges, graph_attrs=None): @@ -133,7 +136,8 @@ def graph_from_gdfs(gdf_nodes, gdf_edges, graph_attrs=None): G : networkx.MultiDiGraph """ if not ("x" in gdf_nodes.columns and "y" in gdf_nodes.columns): # pragma: no cover - raise ValueError("gdf_nodes must contain x and y columns") + msg = "gdf_nodes must contain x and y columns" + raise ValueError(msg) # if gdf_nodes has a geometry attribute set, drop that column (as we use x # and y for geometry information) and warn the user if the geometry values @@ -142,7 +146,8 @@ def graph_from_gdfs(gdf_nodes, gdf_edges, graph_attrs=None): try: all_x_match = (gdf_nodes.geometry.x == gdf_nodes["x"]).all() all_y_match = (gdf_nodes.geometry.y == gdf_nodes["y"]).all() - assert all_x_match and all_y_match + assert all_x_match + assert all_y_match except (AssertionError, ValueError): # pragma: no cover # AssertionError if x/y coords don't match geometry column # ValueError if geometry column contains non-point geometry types diff --git a/pyproject.toml b/pyproject.toml index 85fbb8db5..f5c22439c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ license = {text = "MIT License"} maintainers = [{name = "OSMnx contributors"}] name = "osmnx" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.8" # match classifiers above and ruff target-version below urls.Documentation = "https://osmnx.readthedocs.io" urls.Repository = "https://github.com/gboeing/osmnx" @@ -60,21 +60,45 @@ path = "osmnx/_version.py" [tool.ruff] cache-dir = "~/.cache/ruff" exclude = ["build/*"] -ignore = ["E501"] -line-length = 100 -select = ["A", "B", "C", "D", "E", "ERA", "F", "I", "W"] +ignore = ["PLR091"] # ignore because we're already checking mccabe complexity +line-length = 110 # black line length + 10% +select = ["A", # check python builtins being used as variables or parameters + "ARG", # check unused function arguments + "B", # check common design problems + "C4", # check proper comprehensions + "C9", # check mccabe complexity + "D", # check docstring conventions a la pydocstyle + "E", # check code style conventions a la pycodestyle errors + "EM", # check raw literals inside exception raising + "ERA", # eradicate commented-out code from python files + "F", # check python source code for errors a la pyflakes + "I", # check isort imports + "ISC", # check implicit string literal concatenation + "PGH", # check pygrep hooks + "PL", # check code errors and smells a la pylint + "PT", # check common pytest issues + "PTH", # check pathlib usage + "T20", # check for print statements + "UP", # check outdated syntax a la pyupgrade + "W"] # check code style conventions a la pycodestyle warnings +target-version = "py38" [tool.ruff.isort] force-single-line = true [tool.ruff.mccabe] -max-complexity = 15 +max-complexity = 14 [tool.ruff.per-file-ignores] -"./docs/conf.py" = ["A001"] -"./osmnx/__init__.py" = ["F401", "F403"] -"./osmnx/_api.py" = ["F401"] -"./tests/test_osmnx.py" = ["E402", "D103", "F841", "I001"] +"./docs/conf.py" = ["A001"] # allow copyright variable for sphinx +"./osmnx/__init__.py" = ["F401", # allow API imports to go unused + "F403"] # allow import * from API +"./osmnx/_api.py" = ["F401"] # allow API imports to go unused +"./tests/test_osmnx.py" = ["E402", # allow imports not at top + "D103", # allow missing function docstrings + "F841", # allow unused local variables + "I001", # allow unsorted imports + "PLR2004"] # allow magic values in comparisons [tool.ruff.pydocstyle] convention = "numpy" diff --git a/tests/README.md b/tests/README.md index 9ac0f7729..66844bd8e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,10 +2,10 @@ First, ensure that you have installed the necessary [dependencies](../environments/tests/env-ci.yml) for the test suite. Then use the repository's [pre-commit hooks](../.pre-commit-config.yaml) and the scripts in this folder to: - - format code/docstrings to the project's style - - lint the code - - lint the docstrings - - run tests and coverage +- format code/docstrings to the project's style +- lint the code +- lint the docstrings +- run tests and coverage You can read more about the project's standards and code/docstring style in the [contributing guidelines](../CONTRIBUTING.md). @@ -29,11 +29,11 @@ bash ./tests/lint_test.sh All PRs trigger continuous integration tests via GitHub Actions. See the [configuration](../.github/workflows/ci.yml). The following steps are automatically run: - - build the docs - - check code formatting - - lint the docstrings - - lint the code - - tests and coverage +- build the docs +- check code formatting +- lint the docstrings +- lint the code +- tests and coverage ## Releases diff --git a/tests/environments/env-test-minimal.yml b/tests/environments/env-test-minimal.yml index 444a10477..9cb9fbfa0 100644 --- a/tests/environments/env-test-minimal.yml +++ b/tests/environments/env-test-minimal.yml @@ -12,7 +12,7 @@ dependencies: - networkx=2.5 - numpy=1.20 - pandas=1.1 - - python=3.8 # pin to minimum version from /pyproject.toml + - python=3.8 # pin to minimum version from /pyproject.toml - requests=2.25 - shapely=2.0 @@ -35,4 +35,4 @@ dependencies: # docs - furo - - sphinx=6 # pin to version from /docs/requirements.txt + - sphinx=6 # pin to version from /docs/requirements.txt diff --git a/tests/lint_test.sh b/tests/lint_test.sh index fd22ac333..da9d96ea0 100644 --- a/tests/lint_test.sh +++ b/tests/lint_test.sh @@ -7,13 +7,13 @@ set -e rm -r -f .coverage .pytest_cache .temp ./dist/ ./docs/_build osmnx/__pycache__ tests/__pycache__ find . -type f -name "*.vrt" -delete -# test building and validating the package -hatch build --clean -twine check --strict ./dist/* - # lint pre-commit run --all-files +# test building and validating the package +#hatch build --clean +#twine check --strict ./dist/* + # build the docs # make -C ./docs html # python -m sphinx -b linkcheck docs/ docs/_build/linkcheck diff --git a/tests/test_osmnx.py b/tests/test_osmnx.py index 5332dd594..e7267ce63 100755 --- a/tests/test_osmnx.py +++ b/tests/test_osmnx.py @@ -20,6 +20,7 @@ import numpy as np import pandas as pd import pytest +from requests.exceptions import ConnectionError from shapely import wkt from shapely.geometry import LineString from shapely.geometry import MultiLineString @@ -165,7 +166,7 @@ def test_osm_xml(): assert edge_key in G.edges assert G.edges[edge_key]["name"] in ("8th Street", "Willow Street") - os.remove(temp_filename) + Path.unlink(Path(temp_filename)) # test .osm xml saving default_all_oneway = ox.settings.all_oneway @@ -374,7 +375,7 @@ def test_endpoints(): response_json = ox._downloader._nominatim_request(params=params, request_type="lookup") # Invalid nominatim query type - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Nominatim request_type must be"): response_json = ox._downloader._nominatim_request(params=params, request_type="xyz") default_key = ox.settings.nominatim_key @@ -388,7 +389,7 @@ def test_endpoints(): # Test changing the endpoint. # This should fail because we didn't provide a valid endpoint ox.settings.overpass_endpoint = "http://NOT_A_VALID_ENDPOINT/api/" - with pytest.raises(Exception) as ex: + with pytest.raises(ConnectionError, match="Max retries exceeded with url"): G = ox.graph_from_place(place1, network_type="all") ox.settings.nominatim_key = default_key @@ -458,7 +459,7 @@ def test_graph_save_load(): G2 = ox.load_graphml(filepath, node_dtypes=nd, edge_dtypes=ed) # test loading graphml from a file stream - file_bytes = open("tests/input_data/short.graphml", "rb").read() + file_bytes = Path.open(Path("tests/input_data/short.graphml"), "rb").read() data = str(file_bytes.decode()) G = ox.load_graphml(graphml_str=data, node_dtypes=nd, edge_dtypes=ed) @@ -550,4 +551,4 @@ def test_features(): for filename in ("tests/input_data/West-Oakland.osm.bz2", temp_filename): gdf = ox.geometries_from_xml(filename) assert "Willow Street" in gdf["name"].values - os.remove(temp_filename) + Path.unlink(Path(temp_filename))