Skip to content

Electrical Diagrams

Nikola Tulechki edited this page Apr 7, 2026 · 4 revisions

Table of Contents

Intro

This page covers PowSyBl electrical diagrams. For other visualization approaches see:

We use PowSyBl Diagram (docs) to generate two kinds of electrical diagrams:

img/PowSyBl-nad-sld-diagrams.png

  • Network-area diagrams (NAD).
    • These show voltage levels as nodes, and the lines and transformers between voltage levels as edges
    • Can display the whole grid, one or several voltage levels, and the neighborhood of one or several voltage levels up to a given depth
  • Single-line diagrams (SLD).
    • These show equipment (switches, disconnectors, busbars, voltage levels, power transformers, loads, generators), adjacent lines, voltages and input/output flows
    • Can display one or several substations, voltage levels or areas
    • If several substations are requested, the user needs to provide a 2D array of their MRIDs and they are laid out in a matrix exactly like the 2D array

Section Example Diagrams shows the 4 types of diagram that we have generated (2 NAD and 2 SLD).

Other kinds of electrical diagrams can be found in:

Diagrammable PSRs

Let's count Nordic44+Telemark120 Power System Resources (PSR) that can be diagrammed by PowSyBl. TODO: amend the page Areas and Zones

PREFIX nc: <https://cim4.eu/ns/nc#>
PREFIX cim: <https://cim.ucaiug.io/ns#>
select ?type (count(*) as ?c) {
  values ?type {
    cim:Substation cim:VoltageLevel
    cim:ConformLoad cim:NonConformLoad cim:ConformLoadGroup cim:NonConformLoadGroup
    cim:GeographicalRegion cim:SubGeographicalRegion
    cim:ControlArea nc:SchedulingArea nc:BiddingZone
    cim:LoadArea cim:SubLoadArea
  }
  ?x a ?type
} group by ?type order by ?type
?type ?c
cim:ConformLoad 50
cim:ConformLoadGroup 37
cim:ControlArea 5
cim:GeographicalRegion 1
cim:LoadArea 5
cim:NonConformLoad 13
cim:NonConformLoadGroup 13
cim:SubGeographicalRegion 11
cim:SubLoadArea 12
cim:Substation 55
cim:VoltageLevel 60
nc:BiddingZone 45
nc:SchedulingArea 11

Note: PowSyBl can diagram directly substations and voltage levels. But we can diagram all voltage levels within an area, so we also count different kinds of areas present in our data.

Let's count relations between all these PSRs:

PREFIX onto: <http://www.ontotext.com/>
PREFIX nc: <https://cim4.eu/ns/nc#>
PREFIX cim: <https://cim.ucaiug.io/ns#>
select ?type1 ?p ?type2 (count(*) as ?c)
from onto:explicit {
  values ?type1 {
    cim:Substation cim:VoltageLevel
    cim:ConformLoad cim:NonConformLoad cim:ConformLoadGroup cim:NonConformLoadGroup
    cim:GeographicalRegion cim:SubGeographicalRegion
    cim:ControlArea nc:SchedulingArea nc:BiddingZone
    cim:LoadArea cim:SubLoadArea
  }
  values ?type2 {
    cim:Substation cim:VoltageLevel
    cim:ConformLoad cim:NonConformLoad cim:ConformLoadGroup cim:NonConformLoadGroup
    cim:GeographicalRegion cim:SubGeographicalRegion
    cim:ControlArea nc:SchedulingArea nc:BiddingZone
    cim:LoadArea cim:SubLoadArea
  }
  ?x a ?type1.
  ?x ?p ?y.
  ?y a ?type2.
} group by ?type1 ?p ?type2 order by ?type1 ?p ?type2
?type1 ?p ?type2 ?c
cim:ConformLoad cim:ConformLoad.LoadGroup cim:ConformLoadGroup 50
cim:ConformLoad cim:Equipment.EquipmentContainer cim:VoltageLevel 50
cim:ConformLoadGroup cim:LoadGroup.SubLoadArea cim:SubLoadArea 37
cim:ControlArea cim:ControlArea.EnergyArea cim:LoadArea 4
cim:NonConformLoad cim:Equipment.EquipmentContainer cim:VoltageLevel 13
cim:NonConformLoad cim:NonConformLoad.LoadGroup cim:NonConformLoadGroup 13
cim:NonConformLoadGroup cim:LoadGroup.SubLoadArea cim:SubLoadArea 13
cim:SubGeographicalRegion cim:SubGeographicalRegion.Region cim:GeographicalRegion 11
cim:SubLoadArea cim:SubLoadArea.LoadArea cim:LoadArea 12
cim:Substation cim:Substation.Region cim:SubGeographicalRegion 55
cim:VoltageLevel cim:VoltageLevel.Substation cim:Substation 60
nc:SchedulingArea nc:SchedulingArea.BiddingZone nc:BiddingZone 11
nc:SchedulingArea nc:SchedulingArea.ControlArea cim:ControlArea 11

Let's show this as a diagram:

Connectivity:

  • cim:GeographicalRegion, cim:SubGeographicalRegion are related to cim:VoltageLevel through cim:Substation
  • cim:LoadArea, cim:SubLoadArea are related to cim:VoltageLevel through ConformLoad or NonConformLoad and LoadGroup, namely this property path (we have checked that each VoltageLevel relates to only one SubLoadArea):
  ?voltageLevel cim:EquipmentContainer.Equipments/(cim:ConformLoad.LoadGroup|cim:NonConformLoad.LoadGroup)/cim:LoadGroup.SubLoadArea ?subLoadArea

Generate Diagram Metadata

We write a number of CONSTRUCT queries to make RDF that describes diagram instances. These queries select appropriate PSRs for PowSyBl diagramming:

Query cimr:DiagramKind.PowSyBl- PSRs
PowSyBl-SLD-substation.rq SingleLineDiagram Substation
PowSyBl-SLD-2substations.rq SingleLineDiagram-Multi Substation pairs connected through a Line or directly (that's a Nordic44 modeling shortcut)
PowSyBl-NAD-all.rq NetworkAreaDiagram Full NAD of all voltage levels
PowSyBl-NAD-SubGeographicalRegion.rq NetworkAreaDiagram NAD of voltage levels in a SubGeographicalRegion
PowSyBl-NAD-LoadArea.rq NetworkAreaDiagram NAD of voltage levels in a LoadArea
PowSyBl-NAD-SubLoadArea.rq NetworkAreaDiagram NAD of voltage levels in a SubLoadArea

The metadata uses cimd:Diagram terms as defined in cim-diagrams.ttl.

The query 2substations is most complicated because it picks pairs of substations and It looks like this:

PREFIX cimd: <https://cim.ucaiug.io/diagrams#> 
PREFIX cimr: <https://cim.ucaiug.io/rules#> 
PREFIX cim:  <https://cim.ucaiug.io/ns#>
PREFIX afn:  <http://jena.apache.org/ARQ/function#>
PREFIX dct:  <http://purl.org/dc/terms/>
prefix geo:  <http://www.opengis.net/ont/geosparql#>

construct {
  ?diag a cimd:Diagram; cimd:Diagram.kind cimd:DiagramKind.PowSyBl-SingleLineDiagram-Multi;
    cim:IdentifiedObject.name ?diagName; cim:IdentifiedObject.mRID ?diagMrid; cim:IdentifiedObject.description ?diagDescr;
    cimd:Diagram.PowerSystemResource ?psr1,?psr2; cimd:Diagram.mRIDs ?mrids; cimd:Diagram.link ?link; dct:format "image/svg+xml"
} 
where {
  {select distinct ?psr1 ?psr2 ?psr1Name ?psr2Name ?psr1Mrid ?psr2Mrid ?x1 ?x2 {
    ?psr1 a cim:Substation; cim:IdentifiedObject.name ?psr1Name; cim:IdentifiedObject.mRID ?psr1Mrid.
    ?psr2 a cim:Substation; cim:IdentifiedObject.name ?psr2Name; cim:IdentifiedObject.mRID ?psr2Mrid.
    {?psr1 cimr:connectedThroughPart ?psr2} 
    union {?psr1 cimr:connectedThroughPart ?line. ?line a cim:Line. ?psr2 cimr:connectedThroughPart ?line}
    filter(?psr1Name < ?psr2Name)
    optional {
      ?psr1 geo:hasGeometry/geo:asWKT ?geo1.
      ?psr2 geo:hasGeometry/geo:asWKT ?geo2
      bind(xsd:decimal(replace(str(?geo1),".*POINT *[(]([0-9.]+) ([0-9.]+)[)]","$1","i")) as ?x1)
      bind(xsd:decimal(replace(str(?geo2),".*POINT *[(]([0-9.]+) ([0-9.]+)[)]","$1","i")) as ?x2)
    }
  }}
  bind(coalesce(?x1 > ?x2, false) as ?swap)
  bind(replace(?psr1Name," ","-") as ?psr1Slug)
  bind(replace(?psr2Name," ","-") as ?psr2Slug)
  bind(uuid() as ?diag)
  bind(afn:localname(?diag) as ?diagMrid)
  bind(concat("Diagram of substations ",
              if(?swap,?psr2Name,?psr1Name)," and ",if(?swap,?psr1Name,?psr2Name)) as ?diagName)
  bind(concat("PowSyBl Single-Line-Diagram of substations ",
              if(?swap,?psr2Name,?psr1Name)," and ",if(?swap,?psr1Name,?psr2Name)) as ?diagDescr)
  bind(concat('[["',if(?swap,?psr2Mrid,?psr1Mrid),'","',if(?swap,?psr1Mrid,?psr2Mrid),'"]]') as ?mrids)
  bind(concat("PowSyBl-SLD-2substations-",
              if(?swap,?psr2Slug,?psr1Slug),"-and-",if(?swap,?psr1Slug,?psr2Slug),".svg") as ?link)
}

Notes:

  • The triple patterns are in a subquery before invoking uuid() to work around the bug uuid() sometimes yields same result over different query solutions.
  • We select pairs of substations that are connected:
    • Either directly through some of their parts (this is a modeling shortcut in Nordic44)
    • Or through a shared Line
  • We eliminate pair permutations by a filter ?psr1Name < ?psr2Name
  • However, we want to order each pair by Longitude (the more western substation first, the more eastern substaion second). This is
    • So we fetch the geo:asWKT (GeoSPARQL WKT) geometries of both substations
    • Extract the x1, x2 coordinates through regex replace() because GraphDB doesn't have the GeoSPARQL 1.1 functions geof:minX, maxX
    • Compute a flag ?swap: whether the order of the two substations (which is alphabetical) needs to be swapped
  • Then we transform each Name to Slug by converting spaces to dashes
  • Generate a new URN for the Diagram object: bind(uuid() as ?diag)
  • Fetch only the UUID part of the URN as ?diagMrid
  • Form several strings (name, descr, mrids, link) by using a pattern like concat(if(?swap...)) to account for the order of the two substations
    • mrids is a list of mRIDs of the PSRs to show on the diagram, as needs to be passed to PowSyBl. See next section for details.

It produces metadata like this:

<urn:uuid:2a665063-e775-404f-80a2-ecd0a3774b8d> a cimd:Diagram;
  cimd:Diagram.kind cimd:DiagramKind.PowSyBl-SingleLineDiagram-Multi;
  cim:IdentifiedObject.name "Diagram of substations NEDENES and TELEMA2 04 CB6";
  cim:IdentifiedObject.mRID "2a665063-e775-404f-80a2-ecd0a3774b8d";
  cim:IdentifiedObject.description "PowSyBl Single-Line-Diagram of substations NEDENES and TELEMA2 04 CB6";
  cimd:Diagram.PowerSystemResource <urn:uuid:681a2179-5a55-11eb-a658-74e5f963e191>,
    <urn:uuid:811613d5-c44f-4daa-9dc1-03005740a4e5>;
  cimd:Diagram.mRIDs "[[\"681a2179-5a55-11eb-a658-74e5f963e191\",\"811613d5-c44f-4daa-9dc1-03005740a4e5\"]]";
  cimd:Diagram.link "PowSyBl-SLD-2substations-NEDENES-and-TELEMA2-04-CB6.svg";
  dct:format "image/svg+xml" .

The metadata for all diagrams is concatenated as diagrams.ttl.

Generate Diagrams

This query SELECTS a table of parameters to drive PowSyBl diagram generation:

select ?kind ?mrids ?link {
  ?x a cimd:Diagram;
    cimd:Diagram.kind ?kindUrl;
    cimd:Diagram.mRIDs ?mrids;
    cimd:Diagram.link ?link.
  bind(strafter(str(?kindUrl),"PowSyBl-") as ?kind)
}

It can be run in two ways:

  • On diagrams.ttl using Jena ARQ (currently we use this way)
  • Or the turtle file can be loaded in GraphDB first, then the query can be run using curl

The result is saved as diagrams.tsv and looks like this. mrids is a string representation of the Python parameter that needs to be passed to PowSyBl

?kind ?mrids ?link What is mrids
"SingleLineDiagram" ""f17696b8-9aeb-11e5-91da-b8763fd99c5f"" "PowSyBl-SLD-substation-VYBORG_HVDC.svg" single substation
"SingleLineDiagram-Multi" ""f17695fe-9aeb-11e5-91da-b8763fd99c5f",..." "PowSyBl-SLD-2substations-AJAURE-and-GRUNDFORS.svg" 2D vector consisting of a single row of 2 substations
"NetworkAreaDiagram" "PowSyBl-NAD-all.svg" none
"NetworkAreaDiagram" "("d82114b7-09c0-4cb4-b9c8-19a5fdaf991a",...)" "PowSyBl-NAD-SubGeographicalRegion-DSO-V.svg" array of VoltageLevels in that SubGeographicalRegion
"NetworkAreaDiagram" "("d82114b7-09c0-4cb4-b9c8-19a5fdaf991a",...)" "PowSyBl-NAD-SubLoadArea-FI-SLA.svg" array of VoltageLevels in that SubLoadArea
"NetworkAreaDiagram" "("d82114b7-09c0-4cb4-b9c8-19a5fdaf991a",...)" "PowSyBl-NAD-LoadArea-FI-LA.svg" array of VoltageLevels in that LoadArea

IMPORTANT: I hoped that TSV export will omit all the ugly quoting but unfortunately that's not the case. So we need to use proper TSV parsing to ensure that mrids pases through ok.

Running Python

We use the Python version of PowSyBl (which is on par with the Java version).

The Python script:

  • Loads a zip of Nordic44 and Telemark-120 Grid data to PowSyBl
  • Loads the above TSV and iterates over each row
  • Saves each generated diagram to folder svg using the filename provided in link

SVG Clickability

The generated SVG diagrams are interactive: clicking on a diagram element in the chatbot UI navigates to the corresponding Power System Resource in GraphDB. This section describes how element identification works and how clickability is achieved.

See also allow more SVG areas to be clickable #366 for ongoing improvements.

mRID Encoding in SVG IDs

PowSyBl encodes equipment mRIDs in SVG element id attributes, but with character escaping:

  • id is prepended to the mRID
  • - (dash) is encoded as _45_
  • _ (underscore) is encoded as _95_
  • A "role" suffix may be appended, e.g. _SW_fict, _NW_LABEL, _ARROW_ACTIVE
mRID SVG ID
0169b8e7-c05b-3640-b55e-22b40081ca21 id0169b8e7_45_c05b_45_3640_45_b55e_45_22b40081ca21
0345061e-37c9-9a49-b632-2af77bdca3a2 id0345061e_45_37c9_45_9a49_45_b632_45_2af77bdca3a2_95_SW_95_fict

There are also SVG IDs that encode multiple mRIDs (idBUSCO, idINTERNAL, idEDGE patterns). A detailed analysis is in diagram/svg-click/.

The algorithm to extract a mRID from an SVG element ID:

  1. Globally replace _45_ with -, _95_ with _
  2. If it doesn't start with id, skip. Otherwise remove the id prefix
  3. If the remainder starts with a UUID (optionally followed by _), return that UUID

As a regex:

s{_45_}{-}g; s{_95_}{_}g;
s{^id([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(_.*)?$}{$1}

Post-Processing with add_iri.py

PowSyBl generates each diagram as a pair of files: an SVG and a companion JSON metadata file. The JSON contains a mapping from SVG element IDs to equipment mRIDs (equipmentId), structured differently for each diagram type:

  • NAD JSON: has sections nodes, busNodes, edges, textNodes. Each entry has a svgId (a sequential number like "0", "265") and an equipmentId (the mRID). For example:
    {"svgId": "0", "equipmentId": "1f4fabd4-8c57-c245-b107-4e64c9007ae3", "legendHeader": ["ARENDAL 420"]}
  • SLD JSON: has a nodes section where each entry has an id (the hex-encoded SVG ID) and an equipmentId (the mRID). For example:
    {"id": "id681a2fdd_45_5a55_45_11eb_45_a658_45_74e5f963e191_95_TWO", "equipmentId": "681a2fdd-5a55-11eb-a658-74e5f963e191"}

The script add_iri.py reads the JSON, builds an svgId -> mRID mapping, then walks the SVG and adds an iri attribute to every element whose ID maps to one or more mRIDs. The value is a urn:uuid: IRI (or multiple IRIs joined with >).

The format (NAD vs SLD) is auto-detected from the JSON structure. For SVG elements not found in the JSON mapping, the script falls back to regex-based mRID extraction from the SVG ID itself (using the encoding rules described above).

This is run as part of the diagram generation pipeline after PowSyBl produces the SVGs.

Clickability Status

Both SLD and NAD diagrams are now clickable. This is declared in the cimd ontology via cimd:DiagramKind.isClickable true on all PowSyBl diagram kinds (SingleLineDiagram, SingleLineDiagram-Multi, NetworkAreaDiagram) and on GraphDB-VizGraph.

The chatbot UI uses this flag to determine whether to enable click interaction on a displayed diagram.

Example Diagrams

This section gives an example of each of the generated diagrams:

Single-Line-Diagram of Substation

Arendal:

Single-Line-Diagram of Two Substations

Kristiansand and Arendal:

Network Area Diagram

All voltage levels in Nordic44 and Telemark-120:

Network Area Diagram of SubGeographicalRegion

NO5 (Western Norway):

Clone this wiki locally