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 keeps coordinates when a supported source provides them. They are optional. No parser invents them, and when you emit a located case to a network format that has no place for coordinates, the writer reports the loss.

A standalone geographic document parses to powerio.GeoLayer, a value like any other case. The canonical .geo.json, plain GeoJSON, CSV or JSON records with aliased field names, headerless buscoords CSV, and a PowerWorld .pwd display all parse to it; a .pwd becomes a drawing layer with supported bus, branch, and substation objects. A drawing with no decoded equipment returns an error.

use powerio::{PioValue, emit, parse, serialize};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let module = parse("layer.geo.json")?;
    let PioValue::GeoLayer(layer) = module.value() else {
        panic!("a layer document parses to powerio.GeoLayer");
    };

    // A layer travels through PowerIO IR and out as the canonical document.
    serialize(&module, "layer.pio.json")?;
    emit(&module, "geo-json", "layer.geo.json")?;
    Ok(())
}

If you need the raw display record, PwdDisplay is still available; it has the canvas, the save stamp, and the symbol table in diagram coordinates.

Coordinate fields

Balanced and multiconductor networks use the same JSON shape for coordinates:

#![allow(unused)]
fn main() {
pub struct Location {
    /// Longitude for geographic coordinates.
    pub x: f64,
    /// Latitude for geographic coordinates.
    pub y: f64,
    /// Point origin 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 reach these types as 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, and a package serialization test keeps the two JSON shapes identical. A branch can also have a polyline route (Branch.route, DistLine.route) when the source provides intermediate geometry. Without one, a renderer draws the branch endpoint to endpoint from the bus locations.

The coordinate space belongs to the network, and a point sets its own kind only when its origin differs from the network default. For geographic coordinates, x is longitude and y is latitude, which is GeoJSON axis order, and a missing CRS means EPSG:4326. kind says whether the coordinates came from the source, a generated layout, a manual edit, or a derived transform.

Harvest and emit

A parser promotes source coordinates into location, sets the space, and removes the raw keys from extras. Writers read location.

FormatFieldsSpace
PowerWorld PWBValidated bus latitude/longitude fields in supported 425/537 record layoutsgeographic
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
DOE GO Challenge 3bus longitude/latitudegeographic
OpenDSSBuscoordsunknown; a diagnostic identifies values within longitude and latitude bounds
BMOPF JSONBMOPFTools bus[id].geo Point and line[id].geo LineString; legacy longitude/latitude; canonical extras.geojsondeclared CRS, geographic when omitted

MATPOWER, PSS/E, PowerModels, egret, PSLF, and Surge have no place for geometry. Emitting a located case to one of them reports the dropped locations, in the same way it reports a dropped base_frequency. If you need the coordinates to survive, powerio geo extract writes them as a separate layer document.

The geographic document

Coordinates also arrive and leave as files of their own, such as a Buscoords CSV next to a DSS master or a GeoJSON export from a GIS tool, and a renderer can hand back the layout it computed the same way. The container for such a file is GeoLayer, which the Rust facade’s parse returns as PioValue::GeoLayer.

The canonical document is a GeoJSON FeatureCollection with one foreign member, powerio_geo, and the suggested extension is .geo.json:

{
  "type": "FeatureCollection",
  "powerio_geo": { "powerio_version": "0.11.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.

Parsing is tolerant and emission is canonical. GeoLayer::parse takes UTF-8 text 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. A feature refers to its element by up to three key fields, tried in order: uid, then id, then case insensitive name. A branch route can also fall back to the unordered (from, to) bus pair. A bare integer branch id (branch, branchid, branchnumber, catsid) is accepted on the way in as a one based positional row alias and is not written back; the uid in the payload is the durable key. A branch key accepts id when kind explicitly identifies a line or branch. An untyped feature counter does not identify equipment. BMOPFTools bus_from/bus_to endpoint names are accepted. A CRS string or a named CRS object retains its identifier; PowerIO does not reproject the coordinates.

BalancedNetwork::to_geo_layer() turns a network’s coordinates into a layer, and BalancedNetwork::apply_geo_layer(&layer) applies one and returns a GeoApplyReport with the matched and unmatched feature counts plus unlocated_buses and unlocated_branches, the elements still without geometry when the pass ends. Together those counts let you tell a layer that matched nothing from a model that needed nothing, and report.require_located() is the one line check for a caller that wants everything placed. The multiconductor equivalents, to_dist_geo_layer and apply_dist_geo_layer, live in powerio::dist_geo. The CLI wraps the same functions:

$ 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 a drawing GeoLayer with decoded bus positions, branch paths, and substation positions. Coordinates remain in drawing units. Unsupported or unmatched bus and branch records produce diagnostics. Python also keeps the raw display compatibility helper parse_display, which returns DisplayData(kind="powerworld", data=PwdDisplay(...)) with the canvas dimensions, a timestamp, and the substation symbols.

The facade helpers connect it to the geo model. to_geo_layer_from_pwd lifts the substation symbols into a diagram space GeoLayer, which is also what powerio geo extract case.pwd does. to_geo_layer_from_aux_text parses the Latitude and Longitude columns of an AUX Substation table straight into a geographic layer. apply_substation_points joins either layer onto buses through the SubNum extras key. to_lonlat_from_pwd_mercator is a documented, approximate inverse of the projection PowerWorld’s auto generated layouts use, for when you want to place a diagram on a map. The component crate keeps to_geo_layer_from_aux_substations(&AuxFile) for parser authors, since the facade does not expose its borrowed parser type.

A bus row in a complete case export has its own coordinates as well. The aux reader promotes the substation Latitude:1/Longitude:1 pair, or, when those are absent, the bus’s own bare Latitude/Longitude pair, into Bus.location, and a promoted pair leaves extras.

In Rust a display file parses to PioValue::GeoLayer. There is no path from a display file to a BalancedNetwork; it is always a layer, and both module emission and PowerIO IR handle it as one.

Distribution graph projection

MulticonductorNetwork::to_graph() returns a bus and terminal graph and does not need coordinates to do it; in Python that is dist_net.to_graph(). Graph topology and geographic placement stay separate data.

PowerIO stores and transports coordinates and does not compute them. Laying out a case that has none is renderer math and belongs in the consumer, which can store the result with kind = synthetic so the origin of the coordinates survives.

Python has parse_geo, and both network types have to_geo_layer() and apply_geo_layer().

BMOPF geometry and schema validity

PowerIO reads the geometry proposed in BMOPFTools #258: bus[id].geo contains a Point, line[id].geo contains a LineString, and meta.crs names the coordinate reference system. An absent CRS means WGS84. Coordinates appear in longitude/latitude order for WGS84. A malformed vertex invalidates its entire path; PowerIO does not connect the remaining vertices.

The schema currently identified as draft BMOPF 0.2 does not declare these per-equipment geo fields. Explicit BMOPF output therefore stores a FeatureCollection in the permitted extras.geojson location. Its features use kind, id, bus_from, and bus_to. The powerio_geo member preserves coordinate-space metadata for drawings and coordinates with no declared CRS. This keeps output valid against its stated schema while retaining geometry. Task Force review determines the eventual standard location.

Same-type source emission preserves the input bytes. Editing or explicitly selecting a schema version writes the typed coordinates, so a retained source cannot override updated positions. Both values of the compatibility setting BmopfEmitOptions::sideload_coordinates preserve geometry in extras.geojson. PowerIO IR also retains typed bus locations, line paths, and coordinate metadata.

A RAW revision 33 file does not contain the New England PWB bus-location records. Attach its matching GeoJSON, CSV, AUX, or PWD companion explicitly. PWD positions describe a drawing and must not be interpreted as latitude and longitude.