-
Notifications
You must be signed in to change notification settings - Fork 2
Electrical Diagrams
Table of Contents
This page covers PowSyBl electrical diagrams. For other visualization approaches see:
- Visual Graph -- GraphDB Visual Graph explorations
- Blog: Mapping Electrical Resources with GeoSPARQL -- Geospatial map visualizations
- Diagrams -- Overview of all visualization options
We use PowSyBl Diagram (docs) to generate two kinds of electrical diagrams:

- 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:
- Dataset Telemark 120#diagrams in this wiki, and CIM4NoUtility/Telemark-120/diagrams/svg in the respective github repo: at least 5 kinds of diagrams, some of them instantiated for about 10 substations
- Nordic44/Grid/svg: 3 kinds of CimDesk substation diagrams,
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:SubGeographicalRegionare related tocim:VoltageLevelthroughcim:Substation -
cim:LoadArea, cim:SubLoadAreaare related tocim:VoltageLevelthroughConformLoadorNonConformLoadandLoadGroup, namely this property path (we have checked that eachVoltageLevelrelates to only oneSubLoadArea):
?voltageLevel cim:EquipmentContainer.Equipments/(cim:ConformLoad.LoadGroup|cim:NonConformLoad.LoadGroup)/cim:LoadGroup.SubLoadArea ?subLoadAreaWe 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, x2coordinates through regexreplace()because GraphDB doesn't have the GeoSPARQL 1.1 functionsgeof:minX, maxX - Compute a flag
?swap: whether the order of the two substations (which is alphabetical) needs to be swapped
- So we fetch the
- Then we transform each
NametoSlugby 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 likeconcat(if(?swap...))to account for the order of the two substations-
mridsis 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.
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.ttlusing 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.
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
svgusing the filename provided inlink
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.
PowSyBl encodes equipment mRIDs in SVG element id attributes, but with character escaping:
-
idis 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:
- Globally replace
_45_with-,_95_with_ - If it doesn't start with
id, skip. Otherwise remove theidprefix - 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}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 asvgId(a sequential number like"0","265") and anequipmentId(the mRID). For example:{"svgId": "0", "equipmentId": "1f4fabd4-8c57-c245-b107-4e64c9007ae3", "legendHeader": ["ARENDAL 420"]} -
SLD JSON: has a
nodessection where each entry has anid(the hex-encoded SVG ID) and anequipmentId(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.
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.
This section gives an example of each of the generated diagrams:
Arendal:

Kristiansand and Arendal:

All voltage levels in Nordic44 and Telemark-120:

NO5 (Western Norway):
