BASE <https://w3id.org/itsdata/location/v1/>
PREFIX : <https://w3id.org/itsdata/location/v1/>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX cdm1: <https://w3id.org/citydata/part1/v1/>
PREFIX its-core: <https://w3id.org/itsdata/core/v1/>
PREFIX its-sh: <https://w3id.org/itsdata/shacl/v1/>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

:LinearSHACL
    a owl:Ontology ;
    dcterms:title "SHACL rules for ITS Linear Referencing"@en ;
    skos:definition "SHACL shapes for linear elements, linear locations, and linear representations."@en ;
    its-core:draft "true" ;
    dcterms:created "Draft" ;
    dcterms:modified "2026-04-30"^^xsd:date ;
    owl:versionInfo "0.0.1" ;
    owl:versionIRI <LinearSHACL/r0.1> ;
    owl:imports :LocationCoreSHACL ;
    owl:imports :PointSHACL .

#################################################################
# Node shapes
#################################################################

:LinearLocationShape
    a sh:NodeShape ;
    sh:targetClass :LinearLocation ;
    sh:and ( :LocationShape ) ;
    sh:property [
        sh:path :hasPrimaryRepresentation ;
        sh:node its-sh:ExactlyOneShape ;
        sh:class :LinearRepresentation ;
    ] ;
    sh:property [
        sh:path :hasAlternativeRepresentation ;
        sh:node its-sh:UnlimitedShape ;
        sh:class :LinearRepresentation ;
    ] .

:LinearRepresentationShape
    a sh:NodeShape ;
    sh:targetClass :LinearRepresentation ;
    sh:and ( :GeometryShape ) .

:LinearByPointsShape
    a sh:NodeShape ;
    sh:targetClass :LinearByPoints ;
    sh:and ( :LinearRepresentationShape ) ;
    sh:property [
        sh:path :hasPointList ;
        sh:node its-sh:ExactlyOneShape ;
        sh:class :PointLocationList ;
        sh:message "LinearByPoints must have a single PointList." ;
    ] .

:LinearByPointRepresentationsShape
    a sh:NodeShape ;
    sh:targetClass :LinearByPointRepresentations ;
    sh:and ( :LinearRepresentationShape ) ;
    sh:property [
        sh:path :hasPointRepresentationList ;
        sh:node its-sh:ExactlyOneShape ;
        sh:class :PointRepresentationList ;
        sh:message "LinearByPointRepresentations must have a single PointRepresentationList." ;
    ] .

:PointLocationListShape a sh:NodeShape ;
    sh:targetClass :PointLocationList ;
    sh:property [
        sh:path rdf:first ;
        sh:class :PointLocation ;
        sh:node its-sh:ExactlyOneShape ;
    ] ;
    sh:property [
        sh:path rdf:rest ;
        sh:or ( [ sh:hasValue rdf:nil ] [ sh:class :PointLocationList ] ) ;
        sh:node its-sh:ExactlyOneShape ;
    ] ;
    sh:closed true ;
    sh:ignoredProperties ( rdf:type ) ;
    sh:message "A PointList must be a well-formed rdf:List of Points." .

:PointRepresentationListShape a sh:NodeShape ;
    sh:targetClass :PointRepresentationList ;
    sh:property [
        sh:path rdf:first ;
        sh:class :PointRepresentation ;
        sh:node its-sh:ExactlyOneShape ;
    ] ;
    sh:property [
        sh:path rdf:rest ;
        sh:or ( [ sh:hasValue rdf:nil ] [ sh:class :PointRepresentationList ] ) ;
        sh:node its-sh:ExactlyOneShape ;
    ] ;
    sh:closed true ;
    sh:ignoredProperties ( rdf:type ) ;
    sh:message "A PointRepresentationList must be a well-formed rdf:List of PointRepresentations." .

:OffsetDistanceShape
    a sh:NodeShape ;
    sh:targetClass :OffsetDistance ;
    sh:or (
        [
            sh:property [
                sh:path :offsetLength ;
                sh:node its-sh:ExactlyOneShape ;
                sh:class cdm1:Length ;
            ]
        ]
        [
            sh:property [
                sh:path :offsetPercentage ;
                sh:node its-sh:ExactlyOneShape ;
                sh:class cdm1:Ratio ;
            ]
        ]
    ) ;
    sh:message "OffsetDistance must have exactly one offsetLength or exactly one offsetPercentage." .

:PointByLinearPositionShape
    a sh:NodeShape ;
    sh:targetClass :PointByLinearPosition ;
    sh:and ( :PointRepresentationShape ) ;
    sh:property [
        sh:path :alongLinearRepresentation ;
        sh:node its-sh:ExactlyOneShape ;
        sh:class :LinearByPoints ;
    ] ;
    sh:property [
        sh:path :hasOffsetDistance ;
        sh:node its-sh:ExactlyOneShape ;
        sh:class :OffsetDistance ;
    ] ;
    sh:property [
        sh:path :basePoint ;
        sh:node its-sh:ExactlyOneShape ;
        sh:class :PointLocation ;
    ] ;
    sh:property [
        sh:path :directionPoint ;
        sh:node its-sh:ExactlyOneShape ;
        sh:class :PointLocation ;
    ] ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "basePoint and directionPoint must be different Points." ;
        sh:select """
            PREFIX : <https://w3id.org/itsdata/location/v1/>
            SELECT $this ?base ?dir
            WHERE {
                $this :basePoint ?base ;
                      :directionPoint ?dir .
                FILTER (?base = ?dir)
            }
        """ ;
    ] ;

    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "basePoint and directionPoint must be members of the PointList that defines the referenced LinearByPoints representation." ;
        sh:select """
            PREFIX : <https://w3id.org/itsdata/location/v1/>
            PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
            SELECT $this ?line ?list ?base ?dir
            WHERE {
                $this :alongLinearRepresentation ?line ;
                      :basePoint ?base ;
                      :directionPoint ?dir .

                ?line :hasPointList ?list .

                FILTER (
                    NOT EXISTS { ?list rdf:rest*/rdf:first ?base } ||
                    NOT EXISTS { ?list rdf:rest*/rdf:first ?dir }
                )
            }
        """ ;
    ] .
