-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add graph from travel network (#755)
* add graph from travel network * typo graphsummary * add network tests * test_method * skip net test on windows * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * format * picky ass linter * distance-->cost * ugh. lint. * docstring; test length * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * validate geometry; remove unused options * validate before * imports * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * document node_ids param * add example in docstring * Update libpysal/graph/base.py Co-authored-by: Martin Fleischmann <[email protected]> * Update libpysal/graph/base.py Co-authored-by: Martin Fleischmann <[email protected]> * Update libpysal/graph/base.py Co-authored-by: Martin Fleischmann <[email protected]> * Update libpysal/graph/base.py Co-authored-by: Martin Fleischmann <[email protected]> * continue formatting * Update libpysal/graph/_network.py Co-authored-by: James Gaboardi <[email protected]> * Update libpysal/graph/_network.py Co-authored-by: James Gaboardi <[email protected]> * Update libpysal/graph/_network.py Co-authored-by: James Gaboardi <[email protected]> * Update libpysal/graph/_network.py Co-authored-by: James Gaboardi <[email protected]> * Update libpysal/graph/_network.py Co-authored-by: James Gaboardi <[email protected]> * Update libpysal/graph/_network.py Co-authored-by: James Gaboardi <[email protected]> * curlies if were being picky; im gonna fight this linter--this one was your fault james!!! :P --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Martin Fleischmann <[email protected]> Co-authored-by: James Gaboardi <[email protected]>
- Loading branch information
1 parent
8231e4e
commit 01d41cf
Showing
8 changed files
with
272 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ dependencies: | |
- sqlalchemy | ||
- xarray | ||
- zstd | ||
- pandana | ||
- pip | ||
- pip: | ||
- pulp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ dependencies: | |
- sqlalchemy=2.0 | ||
- zstd | ||
- xarray=2022.3 | ||
- pandana | ||
- pip | ||
- pip: | ||
- pulp | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ dependencies: | |
- sqlalchemy | ||
- xarray | ||
- zstd | ||
- pandana | ||
- pip | ||
- pip: | ||
- pulp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ dependencies: | |
- pyproj | ||
- sqlalchemy | ||
- zstd | ||
- pandana | ||
- pip | ||
- pip: | ||
# dev versions of packages | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import numpy as np | ||
|
||
from ._utils import _induce_cliques, _validate_geometry_input | ||
|
||
|
||
def _build_coplanarity_node_lookup(geoms): | ||
""" | ||
Identify coplanar points and create a look-up table for the coplanar geometries. | ||
Same function as in ``graph._utils``, but need to keep the index to use as graph ids | ||
""" | ||
# geoms = geoms.reset_index(drop=True) | ||
coplanar = [] | ||
nearest = [] | ||
r = geoms.groupby(geoms).groups | ||
for g in r.values(): | ||
if len(g) == 2: | ||
coplanar.append(g[0]) | ||
nearest.append(g[1]) | ||
elif len(g) > 2: | ||
for n in g[1:]: | ||
coplanar.append(n) | ||
nearest.append(g[0]) | ||
return np.asarray(coplanar), np.asarray(nearest) | ||
|
||
|
||
def pdna_to_adj(origins, network, node_ids, threshold): | ||
"""Create an adjacency list of shortest network-based travel between | ||
origins and destinations in a pandana.Network. | ||
Parameters | ||
---------- | ||
origins : geopandas.GeoDataFrame | ||
Geodataframe of origin geometries to begin routing. | ||
network : pandana.Network | ||
pandana.Network instance that stores the local travel network | ||
node_ids: | ||
array of node_ids in the pandana.Network aligned with the input | ||
observations in ``origins``. This is created via a call like | ||
``pandana.Network.get_node_ids(df.geometry.x, df.geometry.y)`` | ||
threshold : int | ||
maximum travel distance (inclusive) | ||
Returns | ||
------- | ||
pandas.DataFrame | ||
adjacency list with columns 'origin', 'destination', and 'cost' | ||
""" | ||
|
||
# map node ids in the network to index in the gdf | ||
mapper = dict(zip(node_ids, origins.index.values, strict=False)) | ||
|
||
namer = {"source": "origin", network.impedance_names[0]: "cost"} | ||
|
||
adj = network.nodes_in_range(node_ids, threshold) | ||
adj = adj.rename(columns=namer) | ||
# swap osm ids for gdf index | ||
adj = adj.set_index("destination").rename(index=mapper).reset_index() | ||
adj = adj.set_index("origin").rename(index=mapper).reset_index() | ||
adj = adj[adj.destination.isin(origins.index.values)] | ||
|
||
return adj | ||
|
||
|
||
def build_travel_graph( | ||
df, | ||
network, | ||
threshold, | ||
): | ||
"""Compute the shortest path between gdf centroids via a pandana.Network | ||
and return an adjacency list with weight=cost. Note unlike distance_band, | ||
:math:`G_{ij}` and :math:`G_{ji}` are often different because travel networks | ||
may be directed. | ||
Parameters | ||
---------- | ||
df : geopandas.GeoDataFrame | ||
geodataframe of observations. CRS should be the same as the locations | ||
of ``node_x`` and ``node_y`` in the pandana.Network (usually 4326 if network | ||
comes from OSM, but sometimes projected to improve snapping quality). | ||
network : pandana.Network | ||
Network that encodes travel costs. See <https://udst.github.io/pandana/> | ||
threshold : int | ||
maximum travel cost to consider neighbors | ||
Returns | ||
------- | ||
pandas.Series | ||
adjacency formatted as multiindexed (focal, neighbor) series | ||
""" | ||
df = df.copy() | ||
_validate_geometry_input(df.geometry, ids=None, valid_geometry_types="Point") | ||
df["node_ids"] = network.get_node_ids(df.geometry.x, df.geometry.y) | ||
|
||
# depending on density of the graph nodes / observations, it is common to have | ||
# multiple observations snapped to the same network node, so use the clique | ||
# expansion logic to handle these cases | ||
|
||
# get indices of multi-observations at unique nodes | ||
coplanar, nearest = _build_coplanarity_node_lookup(df["node_ids"]) | ||
# create adjacency on unique nodes | ||
adj = pdna_to_adj(df, network, df["node_ids"].values, threshold) | ||
# add clique members back to adjacency | ||
adj_cliques = _induce_cliques( | ||
adj.rename( | ||
columns={"origin": "focal", "destination": "neighbor", "cost": "weight"} | ||
), | ||
coplanar=coplanar, | ||
nearest=nearest, | ||
) | ||
# reorder, drop induced dupes, and return | ||
adj_cliques = ( | ||
adj_cliques.groupby(["focal", "neighbor"]) | ||
.first() | ||
.reindex(df.index, level=0) | ||
.reindex(df.index, level=1) | ||
.reset_index() | ||
) | ||
|
||
return adj_cliques |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters