Source code for segregation.network.network
"""Calculate street network-based segregation measures."""
__author__ = "Elijah Knaap <elijah.knaap@ucr.edu> Renan X. Cortes <renanc@ucr.edu> and Sergio J. Rey <sergio.rey@ucr.edu>"
import numpy as np
import pandas as pd
from warnings import warn
from segregation.util import project_gdf
import os
import sys
# This class allows us to hide the diagnostic messages from urbanaccess if the `quiet` flag is set
class _HiddenPrints: # from https://stackoverflow.com/questions/8391411/suppress-calls-to-print-python
def __enter__(self):
self._original_stdout = sys.stdout
sys.stdout = open(os.devnull, 'w')
def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout.close()
sys.stdout = self._original_stdout
[docs]def get_osm_network(geodataframe, maxdist=5000, quiet=True, **kwargs):
"""Download a street network from OSM.
Parameters
----------
geodataframe : geopandas.GeoDataFrame
geopandas.GeoDataFrame of the study area.
Coordinate system should be in WGS84
maxdist : int
Total distance (in meters) of the network queries you may need.
This is used to buffer the network to ensure theres enough to satisfy
your largest query, otherwise there may be edge effects.
quiet: bool
If True, diagnostic messages from urbanaccess will be suppressed
**kwargs : dict
additional kwargs passed through to
urbanaccess.ua_network_from_bbox
Returns
-------
pandana.Network
A pandana Network instance for use in accessibility calculations or
spatial segregation measures that include a distance decay
"""
try:
import pandana as pdna
from urbanaccess.osm.load import ua_network_from_bbox
except ImportError:
raise ImportError(
"You need pandana and urbanaccess to work with segregation's network module\n"
"You can install them with `pip install urbanaccess pandana` "
"or `conda install -c udst pandana urbanaccess`")
gdf = geodataframe.copy()
gdf = project_gdf(gdf)
gdf = gdf.buffer(maxdist)
bounds = gdf.to_crs(epsg=4326).total_bounds
if quiet:
print('Downloading data from OSM. This may take awhile.')
with _HiddenPrints():
net = ua_network_from_bbox(bounds[1], bounds[0], bounds[3],
bounds[2], **kwargs)
else:
net = ua_network_from_bbox(bounds[1], bounds[0], bounds[3], bounds[2],
**kwargs)
print("Building network")
network = pdna.Network(net[0]["x"], net[0]["y"], net[1]["from"],
net[1]["to"], net[1][["distance"]])
return network
[docs]def calc_access(geodataframe,
network,
distance=2000,
decay="linear",
variables=None,
precompute=True):
"""Calculate access to population groups.
Parameters
----------
geodataframe : geopandas.GeoDataFrame
geodataframe with demographic data
network : pandana.Network
pandana.Network instance. This is likely created with `get_osm_network`
or via helper functions from OSMnet or UrbanAccess.
distance : int
maximum distance to consider `accessible` (the default is 2000).
decay : str
decay type pandana should use "linear", "exp", or "flat"
(which means no decay). The default is "linear".
variables : list
list of variable names present on gdf that should be calculated
precompute: bool (default True)
whether pandana should precompute the distance matrix. It can only be
precomputed once, so If you plan to pass the same network to this
function several times, you should set precompute=False for later runs
Returns
-------
pandas.DataFrame
DataFrame with two columns, `total_population` and `group_population`
which represent the total number of each group that can be reached
within the supplied `distance` parameter. The DataFrame is indexed
on node_ids
"""
if precompute:
network.precompute(distance)
geodataframe["node_ids"] = network.get_node_ids(geodataframe.centroid.x,
geodataframe.centroid.y)
access = []
for variable in variables:
network.set(geodataframe.node_ids,
variable=geodataframe[variable],
name=variable)
access_pop = network.aggregate(distance,
type="sum",
decay=decay,
name=variable)
access.append(access_pop)
names = ["acc_" + variable for variable in variables]
access = pd.DataFrame(dict(zip(names, access)))
return access