Building a Distribution Graph#

Once you have a list of ParcelModel objects (see Fetching Parcels), the next step is to construct a DistributionGraph — the connectivity model of nodes and edges that represents the distribution network.

Cluster Parcels#

First, cluster parcels into groups. Each cluster center becomes a candidate distribution transformer location. A common heuristic is roughly two customers per transformer, but you can adjust this to match your design criteria.

from shift import ParcelModel, GeoLocation, get_kmeans_clusters

def _get_parcel_points(parcels: list[ParcelModel]) -> list[GeoLocation]:
    """Extract a single GeoLocation per parcel (centroid for polygons)."""
    return [
        el.geometry[0] if isinstance(el.geometry, list) else el.geometry
        for el in parcels
    ]

num_clusters = max(len(parcels) // 2, 1)
clusters = get_kmeans_clusters(num_clusters, _get_parcel_points(parcels))

Build the Graph with PRSG#

PRSG (Primary–Secondary Road-network Graph) builds the distribution graph in two steps:

  1. Primary network — derived by reducing the road network in the area to a spanning tree.

  2. Secondary network — built as a 2-D grid connecting transformer locations to nearby customer parcels.

The node closest to source_location is treated as the substation.

from shift import PRSG, GeoLocation

builder = PRSG(
    groups=clusters,
    source_location=GeoLocation(-97.3, 32.75),  # substation coordinates
)
graph = builder.get_distribution_graph()

Visualize the Graph#

You can overlay the distribution graph on the parcel plot from the previous step:

from shift import add_distribution_graph_to_plot, PlotManager, GeoLocation
import osmnx as ox

center = GeoLocation(*reversed(ox.geocode("Fort Worth, TX")))
plot_manager = PlotManager(center=center)
add_distribution_graph_to_plot(graph, plot_manager)
plot_manager.show()

Next Step#

Proceed to Updating Branch Types to replace generic branch types with the specific equipment models your simulation requires.