Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: improved node consolidation functions #377

Merged
merged 32 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2f71606
including node_consolidation function
gsagostini Sep 10, 2022
53351c9
adding imports of nx split and deepcopy
gsagostini Sep 10, 2022
5e00851
importing nx_to_gdf
gsagostini Sep 10, 2022
30354ac
adding data for a street network
gsagostini Sep 11, 2022
e3a4931
testing consolidate_intersections
gsagostini Sep 11, 2022
f75afd7
formatting graph dataset loading
gsagostini Sep 11, 2022
fd2b0d1
formatting the tests file
gsagostini Sep 11, 2022
2accf90
adding consolidate_intersections to functions to test list
gsagostini Sep 11, 2022
1e20a98
updating networkx to 2.5 in minimal env
gsagostini Sep 12, 2022
d84b78b
fixing docstrings and adding test for warning section
gsagostini Sep 12, 2022
375bb86
cleaning method user input
gsagostini Sep 12, 2022
558f1a2
docstring of extension
gsagostini Sep 15, 2022
15d9287
fixed docstring
gsagostini Sep 15, 2022
22818dc
fixed docstring
gsagostini Sep 15, 2022
324e219
grammar
gsagostini Sep 15, 2022
777a7ab
added default tolerance
gsagostini Sep 15, 2022
73413f1
replacing column with attribute naming
gsagostini Oct 11, 2022
5b2b415
adding approach to simplified_graph object
gsagostini Oct 11, 2022
930d9df
input can be non-Multi graph type
gsagostini Oct 12, 2022
b9aae1a
throw exception when simplification method unrecofnized
gsagostini Oct 12, 2022
068536d
removing directed paramater, always infer directionality
gsagostini Oct 12, 2022
22775fc
set_geometry in the edge dataframe
gsagostini Oct 12, 2022
efe5119
Merge remote-tracking branch upstream/main into pr/gsagostini/377
martinfleis Oct 12, 2022
0e435d2
adding notebook to user_guide
gsagostini Oct 12, 2022
d0c12e7
Merge remote-tracking branch 'upstream/main' into pr/gsagostini/377
martinfleis Jan 23, 2024
49c1864
lint issues
martinfleis Jan 23, 2024
2d0e2b2
fix
martinfleis Jan 23, 2024
7316f76
try fixing the kernel issue
martinfleis Jan 23, 2024
c199407
adapt title
martinfleis Jan 23, 2024
9edfd6f
api reference
martinfleis Jan 23, 2024
3e43378
conditional import of osmnx in tests
martinfleis Jan 23, 2024
e0949e4
Apply suggestions from code review
martinfleis Jan 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ci/envs/38-minimal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ channels:
dependencies:
- python=3.8
- geopandas=0.8
- libpysal=4.2.2
- inequality
- libpysal=4.6.0
- mapclassify
- networkx=2.3
- networkx=2.5
- numpy=1.21
- osmnx
- packaging
Expand Down
243 changes: 243 additions & 0 deletions docs/user_guide/preprocessing/consolidate_intersections.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "27a090ed-2cb0-4e27-a5de-8be22cee69df",
"metadata": {
"tags": []
},
"source": [
"# Consolidating Intersections in Street Networks"
]
},
{
"cell_type": "markdown",
"id": "c00d13a4-7ca3-46bb-b1fb-90c7e5b730d2",
"metadata": {},
"source": [
"Whereas routing and transportation analysis of street networks must take into account precise street segment directionality and multiple street lanes, morphological analysis in these graphs considers only a coarsened version of their geometry. Details such as four-way intersections, transit exchanges, and roundabouts are often considered geometric \"noise\" and must be cleaned prior to analysis.\n",
"\n",
"A particular issue arises when considering the intersection of multi-lane streets. Two perpendicular two-lane streets will intersect in four points, often creating a dummy city block---a polygon enclosed by street segments---in the network. This artifact can influence metrics on urban form such as block area or block eccentricity. We would prefer that these intersections were consolidated in a single vertex.\n",
"\n",
"The following code exemplifies a simple process for intersection consolidation in street networks using `momepy.consolidate_intersections()`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c27950a-d474-4d1b-85dc-1e3aaced5d8d",
"metadata": {},
"outputs": [],
"source": [
"import momepy as mm"
]
},
{
"cell_type": "markdown",
"id": "aa802b6f-8dcc-4a4e-ac87-a3c5874f4b29",
"metadata": {},
"source": [
"## Load data"
]
},
{
"cell_type": "markdown",
"id": "b1bf3529-c7cb-4ca6-8f1d-a29175704796",
"metadata": {},
"source": [
"For this example we will fetch some data from [OpenStreetMap](https://www.openstreetmap.org/#map=6/40.007/-2.488) (using `osmnx`). The function default arguments for attribute naming are configured for such source. However, any user-defined street network can be passed as input to `consolidate_intersections` as long as it satisfies two conditions:\n",
"\n",
"1. All nodes have attributes determining their x (`x_att`) and y (`y_att`) coordinates;\n",
"2. All edges have attributes determining their origin (`edge_from_att`), destination (`edge_to_att`), and geometry.\n",
"\n",
"These attribute names can be passed as input to the function.\n",
"\n",
"We download a 1-square km street network from New York, USA, and reproject to its local CRS."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "77447ca3-58f2-4bfb-819d-035817bfbca7",
"metadata": {},
"outputs": [],
"source": [
"import osmnx as ox\n",
"\n",
"center = (40.783169, -73.978315)\n",
"radius = 500\n",
"\n",
"graph = ox.graph_from_point((40.783169, -73.978315), dist=500, network_type='drive')\n",
"graph_projected = ox.project_graph(graph)\n",
"graph_undirected = ox.get_undirected(graph_projected)\n",
"\n",
"fig, ax = ox.plot_graph(graph_undirected)"
]
},
{
"cell_type": "markdown",
"id": "2685b409-c75d-486b-84c7-57297f53b872",
"metadata": {},
"source": [
"One of the streets has two parallel lanes creating the problem described above."
]
},
{
"cell_type": "markdown",
"id": "8032deea-1b1f-4b41-b89b-5d151b863351",
"metadata": {},
"source": [
"## Default behavior"
]
},
{
"cell_type": "markdown",
"id": "32156d83-ca5e-43cb-a578-d780e6ff4004",
"metadata": {},
"source": [
"In addition to the graph, the function also receives as argument a `tolerance` value. This is the critical length of street segments in the processed network: any segment below such value will be collapsed. Values between 20 and 30 (default) meters work well."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "010fb5bb-8dfb-49a7-8d9f-fd78443f5c21",
"metadata": {},
"outputs": [],
"source": [
"consolidated_graph = mm.consolidate_intersections(graph_undirected, tolerance=30)"
]
},
{
"cell_type": "markdown",
"id": "19b96ac2-caee-4488-af3f-9a171eca8e79",
"metadata": {},
"source": [
"The graph maintains all its attributes, and can be drawn using the same functions:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5e423039-f9e9-4e74-bd42-8422e60ee0bf",
"metadata": {},
"outputs": [],
"source": [
"fig, ax = ox.plot_graph(consolidated_graph)"
]
},
{
"cell_type": "markdown",
"id": "b61caa08-601c-457c-bee9-f3d760a91913",
"metadata": {},
"source": [
"## Simplification methods"
]
},
{
"cell_type": "markdown",
"id": "c1604082-6f00-4884-aeb0-0ee6da03bbd6",
"metadata": {},
"source": [
"By default, the function rebuilds the graph. Setting the parameter `rebuild_graph` to `False` returns a graph where edges to and from consolidated clusters do not exist. This method is faster and useful for aggregation and summary statistics---e.g. the number and location of intersections."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7b7eab1-307d-48d3-95a1-0bc1136876a7",
"metadata": {},
"outputs": [],
"source": [
"rebuildfalse_graph = mm.consolidate_intersections(graph_undirected, tolerance=30, rebuild_graph=False)\n",
"fig, ax = ox.plot_graph(rebuildfalse_graph)"
]
},
{
"cell_type": "markdown",
"id": "2b0f2f1c-7124-4133-80e1-98ebdf8f0525",
"metadata": {},
"source": [
"When `rebuild_graph` is set to `True`, the user can choose a method to determine the geometry of new edges with the parameter `rebuild_edges_method`. There are three currently implemented methods:"
]
},
{
"cell_type": "markdown",
"id": "9dcf4c24-201c-4383-aa83-3f78b7ca3a4f",
"metadata": {},
"source": [
"1. `extend` reconstruction: Edges are linearly extended from original endpoints until the new nodes. This method preserves most faithfully the network geometry, but often yields non-planar graphs with overlapping edges."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7e7a9c3b-e26c-4741-8c9e-babcc76163c5",
"metadata": {},
"outputs": [],
"source": [
"extend_graph = mm.consolidate_intersections(graph_undirected, tolerance=30, rebuild_edges_method='extend')\n",
"fig, ax = ox.plot_graph(extend_graph)"
]
},
{
"cell_type": "markdown",
"id": "ab41b9f5-997e-44f6-9adc-b9691ac77b5e",
"metadata": {},
"source": [
"2. `spider` reconstruction [default]: Edges are cropped within a buffer of the new endpoints and linearly extended from there. This method improves upon linear reconstruction by mantaining, when possible, network planarity. This is the same network that we obtained beforehand."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ee6964dd-5522-4f55-b191-726befb0494a",
"metadata": {},
"outputs": [],
"source": [
"spider_graph = mm.consolidate_intersections(graph_undirected, tolerance=30, rebuild_edges_method='spider')\n",
"fig, ax = ox.plot_graph(spider_graph)"
]
},
{
"cell_type": "markdown",
"id": "fe401fe4-8cf3-4fc3-b467-0a4fe5a3bf92",
"metadata": {},
"source": [
"3. `euclidean` reconstruction: Edges are ignored and new edges are built as straightlines between new origin and new destination. This method ignores geometry, but efficiently preserves adjacency."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6c8be0e1-55b5-4122-a4fd-0b1d190518be",
"metadata": {},
"outputs": [],
"source": [
"euclidean_graph = mm.consolidate_intersections(graph_undirected, tolerance=30, rebuild_edges_method='euclidean')\n",
"fig, ax = ox.plot_graph(euclidean_graph)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
1 change: 1 addition & 0 deletions docs/user_guide/preprocessing/preprocessing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ This section covers:

simple_preprocessing
roundabout_simplification
consolidate_intersections
9 changes: 6 additions & 3 deletions momepy/datasets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@
__all__ = ["available", "get_path"]

_module_path = os.path.dirname(__file__)
available = ["bubenec", "tests"]
available = ["bubenec", "tests", "nyc_graph"]


def get_path(dataset):
def get_path(dataset, extension="gpkg"):
"""
Get the path to the data file.
Parameters
----------
dataset : str
The name of the dataset. See ``momepy.datasets.available`` for
all options.
extension : str
The extension of the data file
"""
if dataset in available:
return os.path.abspath(os.path.join(_module_path, dataset + ".gpkg"))
filepath = dataset + "." + extension
return os.path.abspath(os.path.join(_module_path, filepath))
msg = "The dataset '{data}' is not available. ".format(data=dataset)
msg += "Available datasets are {}".format(", ".join(available))
raise ValueError(msg)
Loading