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 skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX vann: <http://purl.org/vocab/vann/>

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

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

:LinearFeatureShape
    a sh:NodeShape ;
    sh:targetClass :LinearFeature ;
    sh:and ( :FeatureShape ) .

:LinearGeometryShape
    a sh:NodeShape ;
    sh:targetClass :LinearGeometry ;
    sh:and ( :GeometryShape ) .

:LinearByCodeShape
    a sh:NodeShape ;
    sh:targetClass :LinearByCode ;
    sh:and ( :LinearGeometryShape :CodedGeometryShape ) .

:LinearByPointFeaturesShape
    a sh:NodeShape ;
    sh:targetClass :LinearByPointFeatures ;
    sh:and ( :LinearGeometryShape ) ;
    sh:property [
        sh:path :hasPointList ;
        sh:node its-core:ExactlyOneShape ;
        sh:class :PointFeatureList ;
        sh:message "LinearByPointFeatures must have a single PointFeatureList." ;
    ] .

:LinearByPointGeometriesShape
    a sh:NodeShape ;
    sh:targetClass :LinearByPointGeometries ;
    sh:and ( :LinearGeometryShape ) ;
    sh:property [
        sh:path :hasPointGeometryList ;
        sh:node its-core:ExactlyOneShape ;
        sh:class :PointGeometryList ;
        sh:message "LinearByPointGeometries must have a single PointGeometryList." ;
    ] .

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

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

:OffsetDistanceShape
    a sh:NodeShape ;
    sh:targetClass :OffsetDistance ;
    sh:or (
        [
            sh:property [
                sh:path :offsetLength ;
                sh:node its-core:ExactlyOneShape ;
                sh:class cdm1:Length ;
            ]
        ]
        [
            sh:property [
                sh:path :offsetPercentage ;
                sh:node its-core: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 ( :PointGeometryShape ) ;
    sh:property [
        sh:path :alongLinearGeometry ;
        sh:node its-core:ExactlyOneShape ;
        sh:class :LinearGeometry ;
    ] ;
    sh:property [
        sh:path :hasOffsetDistance ;
        sh:node its-core:ExactlyOneShape ;
        sh:class :OffsetDistance ;
    ] ;
    sh:property [
        sh:path :basePoint ;
        sh:node its-core:ExactlyOneShape ;
        sh:class :PointFeature ;
    ] ;
    sh:property [
        sh:path :directionPoint ;
        sh:node its-core:ExactlyOneShape ;
        sh:class :PointFeature ;
    ] ;
    sh:sparql [
        a sh:SPARQLConstraint ;
        sh:message "basePoint and directionPoint must be different point features." ;
        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 PointFeatureList that defines the referenced LinearByPointFeatures geometry." ;
        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 :alongLinearGeometry ?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 }
                )
            }
        """ ;
    ] .
