Source code for superblockify.graph_stats

"""Spatial graph order measures for the superblockify package."""

from numpy import log
from osmnx import orientation_entropy
from osmnx.projection import is_projected, project_graph
from osmnx.stats import basic_stats
from scipy.stats import entropy

from .population.approximation import get_population_area
from .config import Config


[docs] def basic_graph_stats(graph, area=None): """Calculate several basic graph stats. This function calculates the stats from :func:`osmnx.stats.basic_stats` and adds the street count per node and the street orientation order (:func:`street_orientation_order`). Parameters ---------- graph : networkx.MultiDiGraph The graph to calculate the stats of. area : float or None The area of the graph in square meters. This is used to calculate density measures. """ stats = basic_stats(graph, area=area) stats["street_orientation_order"] = street_orientation_order(graph, Config.NUM_BINS) return stats
[docs] def street_orientation_order(graph, num_bins): r"""Calculate the street orientation order of a graph. .. math:: \phi=1-\left(\frac{H_o-H_g}{H_{\max}-H_g}\right)^2 Where :math:`H_o` is the orientation entropy of the graph, :math:`H_g` is the minimal plausible entropy of a grid like city :math:`H_g \approx 1.386`, and :math:`H_{\max}` is the maximal entropy of a city where the streets are uniformly distributed :math:`H_{\max} = \log(\text{num_bins=36}) \approx 3.584`. To make an order parameter of this from total disorder to order, :math:`\phi` is designed to reach from 0 to 1. [1]_ [2]_ Calculations are done on the undirected graph, and if the graph is projected, it is first unprojected to WGS84. Parameters ---------- graph : networkx.MultiDiGraph The graph to calculate the street orientation order of. num_bins : int The number of bins to use for the orientation histogram in the entropy calculation. Positive integer. Returns ------- float The street orientation order of the graph as defined by [1]_. Raises ------ ValueError If `num_bins` is not a positive integer. References ---------- .. [1] Boeing, G. Urban spatial order: street network orientation, configuration, and entropy. Appl Netw Sci **4**, 67 (2019). https://doi.org/10.1007/s41109-019-0189-1 .. [2] Boeing, G. Street Network Models and Indicators for Every Urban Area in the World. Geographical Analysis 54, 519–535 (2022). https://doi.org/10.1111/gean.12281 """ if not isinstance(num_bins, int) or num_bins < 1: raise ValueError( f"num_bins must be a non-negative integer, not {num_bins} of type " f"{type(num_bins)}." ) graph_unprojected = graph.copy() if is_projected(graph_unprojected.graph["crs"]): # logger.debug( # "Orientation order: Unprojecting graph from %s to 2D coordinates (" # "epsg:4326).", # graph_unprojected.graph["crs"], # ) graph_unprojected = project_graph(graph_unprojected, to_crs="epsg:4326") # orientation_entropy requires an undirected graph graph_unprojected = graph_unprojected.to_undirected() min_entropy_bins = 4 # perfect grid perfect_grid = [1] * min_entropy_bins + [0] * (num_bins - min_entropy_bins) perfect_grid_entropy = entropy(perfect_grid) max_entropy = log(num_bins) o_entropy = orientation_entropy(graph_unprojected, num_bins=num_bins) return ( 1 - ((o_entropy - perfect_grid_entropy) / (max_entropy - perfect_grid_entropy)) ** 2 )
[docs] def calculate_component_metrics(components): """Calculate metrics for the components. Calculates the metrics for the components and writes them to each component dictionary. Parameters ---------- components : list of dicts List of dictionaries containing the components. Notes ----- Works in-place on the components if they are defined, otherwise on the partitions. """ for part in components: part["population"], part["area"] = get_population_area(part["subgraph"]) part["population_density"] = part["population"] / part["area"] # Add basic_stats to the component part.update( basic_graph_stats(part["subgraph"], area=part["area"]), population=part["population"], population_density=part["population_density"], )