@prefix geom-op:  <https://comp-rob2b.github.io/metamodels/geometry/spatial-operators#> .
@prefix geom-op-ext: <https://secorolab.github.io/metamodels/geometry/spatial-operators#> .
@prefix geom-coord: <https://comp-rob2b.github.io/metamodels/geometry/coordinates#> .
@prefix geom-path: <https://secorolab.github.io/metamodels/geometry/path#> .
@prefix qkind:    <http://qudt.org/vocab/quantitykind/> .
@prefix qudt:     <http://qudt.org/schema/qudt/> .
@prefix rdfs:     <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh:       <http://www.w3.org/ns/shacl#> .

geom-op:InvertPose
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op:InvertPose ;
    sh:message "A pose inversion needs one pose-coordinate input and one pose-coordinate output." ;
    sh:property [
        sh:path geom-op:pose ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op:out ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] .

geom-op-ext:PoseDiffEvaluator
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:PoseDiffEvaluator ;
    sh:message "A pose-difference evaluator needs two pose-coordinate inputs and one pose-difference-coordinate output." ;
    sh:property [
        sh:path geom-op:in1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op:in2 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op:out ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseDifferenceCoordinate
    ] .

geom-op-ext:PoseToAngularDistance
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:PoseToAngularDistance ;
    sh:message "A pose-to-angular-distance operator needs one pose-coordinate input and one angular-distance-coordinate output." ;
    sh:property [
        sh:path geom-op:pose ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:angular-distance ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:AngularDistanceCoordinate
    ] .

geom-op-ext:RotationVectorFromDirections
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:RotationVectorFromDirections ;
    sh:message "A rotation-vector-from-directions op needs two direction-coordinate inputs and one vector output." ;
    sh:property [
        sh:path geom-op:in1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:in2 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:out ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:VectorXYZ
    ] .

# The direction along which the angle between two directions grows: normalize(in2 x in1).
# Driving a controller along it moves in1 away from in2, so a controller reading the angle as
# its error drives the angle the way it authored it.
geom-op-ext:AngleGradientFromDirections
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:AngleGradientFromDirections ;
    sh:message "An angle-gradient-from-directions op needs two direction-coordinate inputs and one direction-coordinate gradient." ;
    sh:property [
        sh:path geom-op:in1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:in2 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:gradient ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] .

geom-op-ext:DirectionPlaneToAngularDistance
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:DirectionPlaneToAngularDistance ;
    sh:message "An incident-angle operator needs a versor direction and a plane-normal direction, and produces their signed angle and its gradient." ;
    sh:property [
        sh:path geom-op:in1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:in2 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:angle ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:class qudt:Quantity ;
        sh:node [ sh:property [ sh:path qudt:hasQuantityKind ; sh:hasValue qkind:PlaneAngle ] ]
    ] ;
    sh:property [
        sh:path geom-op-ext:gradient ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] .

geom-op-ext:ComposeOrientation
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:ComposeOrientation ;
    sh:message "An orientation composition needs one base and one rotation, in multiplication order, and one orientation-coordinate composite." ;
    sh:property [
        sh:path geom-op:in1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI
    ] ;
    sh:property [
        sh:path geom-op:in2 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI
    ] ;
    sh:property [
        sh:path geom-op:composite ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:OrientationCoordinate
    ] ;
    sh:xone (
        [
            sh:property [ sh:path geom-op:in1 ; sh:node geom-op-ext:OrientationBaseShape ] ;
            sh:property [ sh:path geom-op:in2 ; sh:node geom-op-ext:RotationShape ]
        ]
        [
            sh:property [ sh:path geom-op:in1 ; sh:node geom-op-ext:RotationShape ] ;
            sh:property [ sh:path geom-op:in2 ; sh:node geom-op-ext:OrientationBaseShape ]
        ]
    ) .

geom-op-ext:OrientationBaseShape
    a sh:NodeShape ;
    sh:message "The base of an orientation composition must be a pose or an orientation coordinate." ;
    sh:or (
        [ sh:class geom-coord:PoseCoordinate ]
        [ sh:class geom-coord:OrientationCoordinate ]
    ) .

geom-op-ext:RotationShape
    a sh:NodeShape ;
    sh:message "A rotation must be parameterised as Euler angles, a quaternion, or direction cosines." ;
    sh:or (
        [ sh:class geom-coord:EulerAngles ]
        [ sh:class geom-coord:Quaternion ]
        [ sh:class geom-coord:DirectionCosineXYZ ]
    ) .

# Where a pose sits on a path. The parameter is measured -- it is read off the robot's own
# state, never commanded -- so it is this operator's output, not its input.
geom-op-ext:PathProjection
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:PathProjection ;
    sh:message "A path projection needs one path typed geom-path:Path and the pose it projects, and produces that pose's path parameter." ;
    sh:property [
        sh:path geom-op-ext:path ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-path:Path
    ] ;
    sh:property [
        sh:path geom-op:pose ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:path-parameter ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:node geom-op-ext:PathParameterShape
    ] .

# The pose a path carries at a parameter. Paths are pure geometry, so this samples the curve
# and nothing else; timing comes from whatever drives the parameter.
geom-op-ext:PathEvaluator
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:PathEvaluator ;
    sh:message "A path evaluator needs one path typed geom-path:Path and one path parameter, and produces one pose coordinate on that path." ;
    sh:property [
        sh:path geom-op-ext:path ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-path:Path
    ] ;
    sh:property [
        sh:path geom-op-ext:path-parameter ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:node geom-op-ext:PathParameterShape
    ] ;
    sh:property [
        sh:path geom-op:out ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] .

# Separating travel along a path from leaving it needs a local frame: one axis along the path
# and two across it. Only the tangent is fixed by the geometry -- any two unit directions
# spanning its orthogonal complement will do -- so the normals are named a and b rather than
# claimed as the Frenet normal and binormal, which curvature would fix and flip at inflections.
geom-op-ext:PathTangentFrame
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:PathTangentFrame ;
    sh:message "A path tangent frame needs one path typed geom-path:Path and one path parameter, and produces the tangent of that path there with two unit normals across it." ;
    sh:property [
        sh:path geom-op-ext:path ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-path:Path
    ] ;
    sh:property [
        sh:path geom-op-ext:path-parameter ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:node geom-op-ext:PathParameterShape
    ] ;
    sh:property [
        sh:path geom-op-ext:tangent ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:normal-a ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:normal-b ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] .

# Speed along a direction, measured at the point the twist describes. A map view cannot say
# this: map:axis is closed to the canonical x, y and z, and this direction is arbitrary.
geom-op-ext:TwistToLinearVelocityAlong
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:TwistToLinearVelocityAlong ;
    sh:message "A twist-to-linear-velocity-along operator needs one velocity twist and one direction, and produces the linear speed of that twist along that direction." ;
    sh:property [
        sh:path geom-op:in ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:VelocityTwistCoordinate
    ] ;
    sh:property [
        sh:path geom-op:direction ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:along-speed ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:class qudt:Quantity ;
        sh:node [ sh:property [ sh:path qudt:hasQuantityKind ; sh:hasValue qkind:LinearVelocity ] ]
    ] .

# Table IIa ops 1-3: in1/in2 are pose coordinates (point, primitive origin), direction is the
# primitive's normal/direction. Ops 4-5: in1/in2 are direction coordinates (line A, line B), and
# pose is the PoseDiffEvaluator output between the two origins -- same role PoseToLinearDistance
# uses for "the pose whose translation is the vector of interest". Deliberate asymmetry, not a bug.
geom-op-ext:PointPlaneToLinearDistance
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:PointPlaneToLinearDistance ;
    sh:message "A point-plane distance needs two pose-coordinate inputs, a direction, and produces a signed distance and its gradient." ;
    sh:property [
        sh:path geom-op:in1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op:in2 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op:direction ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:distance ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:LinearDistanceCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:gradient ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] .

geom-op-ext:PointLineToLinearDistance
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:PointLineToLinearDistance ;
    sh:message "A point-line distance needs two pose-coordinate inputs, a direction, and produces an unsigned distance and its gradient." ;
    sh:property [
        sh:path geom-op:in1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op:in2 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op:direction ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:distance ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:LinearDistanceCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:gradient ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] .

geom-op-ext:PointOnLineProjection
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:PointOnLineProjection ;
    sh:message "A point-on-line projection needs two pose-coordinate inputs, a direction, and produces a signed distance and its gradient." ;
    sh:property [
        sh:path geom-op:in1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op:in2 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseCoordinate
    ] ;
    sh:property [
        sh:path geom-op:direction ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:distance ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:LinearDistanceCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:gradient ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] .

geom-op-ext:LineLineToLinearDistance
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:LineLineToLinearDistance ;
    sh:message "A line-line distance needs two direction-coordinate inputs (line A, line B), the pose-difference between their origins, and produces a signed distance and its gradient." ;
    sh:property [
        sh:path geom-op:in1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:in2 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:pose ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseDifferenceCoordinate
    ] ;
    sh:property [
        sh:path geom-op:distance ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:LinearDistanceCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:gradient ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] .

geom-op-ext:LineOnLineProjection
    a rdfs:Class, sh:NodeShape ;
    sh:targetClass geom-op-ext:LineOnLineProjection ;
    sh:message "A line-line projection needs two direction-coordinate inputs (line A, line B), the pose-difference between their origins, and produces a signed distance and its gradient." ;
    sh:property [
        sh:path geom-op:in1 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:in2 ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] ;
    sh:property [
        sh:path geom-op:pose ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:PoseDifferenceCoordinate
    ] ;
    sh:property [
        sh:path geom-op:distance ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:LinearDistanceCoordinate
    ] ;
    sh:property [
        sh:path geom-op-ext:gradient ;
        sh:minCount 1 ;
        sh:maxCount 1 ;
        sh:nodeKind sh:BlankNodeOrIRI ;
        sh:class geom-coord:DirectionCoordinate
    ] .

geom-op-ext:PathParameterShape
    a sh:NodeShape ;
    sh:message "A path parameter must be a dimensionless quantity whose value, when it has one, lies in the interval [0, 1]." ;
    sh:class qudt:Quantity ;
    sh:property [ sh:path qudt:hasQuantityKind ; sh:hasValue qkind:Dimensionless ] ;
    sh:property [ sh:path qudt:value ; sh:maxCount 1 ; sh:minInclusive 0 ; sh:maxInclusive 1 ] .
