3lbits/CIM4NoUtility

use geo:asGeoJSON instead of (or in addition to) GeoJSON-LD

Opened this issue · 6 comments

https://github.com/3lbits/Grunnprofil/raw/main/DIGIN10/Grid/CIMJSON-LD/DIGIN10-30-WattApp-GL.geojson is a GeoJSON file with a JSON-LD context.

So we can convert it to turtle to see it more clearly (or trig, but it contains no named graphs, so the result is the same):

curl -s https://raw.githubusercontent.com/3lbits/Grunnprofil/main/DIGIN10/Grid/CIMJSON-LD/DIGIN10-30-WattApp-GL.geojson | riot --syntax jsonld --formatted trig -

Here's the result (shortened):

<urn:uuid:4e51e598-8948-4c85-b151-f44ffddc5545>
        rdf:type                    cim:Feeder ;
        cim:IdentifiedObject.mRID   "urn:uuid:4e51e598-8948-4c85-b151-f44ffddc5545" ;
        cim:IdentifiedObject.name   "Sandefjord_næring_og_industri_område"@no , "Sandefjord_business_and_industry_area"@en .

<urn:uuid:971c4254-5365-4aaf-8fa6-02658b3f8e05>
        rdf:type              dcat:Dataset , geojson:FeatureCollection ;
        geojson:features      <urn:uuid:58aee5a8-0cd9-467e-b5e7-180ac161d9b8> .

<urn:uuid:58aee5a8-0cd9-467e-b5e7-180ac161d9b8>
        rdf:type            geojson:Feature ;
        geojson:geometry    [ rdf:type             geojson:Polygon ;
                              geojson:coordinates  ( ( ( 1.022902098465971E1 5.924698254345179E1 ) ( 1.018420614097019E1 5.919982925336586E1 ) ( 1.014288336302374E1 5.913888033657065E1 ) ( 1.020244248428966E1 5.913758660552921E1 ) ( 1.027635787582801E1 5.916126409167293E1 ) ( 1.031050251863826E1 5.922922084872761E1 ) ( 1.022902098465971E1 5.924698254345179E1 ) ) )
                            ] ;
        geojson:properties  <urn:uuid:4e51e598-8948-4c85-b151-f44ffddc5545> .

There are some problems here:

  1. GeoJSON-LD is not any sort of standard. It's a 1-person proposal https://geojson.org/geojson-ld/
  2. Semantic repositories do not index geometries represented in this way. Instead, they support GeoSPARQL
  3. The business object cim:Feeder is considered secondary to the Feature, a mere set of properties attached to the Feature. But in reality it's a distinct RDF resource with its own identity (URL).
  4. Most CIM resources have no geometry, so this "inverted" pattern does not apply to them. So all resources need to reside in the named graph representing the model (Dataset), and such named graph is missing above.

The above is a nice representation for geo toolkits that don't support semantics (eg the Github preview of https://github.com/3lbits/Grunnprofil/blob/main/DIGIN10/Grid/CIMJSON-LD/DIGIN10-30-WattApp-GL.geojson is nice)

But for semantic tools (eg semantic repositories), I propose to use GeoSPARQL.
1.0 supports WKT and GML ontologies. WKT is simpler and widely used, and could look something like this:

<urn:uuid:971c4254-5365-4aaf-8fa6-02658b3f8e05>
        rdf:type              dcat:Dataset.

<urn:uuid:971c4254-5365-4aaf-8fa6-02658b3f8e05> {

<urn:uuid:4e51e598-8948-4c85-b151-f44ffddc5545>
        rdf:type                    cim:Feeder, geo:Feature ;
        cim:IdentifiedObject.mRID   "urn:uuid:4e51e598-8948-4c85-b151-f44ffddc5545" ;
        cim:IdentifiedObject.name   "Sandefjord_næring_og_industri_område"@no , "Sandefjord_business_and_industry_area"@en ;
        geo:hasGeometry <urn:uuid:58aee5a8-0cd9-467e-b5e7-180ac161d9b8>.

<urn:uuid:58aee5a8-0cd9-467e-b5e7-180ac161d9b8>
        rdf:type            geo:Geometry ;
        geo:asWKT """Polygon (( 1.022902098465971E1 5.924698254345179E1, 1.018420614097019E1 5.919982925336586E1, 1.014288336302374E1 5.913888033657065E1, 1.020244248428966E1 5.913758660552921E1, 1.027635787582801E1 5.916126409167293E1, 1.031050251863826E1 5.922922084872761E1, 1.022902098465971E1 5.924698254345179E1))"""^^geo:wktLiteral.
}

GML is an XML format and is also widely used, eg it's the basis of CityGML.

GeoSPARQL 1.1 supports KML and GeoJSON geometries. To use the latter, you'd change only the last line:

  geo:asGeoJSON """{type: "Polygon",
coordinates: [[[10.229020984659712,59.24698254345179], [10.18420614097019,59.19982925336586], [10.142883363023742,59.13888033657065], [10.202442484289662,59.13758660552921], [10.276357875828012,59.16126409167293], [10.310502518638259,59.22922084872761], [10.229020984659712,59.24698254345179]]]}"""^^geo:geoJSONLiteral.

Important benefits of GeoSPARQL include:

  • It's an important and widely used standard.
  • You can emit the same geometry in multiple serializations to cater to different consumers, Just use several asFoo properties.
  • You can emit multiple geometries, eg centroid, bounding box (envelope), etc.
  • Semantic repositories index such literals in a geospatial index, which allows them to implement quick geospatial (region algebra) relations, eg to find all features within a region, the area or perimeter of a region, the shortest distance between two regions, etc.

A particular use case in electricity could be to compute easement, e.g. the area around a transmission line that is prohibited for building use. The function geof:buffer(geom,distance) can be used for that.

You can use JSON-LD context to capture part of a JSON payload as an RDF literal.
See w3c/json-ld-syntax#425 for details.

@VladimirAlexiev , thanks for your work/assistance in this area. This will offer a great opportunity for discussion on Friday I would assume.

@Sveino As per discussion in w3c/json-ld-syntax#425, I made a typo: the property is geo:asGeoJSON not geo:asGeoJSONLiteral. I fixed that in the example above.

Hello, do you have any suggestion for how this will be in a jsonld?
I have a couple of variants here:

Variant1

{
    "@context": {
        "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
        "cim": "https://cim.ucaiug.io/ns#",
        "eu": "http://iec.ch/TC57/CIM100-European#",
        "dcterms": "http://purl.org/dc/terms/",
        "dcat": "http://www.w3.org/ns/dcat#",
        "prov": "http://www.w3.org/ns/prov#",
        "xsd": "http://www.w3.org/2001/XMLSchema#",
        "nc-no": "https://cim4.eu/ns/nc-no#",
        "geo": "http://www.opengis.net/ont/geosparql#"
    },
    "@graph": [
        {
            "@id": "urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed",
            "@type": ["nc-no:ACLineSegmentSpan", "geo:Feature"],
            "cim:IdentifiedObject.mRID": "802a2294-3cc5-4d3a-b2ba-57706abfe8ed",
            "cim:IdentifiedObject.description": "ACLineSegmentSpan 2",
            "cim:IdentifiedObject.name": "ACLSS_2",
            "nc-no:PowerSystemResource.locationMethod": {
                "@id": "nc-no:locationMethodKind.measured"
            },
            "nc-no:ACLineSegmentSpan.aviationObstacleMarkingKind": {
                "@id": "nc-no:LineMarkingKind.colourMarking"
            },
            "nc-no:ACLineSegmentSpan.aviationObstacleLightingKind": {
                "@id": "nc-no:LineLightingKind.lit"
            },
            "nc-no:ACLineSegmentSpan.maxWidth": 3.97,
            "nc-no:ACLineSegmentSpan.maxHeight": 153.23,
            "nc-no:ACLineSegmentSpan.spanWireLength": 63.01,
            "nc-no:ACLineSegmentSpan.ACLineSegment": [
                {
                    "@id": "urn:uuid:f8b08d2f-9110-4016-8aca-bdcec9fa02be"
                }
            ],
            "geo:hasGeometry": {
                "geo:asWKT": "\"LINESTRING (6502691.542169236 972304.9284766684,6502691.540990914 972304.9204293368)\"^^geo:wktLiteral ;",
                "geo:asGeoJSON": "\"{\"type\": \"LineString\", \"coordinates\": [[[8.7007293714585,58.41467201058505], [8.7007293,58.414672]]]}\"^^geo:geoJSONLiteral ;"
            }
        }
    ]
}

Variant2

{
    "@context": {
        "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
        "cim": "https://cim.ucaiug.io/ns#",
        "eu": "http://iec.ch/TC57/CIM100-European#",
        "dcterms": "http://purl.org/dc/terms/",
        "dcat": "http://www.w3.org/ns/dcat#",
        "prov": "http://www.w3.org/ns/prov#",
        "xsd": "http://www.w3.org/2001/XMLSchema#",
        "nc-no": "https://cim4.eu/ns/nc-no#",
        "geo": "http://www.opengis.net/ont/geosparql#"
    },
    "@graph": [
        {
            "@id": "urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed",
            "@type": "nc-no:ACLineSegmentSpan, geo:Feature",
            "cim:IdentifiedObject.mRID": "802a2294-3cc5-4d3a-b2ba-57706abfe8ed",
            "cim:IdentifiedObject.description": "ACLineSegmentSpan 2",
            "cim:IdentifiedObject.name": "ACLSS_2",
            "nc-no:PowerSystemResource.locationMethod": {
                "@id": "nc-no:locationMethodKind.measured"
            },
            "nc-no:ACLineSegmentSpan.aviationObstacleMarkingKind": {
                "@id": "nc-no:LineMarkingKind.colourMarking"
            },
            "nc-no:ACLineSegmentSpan.aviationObstacleLightingKind": {
                "@id": "nc-no:LineLightingKind.lit"
            },
            "nc-no:ACLineSegmentSpan.maxWidth": 3.97,
            "nc-no:ACLineSegmentSpan.maxHeight": 153.23,
            "nc-no:ACLineSegmentSpan.spanWireLength": 63.01,
            "nc-no:ACLineSegmentSpan.ACLineSegment": [
                {
                    "@id": "urn:uuid:f8b08d2f-9110-4016-8aca-bdcec9fa02be"
                }
            ],
            "geo:hasGeometry": {
                "geo:asWKT": "\"LINESTRING (6502691.542169236 972304.9284766684,6502691.540990914 972304.9204293368)\"^^geo:wktLiteral ;",
                "geo:asGeoJSON": "\"{\"type\": \"LineString\", \"coordinates\": [[[8.7007293714585,58.41467201058505], [8.7007293,58.414672]]]}\"^^geo:geoJSONLiteral ;"
            }
        }
    ]
}

Variant3

{
    "@context": {
        "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
        "cim": "https://cim.ucaiug.io/ns#",
        "eu": "http://iec.ch/TC57/CIM100-European#",
        "dcterms": "http://purl.org/dc/terms/",
        "dcat": "http://www.w3.org/ns/dcat#",
        "prov": "http://www.w3.org/ns/prov#",
        "xsd": "http://www.w3.org/2001/XMLSchema#",
        "nc-no": "https://cim4.eu/ns/nc-no#",
        "geo": "http://www.opengis.net/ont/geosparql#"
    },
    "@graph": [
        {
            "@id": "urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed",
            "@type": "nc-no:ACLineSegmentSpan",
            "cim:IdentifiedObject.mRID": "802a2294-3cc5-4d3a-b2ba-57706abfe8ed",
            "cim:IdentifiedObject.description": "ACLineSegmentSpan 2",
            "cim:IdentifiedObject.name": "ACLSS_2",
            "nc-no:PowerSystemResource.locationMethod": {
                "@id": "nc-no:locationMethodKind.measured"
            },
            "nc-no:ACLineSegmentSpan.aviationObstacleMarkingKind": {
                "@id": "nc-no:LineMarkingKind.colourMarking"
            },
            "nc-no:ACLineSegmentSpan.aviationObstacleLightingKind": {
                "@id": "nc-no:LineLightingKind.lit"
            },
            "nc-no:ACLineSegmentSpan.maxWidth": 3.97,
            "nc-no:ACLineSegmentSpan.maxHeight": 153.23,
            "nc-no:ACLineSegmentSpan.spanWireLength": 63.01,
            "nc-no:ACLineSegmentSpan.ACLineSegment": [
                {
                    "@id": "urn:uuid:f8b08d2f-9110-4016-8aca-bdcec9fa02be"
                }
            ],
            "nc-no:PowerSystemResource.SpatialObject": {
                "@id": "urn:uuid:2b25854f-be7c-4511-9251-9b085207c15c",
                "@type": "geo:Feature",
                "geo:hasGeometry": {
                    "geo:asWKT": "\"LINESTRING (6502691.542169236 972304.9284766684,6502691.540990914 972304.9204293368)\"^^geo:wktLiteral ;",
                    "geo:asGeoJSON": "\"{\"type\": \"LineString\", \"coordinates\": [[[8.7007293714585,58.41467201058505], [8.7007293,58.414672]]]}\"^^geo:geoJSONLiteral ;"
                }
            }
        }
    ]
}

Variant4

{
    "@context": {
        "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
        "cim": "https://cim.ucaiug.io/ns#",
        "eu": "http://iec.ch/TC57/CIM100-European#",
        "dcterms": "http://purl.org/dc/terms/",
        "dcat": "http://www.w3.org/ns/dcat#",
        "prov": "http://www.w3.org/ns/prov#",
        "xsd": "http://www.w3.org/2001/XMLSchema#",
        "nc-no": "https://cim4.eu/ns/nc-no#",
        "geo": "http://www.opengis.net/ont/geosparql#"
    },
    "@graph": [
        {
            "@id": "urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed",
            "@type": "nc-no:ACLineSegmentSpan",
            "cim:IdentifiedObject.mRID": "802a2294-3cc5-4d3a-b2ba-57706abfe8ed",
            "cim:IdentifiedObject.description": "ACLineSegmentSpan 2",
            "cim:IdentifiedObject.name": "ACLSS_2",
            "nc-no:PowerSystemResource.locationMethod": {
                "@id": "nc-no:locationMethodKind.measured"
            },
            "nc-no:ACLineSegmentSpan.aviationObstacleMarkingKind": {
                "@id": "nc-no:LineMarkingKind.colourMarking"
            },
            "nc-no:ACLineSegmentSpan.aviationObstacleLightingKind": {
                "@id": "nc-no:LineLightingKind.lit"
            },
            "nc-no:ACLineSegmentSpan.maxWidth": 3.97,
            "nc-no:ACLineSegmentSpan.maxHeight": 153.23,
            "nc-no:ACLineSegmentSpan.spanWireLength": 63.01,
            "nc-no:ACLineSegmentSpan.ACLineSegment": [
                {
                    "@id": "urn:uuid:f8b08d2f-9110-4016-8aca-bdcec9fa02be"
                }
            ],
            "nc-no:PowerSystemResource.SpatialObject": {
                "geo:asWKT": "\"LINESTRING (6502691.542169236 972304.9284766684,6502691.540990914 972304.9204293368)\"^^geo:wktLiteral ;",
                "geo:asGeoJSON": "\"{\"type\": \"LineString\", \"coordinates\": [[[8.7007293714585,58.41467201058505], [8.7007293,58.414672]]]}\"^^geo:geoJSONLiteral ;"
            }
        }
    ]
}

After som more research by @EmilieSkog we now see that jsonld supports @type as a list. So we believe variant1 is probably the best solution we have so far.

  • yes, Variant1 "@type": "nc-no:ACLineSegmentSpan, geo:Feature" is invalid because that's a comma-separated string instead of 2 separate values.
  • Variants3, 4 are not good because you should use geo:hasGeometry
  • So I took your Variant2 but modified it:
    • to represent the GeoSPARQL literals with @value, @type (as dictated by JSONLD) rather than using ^^ (which is a Turtle notation)
    • Added type geo:Geometry
    • Added @id: urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed_geometry. This is optional, but it's a good practice not to use blank nodes. Formally speaking, the last part _geometry makes this not conform to the urn:uuid scheme, but who cares

Variant5:

{
  "@context": {
    "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
    "cim": "https://cim.ucaiug.io/ns#",
    "eu": "http://iec.ch/TC57/CIM100-European#",
    "dcterms": "http://purl.org/dc/terms/",
    "dcat": "http://www.w3.org/ns/dcat#",
    "prov": "http://www.w3.org/ns/prov#",
    "xsd": "http://www.w3.org/2001/XMLSchema#",
    "nc-no": "https://cim4.eu/ns/nc-no#",
    "geo": "http://www.opengis.net/ont/geosparql#"
  },
  "@graph": [
    {
      "@id": "urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed",
      "@type": ["nc-no:ACLineSegmentSpan", "geo:Feature"],
      "cim:IdentifiedObject.mRID": "802a2294-3cc5-4d3a-b2ba-57706abfe8ed",
      "cim:IdentifiedObject.description": "ACLineSegmentSpan 2",
      "cim:IdentifiedObject.name": "ACLSS_2",
      "nc-no:PowerSystemResource.locationMethod": {
        "@id": "nc-no:locationMethodKind.measured"
      },
      "nc-no:ACLineSegmentSpan.aviationObstacleMarkingKind": {
        "@id": "nc-no:LineMarkingKind.colourMarking"
      },
      "nc-no:ACLineSegmentSpan.aviationObstacleLightingKind": {
        "@id": "nc-no:LineLightingKind.lit"
      },
      "nc-no:ACLineSegmentSpan.maxWidth": 3.97,
      "nc-no:ACLineSegmentSpan.maxHeight": 153.23,
      "nc-no:ACLineSegmentSpan.spanWireLength": 63.01,
      "nc-no:ACLineSegmentSpan.ACLineSegment": [
        {
          "@id": "urn:uuid:f8b08d2f-9110-4016-8aca-bdcec9fa02be"
        }
      ],
      "geo:hasGeometry": {
        "@id": "urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed_geometry",
        "@type": "geo:Geometry",
        "geo:asWKT": {"@value":"LINESTRING (6502691.542169236 972304.9284766684,6502691.540990914 972304.9204293368)", "@type":"geo:wktLiteral"},
        "geo:asGeoJSON": {"@value":"{\"type\": \"LineString\", \"coordinates\": [[[8.7007293714585,58.41467201058505], [8.7007293,58.414672]]]}", "@type":"geo:geoJSONLiteral"}
      }
    }
  ]
}

You should always check by converting your JSONLD to another representation (Turtle) to ensure you got what you expected. In this case you can do it with Jena RIOT:

# riot -out ttl variant5.jsonld
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix cim: <https://cim.ucaiug.io/ns#> .
@prefix eu: <http://iec.ch/TC57/CIM100-European#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix dcat: <http://www.w3.org/ns/dcat#> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix nc-no: <https://cim4.eu/ns/nc-no#> .
@prefix geo: <http://www.opengis.net/ont/geosparql#> .

<urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed>
        rdf:type                   nc-no:ACLineSegmentSpan ;
        rdf:type                   geo:Feature ;
        geo:hasGeometry            <urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed_geometry> ;
        cim:IdentifiedObject.description  "ACLineSegmentSpan 2" ;
        cim:IdentifiedObject.mRID  "802a2294-3cc5-4d3a-b2ba-57706abfe8ed" ;
        cim:IdentifiedObject.name  "ACLSS_2" ;
        nc-no:ACLineSegmentSpan.ACLineSegment  <urn:uuid:f8b08d2f-9110-4016-8aca-bdcec9fa02be> ;
        nc-no:ACLineSegmentSpan.aviationObstacleLightingKind  nc-no:LineLightingKind.lit ;
        nc-no:ACLineSegmentSpan.aviationObstacleMarkingKind  nc-no:LineMarkingKind.colourMarking ;
        nc-no:ACLineSegmentSpan.maxHeight  1.5323E2 ;
        nc-no:ACLineSegmentSpan.maxWidth  3.97E0 ;
        nc-no:ACLineSegmentSpan.spanWireLength  6.301E1 ;
        nc-no:PowerSystemResource.locationMethod  nc-no:locationMethodKind.measured .

<urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed_geometry>
        rdf:type       geo:Geometry ;
        geo:asGeoJSON  "{\"type\": \"LineString\", \"coordinates\": [[[8.7007293714585,58.41467201058505], [8.7007293,58.414672]]]}"^^geo:geoJSONLiteral ;
        geo:asWKT      "LINESTRING (6502691.542169236 972304.9284766684,6502691.540990914 972304.9204293368)"^^geo:wktLiteral .

IMPORTANT:

  • Your geojson uses WGS84 (I think that's the only CRS it supports)
  • But the WKT seems to use some northing/easting CRS. You must specify its OGC URL as the first thing in the value.
    You can see an example here https://docs.ogc.org/is/22-047r1/22-047r1.html#C.1.1.3.2
  • Numbers are not well defined in JSON. As you can see from the Turtle, they are interpreted as xsd:float and converted to scientific notation, which may be confusing to clients. It's best to use xsd:decimal that has infinite precision and won't change the representation, i.e.
"nc-no:ACLineSegmentSpan.maxWidth": {"@type": "xsd:decimal", "@value": "3.97"},

@ThomasRanvikEriksen can you point me to some NC resources/specs?

Weekly call with @Sveino

  • Indeed, GeoJSON supports only WGS84, and altitude is meters above the Geoid: see https://datatracker.ietf.org/doc/html/rfc7946#section-4
  • Svein disliked the URI urn:uuid:802a2294-3cc5-4d3a-b2ba-57706abfe8ed_geometry because it tramples upon the UUID standard (which the beginning urn:uuid suggests).
    • On the other hand, it's convenient to be able to make URIs for sub-objects: generating a brand new UUID just for the Geometry will complicate things slightly.