Blog Mapping Electrical Resources with GeoSPARQL - statnett/Talk2PowerSystem GitHub Wiki
Date: 2025-11-10. Author: Nikola Tulechki
Table of Contents
- Intro
- Visualizing Geospatial Information
- Geospatial Queries and Functions
- Integrating Third-Party Geospatial Data
- Conclusion
Objects in CIM often have a geospatial component. Power stations have specific locations, power lines follow defined routes across the landscape. Being able to visualize these on a map or query them based on their geometries (such as finding all objects within a bounding box) can provide valuable insights into the power system infrastructure.
To manipulate geospatial objects in GraphDB, we must use the "GeoSPARQL Ontology" to represent them (see the GraphDB documentation on GeoSPARQL support). In working with CIM data, we strive to reuse existing RDF resources as much as possible, and they map nicely to the GeoSPARQL Ontology:
-
cim:PowerSystemResourcenodes with geospatial information receive the typegeo:Feature. -
cim:Locationobjects receive the typegeo:Geometryand hold the geospatial information in the form of a WKT (or Well Known Text) literal. - The
geo:hasGeometryrelation connects Features and Geometries, parallel to thecim:PowerSystemResource.Locationrelation from CIM.
Below is an example of the geometry of the 300OSLO-ASKER AC line segment urn:uuid:f1769c68-9aeb-11e5-91da-b8763fd99c5f:
<urn:uuid:f1769c68-9aeb-11e5-91da-b8763fd99c5f> a cim:ACLineSegment, geo:Feature ;
cim:IdentifiedObject.name "300OSLO-ASKER" ;
geo:hasGeometry urn:uuid:0ecda9c5-8ead-4a46-9c85-0ee083cff821 ;
cim:PowerSystemResource.Location urn:uuid:0ecda9c5-8ead-4a46-9c85-0ee083cff821 .
<urn:uuid:0ecda9c5-8ead-4a46-9c85-0ee083cff821> a cim:Location, geo:Geometry ;
geo:asWKT "LINESTRING(10.5884369676798 59.9186412503614, 10.7850931467799 59.9405454311513)"^^geo:wktLiteralNote the WKT string defining the geospatial information. In this case, it's a simple line defined by two pairs of coordinates. Such geometries can become quite complex, and tools like the WKT Playground are useful for decoding and constructing them.
The following query counts all objects that currently have geometries on the public CIM endpoint.
PREFIX sesame: <http://www.openrdf.org/schema/sesame#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX cim: <https://cim.ucaiug.io/ns#>
SELECT ?type (COUNT(*) AS ?n) {
?x sesame:directType ?type ;
cim:IdentifiedObject.name ?name ;
geo:hasGeometry [] .
FILTER(?type!=geo:Feature)
} GROUP BY ?type ORDER BY DESC(?n)Besides WKT, geospatial data can be represented in CIM as cim:PositionPoint nodes, containing each a pair of coordinates, recorded with the cim:PositionPoint.xPosition and cim:PositionPoint.yPosition property.
When part of a line, these nodes are sequenced using the cim:PositionPoint.sequenceNumber property. We convert it to WKT, as part of the loading procedure with the this SPARQL query.
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX cim: <https://cim.ucaiug.io/ns#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
insert {
graph ?g {
?psr a geo:Feature;
geo:hasGeometry ?loc .
?loc a geo:Geometry;
geo:asWKT ?wkt .
}
} where {
select ?psr ?loc ?g (strdt(concat(?type,"(",group_concat(concat(str(?x)," ",str(?y)); separator=", "),")"),geo:wktLiteral) as ?wkt)
where {
graph ?g {
?loc cim:Location.PowerSystemResources ?psr .
?point cim:PositionPoint.Location ?loc ;
cim:PositionPoint.sequenceNumber ?seq;
cim:PositionPoint.xPosition ?x;
cim:PositionPoint.yPosition ?y;
.
}
bind(if(exists{
?loc ^cim:PositionPoint.Location/cim:PositionPoint.sequenceNumber 2
},
"LINESTRING","POINT") as ?type)
} group by ?g ?psr ?loc ?type order by ?seq
}Note the logic choosing whether to emit a LINESTRING or POINT WKT literal, based on the existence of a second (hence line-forming)cim:PositionPoint.
One of the most useful features of geospatial information is the ability to visualize it on a map.
Unfortunately, GraphDB does not currently have built-in mapping support,
but there exist a number of third-party tools that work out of the box with GeoSPARQL RDF data using WKT literals.
One such tool is YasGUI
that we use for the examples here.
We specify our public SPARQL endpoint https://cim.ontotext.com/graphdb/repositories/cim in YasGUI settings (whereas https://cim.ontotext.com/graphdb/sparql is our SPARQL editor used to write queries)
which we use for the examples here, running on the public endpoint at cim.ontotext.com.
This query, for example, shows all the geometries present in the data on a map.
PREFIX cim: <https://cim.ucaiug.io/ns#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
SELECT * WHERE {
?psr a geo:Feature ;
geo:hasGeometry ?loc ;
cim:IdentifiedObject.name ?wktLabel .
?loc a geo:Geometry ; geo:asWKT ?wkt .
}The geometries used for plotting are contained in the ?wkt variable and the label in the associated ?wktLabel.

We can combine data from the graph and query-time SPARQL functions to construct more informational visualizations. This query displays the geometries in the graph while additionally:
- Adding a color to the AC Lines, based on their nominal voltage (variable
wktColor). - Constructing richer tooltips by concatenating multiple strings into each tooltip (variables
wktLabelandwktTooltip)
Note that any selected variable that contains data typed as geo:wktLiteral will be plotted on the map, regardless of how it is named.
If such a variable is found, the renderer will also look for variables with the same name followed by Label, Color and show the relative properties to the displayed geometry.
PREFIX afn: <http://jena.apache.org/ARQ/function#>
PREFIX sesame: <http://www.openrdf.org/schema/sesame#>
PREFIX cim: <https://cim.ucaiug.io/ns#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
SELECT * WHERE {
?psr geo:hasGeometry/geo:asWKT ?wkt;
sesame:directType ?type;
cim:IdentifiedObject.name ?name .
filter(?type!=geo:Feature)
bind(afn:localname(?type) as ?typeName)
bind(concat(?typeName,": ",?name) as ?wktLabel)
bind(?wktLabel as ?wktTooltip)
optional {
?psr cim:ConductingEquipment.BaseVoltage/cim:BaseVoltage.nominalVoltage ?voltage
bind(if(?voltage<=300,"green","red") as ?wktColor)
}
}
Besides visualization, geospatial data can also be queried and reasoned upon directly in GraphDB. The relationships between geospatial objects—such as intersection and overlap—and the distance between them, as well as their geospatial characteristics (such as the area of a polygon), are accessible via a series of SPARQL functions.
Here are some example queries that fetch all geometries which intersect the Sandefjord business and industry area:
In this first example, we use the function geof:sfIntersects
and fetch both geometries from the data:
PREFIX cim: <https://cim.ucaiug.io/ns#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
SELECT * WHERE {
bind(<urn:uuid:4e51e598-8948-4c85-b151-f44ffddc5545> as ?psr1) #Sandefjord_business_and_industry_area
?psr1 a geo:Feature ;
geo:hasGeometry ?loc1 .
?loc1 a geo:Geometry;
geo:asWKT ?wkt1 .
?psr2 a geo:Feature ;
geo:hasGeometry ?loc2 ;
cim:IdentifiedObject.name ?name2 .
?loc2 a geo:Geometry;
geo:asWKT ?wkt2 ;
filter(geof:sfIntersects(?wkt1,?wkt2))
}In this second example, we use the magic predicate geo:sfIntersects,
which relies on an underlying geospatial index, making querying large datasets efficient and possible.
PREFIX cim: <https://cim.ucaiug.io/ns#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
SELECT * WHERE {
bind(<urn:uuid:4e51e598-8948-4c85-b151-f44ffddc5545> as ?psr1) #Sandefjord_business_and_industry_area
?psr1 a geo:Feature ;
geo:hasGeometry ?loc1 .
?loc1 a geo:Geometry;
geo:asWKT ?wkt1 .
?psr2 a geo:Feature ;
geo:hasGeometry ?loc2 ;
cim:IdentifiedObject.name ?name2 .
?loc2 a geo:Geometry;
geo:asWKT ?wkt2 .
?loc1 geo:sfIntersects ?loc2 .
}In this third example, instead of fetching the geometry from the data, we construct a rectangle directly in the query. This query construction pattern is useful for workflows where the user draws a bounding box, and the system should display all features within.
PREFIX cim: <https://cim.ucaiug.io/ns#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
SELECT * WHERE {
bind("POLYGON((9.781351089477539 60.718296868537124,9.816219806671143 59.94741379052206,12.15358257293701 59.92807630232278,12.245099544525145 60.7278039605813,9.781351089477539 60.718296868537124))"^^geo:wktLiteral as ?wkt) .
?psr2 geo:hasGeometry ?geo2 ;
cim:IdentifiedObject.name ?name2 .
?geo2 geo:asWKT ?wkt2 .
filter(geof:sfIntersects(?wkt,?wkt2))
}And here is a query ordering all geometries according to their distance from an arbitrary point:
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX cim: <https://cim.ucaiug.io/ns#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
PREFIX uom: <http://www.opengis.net/def/uom/OGC/1.0/>
SELECT * WHERE {
BIND("POINT(10.221405029296875 59.129453854784)"^^geo:wktLiteral as ?my_pos) #Sandefjord
?psr a geo:Feature ;
geo:hasGeometry ?loc ;
cim:IdentifiedObject.name ?wktLabel .
?loc a geo:Geometry; geo:asWKT ?wkt .
BIND(geof:distance(?my_pos, ?wkt, uom:metre) as ?distance)
FILTER(LANG(?wktLabel) != "no")
} ORDER BY ?distanceAnother exciting feature of working with geospatial data in RDF is the possibility to integrate rich third-party resources and use them in conjunction with your private data. One such example is OpenStreetMap, which is readily accessible as RDF.
In the following example, we leverage the public OSM2RDF endpoint with SPARQL federation. From it, we can fetch detailed geometries of any object in OpenStreetMap In our case these are polygons corresponding to different administrative divisions of Norway (e.g the county of Viken).
We can then directly use the polygon to query the features in CIM and select only those, which fall within a given polygon—that is, which are part of a certain administrative subdivision.
PREFIX cim: <https://cim.ucaiug.io/ns#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
PREFIX osmrel: <https://www.openstreetmap.org/relation/>
PREFIX osmkey: <https://www.openstreetmap.org/wiki/Key:>
SELECT * WHERE {
service <https://qlever.dev/api/osm-planet> {
select ?wkt {
?osm osmkey:name "Viken" ;
osmkey:was:boundary "administrative" ;
osmkey:was:admin_level "4" ;
geo:hasGeometry ?geo .
?geo geo:asWKT ?wkt .
}
}
?psr2 geo:hasGeometry ?geo2 ;
cim:IdentifiedObject.name ?name2 .
?geo2 geo:asWKT ?wkt2 .
filter(geof:sfIntersects(?wkt,?wkt2))
}This query pattern can be extended to other sorts of features such as rivers and roads, and can be used not only for querying and displaying, but also to infer new information, such as:
- Systematically adding associations with the administrative territorial hierarchy for all geolocated objects.
- Finding power lines that pass in proximity to rivers or lakes
- Etc
Integrating GeoSPARQL in CIM opens up new exciting possibilities for integrating power system analytics with geospatial analysis and visualizations. By leveraging the GeoSPARQL ontology, we can seamlessly integrate geospatial data with electrical infrastructure information, enabling everything from interactive visualization to complex spatial queries. The ability to combine private CIM data with public geospatial resources like OpenStreetMap further extends these capabilities, allowing for rich, context-aware analyses that bridge the gap between electrical engineering and geographic information.