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 geopandas as gpd
import pandas as pd
from tqdm.auto import tqdm


def _reproject_osm_nodes(nodes_df, input_crs, output_crs):
    #  take original x,y coordinates and convert into geopandas.Series, then reproject
    nodes = gpd.points_from_xy(x=nodes_df.x, y=nodes_df.y, crs=input_crs).to_crs(
        output_crs
    )
    #  convert to dataframe and recreate the x and y cols
    nodes = gpd.GeoDataFrame(index=nodes_df.index, geometry=nodes)
    nodes["x"] = nodes.centroid.x
    nodes["y"] = nodes.centroid.y
    return nodes


def calc_access(
    geodataframe,
    network,
    distance=2000,
    decay="linear",
    variables=None,
    precompute=True,
    return_node_data=False,
):
    """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
    return_node_data : bool, default is False
        Whether to return nodel-level accessibility data or to trim output to
        the same geometries as the input. Default is the latter.

    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 not decay:
        raise Exception("You must pass a decay function such as `linear`")
    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)
    access = pd.DataFrame(dict(zip(variables, access)))
    if return_node_data:
        return access.round(0)
    access = geodataframe[["node_ids", geodataframe.geometry.name]].merge(
        access, right_index=True, left_on="node_ids", how="left"
    )

    return access.dropna()


[docs]def compute_travel_cost_matrix(origins, destinations, network, reindex_name=None): """Compute a shortest path matrix from a pandana network Parameters ---------- origins : geopandas.GeoDataFrame the set of origin geometries. If polygon input, the function will use their centroids destinations : geopandas.GeoDataFrame the set of destination geometries. If polygon input, the function will use their centroids network : pandana.Network Initialized pandana Network object holding a travel network for a study region reindex_name : str, optional Name of column on the origin/destinatation dataframe that holds unique index values If none (default), the index of the pandana Network node will be used Returns ------- pandas.DataFrame an origin-destination cost matrix. Rows are origin indices, columns are destination indices, and values are shortest network path cost between the two """ origins = origins.copy() destinations = destinations.copy() # Note: these are not necessarily "OSM" ids, they're just the identifiers for each node. # with an integrated ped/transit network, these could be bus stops... origins["osm_ids"] = network.get_node_ids(origins.centroid.x, origins.centroid.y) destinations["osm_ids"] = network.get_node_ids( destinations.centroid.x, destinations.centroid.y ) ods = {} with tqdm(total=len(origins["osm_ids"])) as pbar: for origin in origins["osm_ids"]: ods[f"{origin}"] = network.shortest_path_lengths( [origin] * len(origins), destinations["osm_ids"] ) pbar.update(1) if reindex_name: df = pd.DataFrame(ods, index=origins[reindex_name]) df.columns = df.index else: df = pd.DataFrame(ods, index=origins) return df
[docs]def project_network(network, output_crs=None, input_crs=4326): """Reproject a pandana.Network object into another coordinate system Parameters ---------- network : pandana.Network an instantiated pandana Network object input_crs : int, optional the coordinate system used in the Network.node_df dataframe. Typically these data are collected in Lon/Lat, so the default 4326 output_crs : int, str, or pyproj.crs.CRS, required EPSG code or pyproj.crs.CRS object of the output coordinate system Returns ------- pandana.Network an initialized pandana.Network with 'x' and y' values represented by coordinates in the specified CRS """ try: import pandana as pdna except ImportError: raise ImportError( "You need pandana to work with segregation's network module\n" "You can install them with `pip install urbanaccess pandana` " "or `conda install -c udst pandana urbanaccess`" ) assert output_crs, "You must provide an output CRS" # take original x,y coordinates and convert into geopandas.Series, then reproject nodes = _reproject_osm_nodes(network.nodes_df, input_crs, output_crs) # reinstantiate the network (needs to rebuild the tree) net = pdna.Network( node_x=nodes["x"], node_y=nodes["y"], edge_from=network.edges_df["from"], edge_to=network.edges_df["to"], edge_weights=network.edges_df[network.impedance_names], twoway=network._twoway, ) return net