Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Geographic and display data

PowerIO stores coordinates when a supported source provides them. Coordinates are optional; readers do not invent them, and network writers without a coordinate representation report the loss.

PowerWorld .pwd files are display data rather than network cases. Parse them with parse_display_file or parse_display_bytes rather than the network parser.

Coordinate fields

Both network model families expose the same JSON shape:

#![allow(unused)]
fn main() {
pub struct Location {
    /// Longitude for geographic coordinates.
    pub x: f64,
    /// Latitude for geographic coordinates.
    pub y: f64,
    /// Point provenance when it differs from the network default.
    pub kind: Option<CoordsKind>,
}

pub enum CoordsKind { Source, Synthetic, Manual, Derived }

pub struct GeoMeta {
    pub space: CoordinateSpace,
    pub kind: Option<CoordsKind>,
}

pub struct Canvas {
    pub width: Option<f64>,
    pub height: Option<f64>,
    pub units: Option<String>,
}

pub enum CoordinateSpace {
    Geographic { crs: Option<String> },
    Projected { crs: Option<String> },
    Diagram { canvas: Option<Canvas> },
    Unknown,
}
}

Balanced networks use powerio::geo::{Location, CoordsKind, CoordinateSpace, GeoMeta, Canvas} through BalancedNetwork.geo and Bus.location. Multiconductor networks use the matching powerio_dist::geo types through MulticonductorNetwork.geo and DistBus.location. A package serialization test keeps the two JSON shapes identical. Branches carry optional polyline routing (Branch.route, DistLine.route) when a source provides intermediate geometry; endpoint only rendering derives from the bus locations.

The coordinate space belongs to the network. For geographic coordinates, x is longitude and y is latitude in GeoJSON axis order. A missing CRS in a geographic space means EPSG:4326. kind records whether coordinates came from the source, a generated layout, a manual edit, or a derived transform.

Harvest and emit

Readers promote coordinates into location and stamp the space; promotion removes the raw keys from extras. Writers emit from location.

FormatFieldsSpace
PowerWorld auxLatitude:1/Longitude:1 bus columns, else the bare Latitude/Longitude pair (SubNum stays in extras: it is identity rather than geometry)geographic
pandapowerbus geo GeoJSON Point stringsgeographic
PyPSAbuses.csv x/ygeographic
OpenDSSBuscoordsunknown; a diagnostic identifies values within longitude and latitude bounds
BMOPF JSONlongitude/latitude (the BMOPFTools sideload convention; writing is opt in via BmopfWriteOptions::sideload_coordinates)geographic

MATPOWER, PSS/E, PowerModels, egret, GOC3, PSLF, and Surge carry no geometry. Writing a located case to one of them reports the dropped locations, the same behavior base_frequency has; powerio geo extract writes the sidecar as the escape hatch.

The geographic document

Coordinates also arrive and leave as files of their own: a Buscoords CSV next to a DSS master, a GeoJSON export from a GIS tool, a layout computed by a renderer. The container is GeoLayer, surfaced as DisplayData::Geo beside the PowerWorld .pwd display path.

The canonical form is a GeoJSON FeatureCollection with one foreign member, suggested extension .geo.json:

{
  "type": "FeatureCollection",
  "powerio_geo": { "version": "0.1.0", "space": "geographic", "kind": "source" },
  "features": [
    { "type": "Feature",
      "geometry": { "type": "Point", "coordinates": [-80.05, 34.20] },
      "properties": { "target": "bus", "id": "312", "uid": "buses:11" } },
    { "type": "Feature",
      "geometry": { "type": "LineString", "coordinates": [[-80.05, 34.20], [-80.10, 34.30]] },
      "properties": { "target": "branch", "uid": "branches:4", "from": "312", "to": "410" } }
  ]
}

When the space is geographic this is valid RFC 7946 GeoJSON, so GIS tools open it directly.

Reading is tolerant; writing is canonical. GeoLayer::parse_bytes takes bytes plus a file name hint and touches no filesystem. It accepts headerless buscoords CSV (bus, x, y), CSV and JSON records with aliased field names (bus_i/bus/id, lat/latitude/y, lon/lng/longitude/x, branch endpoint pairs), and GeoJSON Point and LineString features. Features reference elements by up to three key fields, matched in order: uid, then id, then case insensitive name. Branch routes additionally fall back to the unordered (from, to) bus pair. A bare integer branch id (branch, branchid, branchnumber, catsid) is accepted on read as a 1-based positional row alias and never written; the durable identity is the payload uid. A branch key never reads from a bare id property, because GIS exports and RFC 7946 tooling put a feature row counter there.

Network::geo_layer() extracts, and Network::apply_geo_layer(&layer) applies and returns a GeoApplyReport with the matched and unmatched feature counts plus unlocated_buses and unlocated_branches, the elements that carry no geometry when the pass ends. The two together tell a layer that matched nothing from a model that needed nothing; report.require_located() is the strict caller’s one line check. The multiconductor equivalents attach through powerio-pkg (dist_geo_layer, apply_dist_geo_layer). The CLI wraps the same surface:

$ powerio geo extract case.aux -o case.geo.json
$ powerio geo apply case.m layout.csv -o placed.m
$ powerio geo convert buscoords.csv -o case.geo.json

PowerWorld display files

The .pwd reader returns DisplayData::PowerWorld with a PwdDisplay: canvas dimensions, a timestamp, and substation symbols with number, name, and diagram coordinates.

Four helpers connect it to the geo model. geo_layer_from_pwd lifts the substation symbols into a diagram space GeoLayer (also reachable as powerio geo extract case.pwd); geo_layer_from_aux_substations lifts the Latitude and Longitude columns of an aux Substation table into a geographic one; apply_substation_points joins either onto buses through the SubNum extras key; and pwd_mercator_to_lonlat is a documented, approximate inverse of the projection PowerWorld’s auto generated layouts use, for consumers that want to place a diagram on a map.

A bus row of a complete case export carries its own coordinates as well. The aux reader promotes the substation Latitude:1/Longitude:1 pair, and the bus’s own bare Latitude/Longitude pair, into Bus.location; a promoted pair leaves extras.

Rust uses parse_display_file and parse_display_bytes. Python exposes the same names and returns DisplayData(kind="powerworld", data=PwdDisplay(...)). Display files do not pass through BalancedNetwork, Conversion, or .pio.json.

Distribution graph projection

MulticonductorNetwork::graph() returns a bus and terminal graph without requiring coordinates. Python exposes dist_net.graph(), and the C dist feature exposes pio_dist_graph_json. Graph topology and geographic placement remain separate data.

PowerIO stores and transports coordinates; it does not compute them. Synthetic layout of a coordinate free case is renderer math and stays in the consumer, which can write the result back with kind = synthetic so the provenance survives.

The C ABI exposes the document as strings: pio_geo_parse normalizes a tolerant sidecar to the canonical form, pio_geo_extract and pio_geo_apply work on a parsed network handle (apply returns a new handle whose warnings carry the match report), and pio_dist_geo_extract/pio_dist_geo_apply are the multiconductor equivalents. Python mirrors the surface with parse_geo and geo_layer()/apply_geo_layer() on both network types.