This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 9680bcdb1aff63af9e26613db2006eaff5efae59
Author: jsorel <[email protected]>
AuthorDate: Tue Oct 13 11:57:48 2026 +0200

    feat(Geometry): add Curve.subCurve(dist), start implementing 
getRepresentativePoint
---
 .../main/org/apache/sis/geometries/Curve.java      |  37 +++++
 .../apache/sis/geometries/curve/CompoundCurve.java |  33 +++++
 .../main/org/apache/sis/geometries/curve/Line.java |  14 ++
 .../apache/sis/geometries/curve/LineString.java    |  14 ++
 .../sis/geometries/internal/shared/Curves.java     | 160 +++++++++++++++++++++
 .../geometries/internal/shared/DefaultLine.java    |  10 --
 .../internal/shared/DefaultLineString.java         |  10 --
 .../internal/shared/DefaultReversedCurve.java      |  16 +++
 .../sis/geometries/curve/CompoundCurveTest.java    |  26 +++-
 .../sis/geometries/curve/LineStringTest.java       | 108 +++++++++++++-
 .../internal/shared/DefaultReversedCurveTest.java  |  22 ++-
 11 files changed, 421 insertions(+), 29 deletions(-)

diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Curve.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Curve.java
index 9d731eb4c8..fc97b02f23 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Curve.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Curve.java
@@ -159,6 +159,22 @@ public sealed interface Curve extends Orientable
         return points.getPoint(points.size()-1);
     }
 
+    /**
+     * Returns the point at the middle of this curve, i.e. at 50% of its 
{@linkplain #getLength() length}.
+     *
+     * @return the point at middle distance along this curve, or {@code null} 
if this curve is empty.
+     *
+     * @see #subCurve(Quantity)
+     * @see ISO 19107:2019 - 6.4.4.19
+     */
+    @Override
+    default Point getRepresentativePoint() {
+        if (isEmpty()) {
+            return null;
+        }
+        return subCurve(Quantities.create(50, Units.PERCENT));
+    }
+
     /**
      * Returns whether this curve is closed, i.e. whether its start point 
equals its end point.
      * For a simple curve, this is equivalent to {@link Geometry#isCycle()}.
@@ -667,6 +683,27 @@ public sealed interface Curve extends Orientable
         throw new UnsupportedOperationException();
     }
 
+    /**
+     * Extracts the portion of this curve at given length.
+     *
+     * <p>Difference with ISO 19107: declared as a copy constructor in the 
standard,
+     * with a start and end parameter, this version gets only one point.</p>
+     *
+     * <p>Difference with ISO-19107, the Length type has been changed to 
Quantity to
+     * handle temporal geometries and crs-less geometries.</p>
+     *
+     * <p>To get the start or end point use the Percent unit, 0 or 100</p>
+     *
+     * @param  dist length parameter where is the returned point.
+     * @return the ppoint at given length.
+     *
+     * @see derived from ISO 19107:2019 - 6.4.18.22
+     */
+    default Point subCurve(Quantity<?> dist) {
+        //TODO
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * Extracts the portion of this curve between two arc length parameters.
      *
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CompoundCurve.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CompoundCurve.java
index 1a0ec466fa..ade5c4dad8 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CompoundCurve.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/CompoundCurve.java
@@ -19,6 +19,7 @@ package org.apache.sis.geometries.curve;
 import java.util.ArrayList;
 import java.util.List;
 import javax.measure.Quantity;
+import javax.measure.Unit;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.CurveInterpolation;
 import org.apache.sis.geometries.DataPoints;
@@ -27,6 +28,7 @@ import org.apache.sis.geometries.Geometry;
 import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.internal.shared.ConcatenatedDataPoints;
+import org.apache.sis.geometries.internal.shared.Curves;
 import org.apache.sis.geometries.internal.shared.DefaultCompoundCurve;
 import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
@@ -124,6 +126,37 @@ public sealed interface CompoundCurve extends Curve
         return length;
     }
 
+    /**
+     * Returns the point at the given distance along this compound curve, 
found on the component
+     * which covers that distance. The distance is in the unit of the 
{@linkplain #getLength() length},
+     * or in percent of that length.
+     *
+     * @return the point at the given distance, or {@code null} if this 
compound curve has no component.
+     * @throws IllegalArgumentException if the distance is outside this 
compound curve.
+     */
+    @Override
+    default Point subCurve(Quantity<?> dist) {
+        final int n = getNumCurves();
+        if (n == 0) {
+            return null;
+        }
+        final Unit<?> unit = getCurveN(0).getLength().getUnit();
+        final double[] lengths = new double[n];
+        double total = 0;
+        for (int i = 0; i < n; i++) {
+            lengths[i] = Curves.convert(getCurveN(i).getLength(), unit);
+            total += lengths[i];
+        }
+        double s = Curves.toArcLength(dist, total, unit);
+        for (int i = 0; i < n; i++) {
+            if (s <= lengths[i] || i == n - 1) {
+                return getCurveN(i).subCurve(Quantities.create(Math.min(s, 
lengths[i]), unit));
+            }
+            s -= lengths[i];
+        }
+        throw new AssertionError();         // Unreachable: the last component 
always returns.
+    }
+
     /**
      * Returns whether the last component ends where the first one starts.
      * An empty compound curve is not closed.
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Line.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Line.java
index 7694226626..1298ae7230 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Line.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Line.java
@@ -28,6 +28,8 @@ import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.DataPointsType;
+import org.apache.sis.geometries.Geometries;
+import org.apache.sis.geometries.internal.shared.Curves;
 import org.apache.sis.geometries.internal.shared.DefaultLine;
 import org.apache.sis.geometries.internal.shared.DefaultLineString;
 import org.apache.sis.maths.Array;
@@ -95,6 +97,18 @@ public sealed interface Line extends Curve
         return new DefaultLineString(getDataPoints());
     }
 
+    /**
+     * Returns the point at the given distance along the segments of this line.
+     * The distance is in the unit of the coordinate system axes, or in 
percent of the length.
+     *
+     * @return the point at the given distance, or {@code null} if this line 
is empty.
+     * @throws IllegalArgumentException if the distance is outside this line.
+     */
+    @Override
+    default Point subCurve(Quantity<?> dist) {
+        return Curves.alongPolyline(getDataPoints(), dist, 
Geometries.getLinearUnit(this));
+    }
+
     /**
      * The number of Points in this LineString.
      *
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java
index 3c0cf0400e..cffc5b9109 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/LineString.java
@@ -27,6 +27,8 @@ import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.DataPointsType;
+import org.apache.sis.geometries.Geometries;
+import org.apache.sis.geometries.internal.shared.Curves;
 import org.apache.sis.geometries.internal.shared.DefaultLineString;
 import org.apache.sis.geometries.mesh.MeshPrimitive;
 import org.apache.sis.geometries.operation.triangulate.delaunay.OrientedEdge;
@@ -99,6 +101,18 @@ public sealed interface LineString extends Curve
         return this;
     }
 
+    /**
+     * Returns the point at the given distance along the segments of this line 
string.
+     * The distance is in the unit of the coordinate system axes, or in 
percent of the length.
+     *
+     * @return the point at the given distance, or {@code null} if this line 
string is empty.
+     * @throws IllegalArgumentException if the distance is outside this line 
string.
+     */
+    @Override
+    default Point subCurve(Quantity<?> dist) {
+        return Curves.alongPolyline(getDataPoints(), dist, 
Geometries.getLinearUnit(this));
+    }
+
     /**
      * The number of Points in this LineString.
      *
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Curves.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Curves.java
new file mode 100644
index 0000000000..45462b3f3e
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Curves.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.geometries.internal.shared;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.measure.IncommensurableException;
+import javax.measure.Quantity;
+import javax.measure.Unit;
+import org.apache.sis.geometries.AttributeInterpolation;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.DataPointsType;
+import org.apache.sis.geometries.GeometryFactory;
+import org.apache.sis.geometries.Point;
+import org.apache.sis.maths.Array;
+import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.Vector;
+import org.apache.sis.measure.Units;
+
+
+/**
+ * Helper methods for locating positions at a distance along a curve.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class Curves {
+
+    private Curves() {
+    }
+
+    /**
+     * Returns whether the given distance is expressed in percent of the curve 
length.
+     *
+     * @param  dist  the distance along a curve.
+     * @return whether the distance is a percentage.
+     */
+    public static boolean isPercent(final Quantity<?> dist) {
+        return Units.PERCENT.equals(dist.getUnit());
+    }
+
+    /**
+     * Converts the given quantity to the given unit.
+     *
+     * @param  q     the quantity to convert.
+     * @param  unit  the target unit.
+     * @return the value of the quantity in the target unit.
+     * @throws IllegalArgumentException if the quantity cannot be expressed in 
the target unit.
+     */
+    public static double convert(final Quantity<?> q, final Unit<?> unit) {
+        try {
+            return 
q.getUnit().getConverterToAny(unit).convert(q.getValue().doubleValue());
+        } catch (IncommensurableException e) {
+            throw new IllegalArgumentException("Cannot express " + q + " in " 
+ unit + '.', e);
+        }
+    }
+
+    /**
+     * Converts a distance along a curve to an arc length in the unit of the 
curve length.
+     * A distance in {@linkplain Units#PERCENT percent} is relative to the 
given length.
+     *
+     * @param  dist    the distance along the curve.
+     * @param  length  the length of the curve, in {@code unit}.
+     * @param  unit    the unit of the curve length.
+     * @return the arc length, between 0 and {@code length} inclusive.
+     * @throws IllegalArgumentException if the distance cannot be expressed in 
the length unit,
+     *         or is outside the curve.
+     */
+    public static double toArcLength(final Quantity<?> dist, final double 
length, final Unit<?> unit) {
+        double s = isPercent(dist) ? dist.getValue().doubleValue() / 100 * 
length : convert(dist, unit);
+        // Rounding errors of unit conversions and of length sums are not a 
distance outside the curve.
+        final double tolerance = length * 1E-12;
+        if (s < 0 && s >= -tolerance) {
+            s = 0;
+        } else if (s > length && s <= length + tolerance) {
+            s = length;
+        }
+        if (!(s >= 0 && s <= length)) {
+            throw new IllegalArgumentException("The distance " + dist + " is 
outside the curve, whose length is "
+                    + length + ' ' + unit + '.');
+        }
+        return s;
+    }
+
+    /**
+     * Returns the point at the given distance along the line segments joining 
consecutive points.
+     *
+     * @param  points  the points of the polyline.
+     * @param  dist    the distance along the polyline, in {@code unit} or in 
percent.
+     * @param  unit    the unit of the coordinates.
+     * @return the point at the given distance, or {@code null} if there are 
no points.
+     * @throws IllegalArgumentException if the distance is outside the 
polyline.
+     */
+    public static Point alongPolyline(final DataPoints points, final 
Quantity<?> dist, final Unit<?> unit) {
+        final int n = points.size();
+        if (n == 0) {
+            return null;
+        }
+        final double[] segments = new double[n - 1];
+        double total = 0;
+        for (int i=0; i<segments.length; i++) {
+            segments[i] = 
points.getPosition(i).subtract(points.getPosition(i+1)).length();
+            total += segments[i];
+        }
+        double s = toArcLength(dist, total, unit);
+        for (int i=0; i<segments.length; i++) {
+            final double length = segments[i];
+            if (s <= length || i == segments.length - 1) {
+                final double ratio = (length > 0) ? Math.min(s / length, 1) : 
0;
+                return interpolate(points, i, i+1, ratio);
+            }
+            s -= length;
+        }
+        return interpolate(points, 0, 0, 0);    // A single position, at 
distance zero.
+    }
+
+    /**
+     * Returns a new point on the segment joining two points of the given 
sequence.
+     * The position is linearly interpolated, and each other attribute is 
computed as declared by
+     * its {@linkplain DataPointsType#getAttributeInterpolation(String) 
attribute interpolation}.
+     *
+     * @param  points  the sequence holding the two points.
+     * @param  i0      index of the point at ratio 0, the start of the segment.
+     * @param  i1      index of the point at ratio 1, the end of the segment.
+     * @param  ratio   position between the two points, from 0 to 1.
+     * @return a new point holding the computed attributes, independent of the 
given sequence.
+     */
+    public static Point interpolate(final DataPoints points, final int i0, 
final int i1, final double ratio) {
+        final DataPointsType type = points.getType();
+        final Map<String,Array> attributes = new LinkedHashMap<>();
+        for (final String name : type.getAttributeNames()) {
+            final AttributeInterpolation method = 
DataPointsType.ATT_POSITION.equals(name)
+                    ? AttributeInterpolation.LINEAR : 
type.getAttributeInterpolation(name);
+            final Vector<?> value;
+            switch ((method != null) ? method : 
AttributeInterpolation.NEAREST) {
+                case LINEAR:
+                case TRANSLATE: value = points.getAttribute(i0, 
name).lerp(points.getAttribute(i1, name), ratio); break;
+                case LEADING:   value = points.getAttribute(ratio < 1 ? i0 : 
i1, name); break;
+                default:        value = points.getAttribute(ratio <= 0.5 ? i0 
: i1, name); break;
+            }
+            final Array array = NDArrays.of(type.getAttributeSystem(name), 
type.getAttributeType(name), 1);
+            array.set(0, value);
+            attributes.put(name, array);
+        }
+        return 
GeometryFactory.DEFAULT.createPoint(GeometryFactory.DEFAULT.createDataPoints(attributes));
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultLine.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultLine.java
index 61172ef87e..b2ec3d8312 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultLine.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultLine.java
@@ -62,16 +62,6 @@ public non-sealed class DefaultLine extends AbstractGeometry 
implements Line {
         return (Point) fromJTS(asJTS().getCentroid());
     }
 
-    @Override
-    public Point getRepresentativePoint() {
-        if (isEmpty()) {
-            // The empty set has no interior position.
-            return null;
-        }
-        //TODO : fallback on JTS until implemented
-        return (Point) fromJTS(asJTS().getInteriorPoint());
-    }
-
     @Override
     public boolean isSimple() {
         //TODO : fallback on JTS until implemented
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultLineString.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultLineString.java
index 762200d7e1..53a183b0d0 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultLineString.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultLineString.java
@@ -62,16 +62,6 @@ public non-sealed class DefaultLineString extends 
AbstractGeometry implements Li
         return (Point) fromJTS(asJTS().getCentroid());
     }
 
-    @Override
-    public Point getRepresentativePoint() {
-        if (isEmpty()) {
-            // The empty set has no interior position.
-            return null;
-        }
-        //TODO : fallback on JTS until implemented
-        return (Point) fromJTS(asJTS().getInteriorPoint());
-    }
-
     @Override
     public boolean isSimple() {
         //TODO : fallback on JTS until implemented
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedCurve.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedCurve.java
index 08760adab1..1dd945c9ce 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedCurve.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultReversedCurve.java
@@ -29,6 +29,8 @@ import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.Primitive;
 import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.maths.Array;
+import org.apache.sis.measure.Quantities;
+import org.apache.sis.measure.Units;
 
 
 /**
@@ -99,6 +101,20 @@ public non-sealed class DefaultReversedCurve extends 
AbstractGeometry implements
         return base.getLength();
     }
 
+    /**
+     * Returns the point of the base curve at the given distance from its end, 
which is where this
+     * curve starts.
+     */
+    @Override
+    public Point subCurve(Quantity<?> dist) {
+        if (Curves.isPercent(dist)) {
+            return base.subCurve(Quantities.create(100 - 
dist.getValue().doubleValue(), Units.PERCENT));
+        }
+        final Quantity<?> length = base.getLength();
+        final double s = Curves.toArcLength(dist, 
length.getValue().doubleValue(), length.getUnit());
+        return base.subCurve(Quantities.create(length.getValue().doubleValue() 
- s, length.getUnit()));
+    }
+
     @Override
     public boolean isClosed() {
         return base.isClosed();
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/curve/CompoundCurveTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/curve/CompoundCurveTest.java
index b3ee4f38bf..37344bdb84 100644
--- 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/curve/CompoundCurveTest.java
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/curve/CompoundCurveTest.java
@@ -17,9 +17,12 @@
 package org.apache.sis.geometries.curve;
 
 import org.apache.sis.geometries.GeometryFactory;
+import org.apache.sis.measure.Quantities;
+import org.apache.sis.measure.Units;
 
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.apache.sis.geometries.GeometryTest;
@@ -204,6 +207,24 @@ public class CompoundCurveTest extends GeometryTest {
     public void testSubCurve_Length_Length() {
     }
 
+    /**
+     * The point at a distance along two components of length 10 is found on 
the component
+     * which covers that distance.
+     */
+    @Test
+    public void testSubCurve_Length() {
+        final CompoundCurve curve = 
GeometryFactory.DEFAULT.createCompoundCurve(
+                
GeometryFactory.DEFAULT.createLineString(GeometryFactory.DEFAULT.createDataPoints(CRS_2D,
 0,0, 10,0)),
+                
GeometryFactory.DEFAULT.createLineString(GeometryFactory.DEFAULT.createDataPoints(CRS_2D,
 10,0, 10,10)));
+        assertPositionEquals( 5,  0, curve.subCurve(Quantities.create( 25, 
Units.PERCENT)));
+        assertPositionEquals(10,  0, curve.subCurve(Quantities.create( 50, 
Units.PERCENT)));
+        assertPositionEquals(10,  5, curve.subCurve(Quantities.create( 75, 
Units.PERCENT)));
+        assertPositionEquals(10, 10, curve.subCurve(Quantities.create(100, 
Units.PERCENT)));
+        assertPositionEquals(10,  8, curve.subCurve(Quantities.create( 18, 
Units.DEGREE)));
+        assertPositionEquals(10,  0, curve.getRepresentativePoint());
+        assertThrows(IllegalArgumentException.class, () -> 
curve.subCurve(Quantities.create(21, Units.DEGREE)));
+    }
+
     @Test
     @Disabled("Not implemented yet.")
     public void testGetOrientationSign() {
@@ -285,8 +306,11 @@ public class CompoundCurveTest extends GeometryTest {
     }
 
     @Test
-    @Disabled("Not implemented yet.")
     public void testGetRepresentativePoint() {
+        final LineString segment = GeometryFactory.DEFAULT.createLineString(
+                GeometryFactory.DEFAULT.createDataPoints(CRS_2D, 0,0, 10,0));
+        final CompoundCurve curve = 
GeometryFactory.DEFAULT.createCompoundCurve(segment);
+        assertPositionEquals(5, 0, curve.getRepresentativePoint());
     }
 
     @Test
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/curve/LineStringTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/curve/LineStringTest.java
index a5f127d55f..7146cab4d7 100644
--- 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/curve/LineStringTest.java
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/curve/LineStringTest.java
@@ -16,10 +16,26 @@
  */
 package org.apache.sis.geometries.curve;
 
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import javax.measure.Quantity;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.geometries.AttributeInterpolation;
+import org.apache.sis.geometries.DataPoints;
+import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.geometries.Geometry;
 import org.apache.sis.geometries.GeometryCollection;
+import org.apache.sis.geometries.GeometryFactory;
+import org.apache.sis.geometries.Point;
+import org.apache.sis.maths.Array;
+import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.ReadOnly;
+import org.apache.sis.maths.SampleSystem;
+import org.apache.sis.maths.Vector;
+import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
 
 // Test dependencies
@@ -267,6 +283,88 @@ public abstract class LineStringTest extends GeometryTest {
     public void testSubCurve_Length_Length() {
     }
 
+    /**
+     * The point at a distance along a curve of two segments of length 10, the 
distance being given
+     * either in the unit of the axes or in percent of the length.
+     */
+    @Test
+    public void testSubCurve_Length() {
+        final LineString curve = createLineString(CRS_2D, 0,0, 10,0, 10,10);
+        assertPositionEquals( 0,  0, curve.subCurve(Quantities.create(  0, 
Units.PERCENT)));
+        assertPositionEquals(10,  5, curve.subCurve(Quantities.create( 75, 
Units.PERCENT)));
+        assertPositionEquals(10, 10, curve.subCurve(Quantities.create(100, 
Units.PERCENT)));
+        assertPositionEquals( 5,  0, curve.subCurve(Quantities.create(  5, 
Units.DEGREE)));
+        assertPositionEquals(10,  0, curve.subCurve(Quantities.create( 10, 
Units.DEGREE)));
+        assertPositionEquals(10,  8, curve.subCurve(Quantities.create( 18, 
Units.DEGREE)));
+    }
+
+    /**
+     * The attributes other than the position are kept, each computed as its 
attribute interpolation
+     * declares. The point at 80% lies at 60% of the second segment, between 
the values 10 and 30.
+     */
+    @Test
+    public void testSubCurve_LengthWithAttributes() {
+        final Map<String,Array> attributes = new LinkedHashMap<>();
+        attributes.put(DataPointsType.ATT_POSITION, NDArrays.of(CRS_2D, new 
double[] {0,0, 10,0, 10,10}));
+        attributes.put("nearest", NDArrays.of(1, new double[] {0, 10, 30}));
+        attributes.put("linear",  NDArrays.of(1, new double[] {0, 10, 30}));
+        attributes.put("leading", NDArrays.of(1, new double[] {0, 10, 30}));
+        final LineString curve = 
GeometryFactory.DEFAULT.createLineString(withInterpolations(
+                GeometryFactory.DEFAULT.createDataPoints(attributes),
+                Map.of("linear",  AttributeInterpolation.LINEAR,
+                       "leading", AttributeInterpolation.LEADING)));
+
+        Point point = curve.subCurve(Quantities.create(80, Units.PERCENT));
+        assertPositionEquals(10, 6, point);
+        assertEquals(Set.of(DataPointsType.ATT_POSITION, "nearest", "linear", 
"leading"),
+                     
Set.copyOf(point.getDataPointsType().getAttributeNames()));
+        assertEquals(30, point.getAttribute("nearest").get(0), TOLERANCE, 
"Value of the nearest point.");
+        assertEquals(22, point.getAttribute("linear" ).get(0), TOLERANCE, 
"Interpolated along the segment.");
+        assertEquals(10, point.getAttribute("leading").get(0), TOLERANCE, 
"Value of the segment start.");
+
+        point = curve.subCurve(Quantities.create(100, Units.PERCENT));
+        assertEquals(30, point.getAttribute("leading").get(0), TOLERANCE, "The 
end point has its own value.");
+    }
+
+    /**
+     * Returns a view of the given sequence declaring the given attribute 
interpolations.
+     * Attributes absent from the map keep the interpolation declared by the 
sequence.
+     */
+    private static DataPoints withInterpolations(final DataPoints points, 
final Map<String,AttributeInterpolation> methods) {
+        final DataPointsType base = points.getType();
+        final DataPointsType type = new DataPointsType() {
+            @Override public List<String> getAttributeNames()            
{return base.getAttributeNames();}
+            @Override public SampleSystem getAttributeSystem(String name) 
{return base.getAttributeSystem(name);}
+            @Override public DataType     getAttributeType(String name)   
{return base.getAttributeType(name);}
+            @Override public AttributeInterpolation 
getAttributeInterpolation(String name) {
+                return methods.getOrDefault(name, 
base.getAttributeInterpolation(name));
+            }
+        };
+        return new DataPoints() {
+            @Override public CoordinateReferenceSystem 
getCoordinateReferenceSystem() {return points.getCoordinateReferenceSystem();}
+            @Override public void 
setCoordinateReferenceSystem(CoordinateReferenceSystem crs) 
{points.setCoordinateReferenceSystem(crs);}
+            @Override public DataPointsType getType() {return type;}
+            @Override public int size() {return points.size();}
+            @Override public Point getPoint(int index) {return 
points.getPoint(index);}
+            @Override public Vector<?> getPosition(int index) {return 
points.getPosition(index);}
+            @Override public void setPosition(int index, ReadOnly.Vector<?> 
value) {points.setPosition(index, value);}
+            @Override public Vector<?> getAttribute(int index, String name) 
{return points.getAttribute(index, name);}
+            @Override public void setAttribute(int index, String name, 
ReadOnly.Vector<?> value) {points.setAttribute(index, name, value);}
+        };
+    }
+
+    /**
+     * A distance outside the curve, or which cannot be expressed in the unit 
of the axes, is rejected.
+     */
+    @Test
+    public void testSubCurve_LengthOutside() {
+        final LineString curve = createLineString(CRS_2D, SEGMENT);
+        assertThrows(IllegalArgumentException.class, () -> 
curve.subCurve(Quantities.create(101, Units.PERCENT)));
+        assertThrows(IllegalArgumentException.class, () -> 
curve.subCurve(Quantities.create( -1, Units.DEGREE)));
+        assertThrows(IllegalArgumentException.class, () -> 
curve.subCurve(Quantities.create( 11, Units.DEGREE)));
+        assertThrows(IllegalArgumentException.class, () -> 
curve.subCurve(Quantities.create(  5, Units.METRE)));
+    }
+
     @Test
     @Disabled("Not implemented yet.")
     public void testGetOrientationSign() {
@@ -332,16 +430,12 @@ public abstract class LineStringTest extends GeometryTest 
{
     }
 
     /**
-     * Contrarily to the centroid, the returned position shall lie on the 
curve, but which position
-     * is returned is left to the implementation. The segment under test being 
horizontal, lying on
-     * it means having a null second ordinate and a first ordinate within the 
range of the segment.
+     * The point at middle distance along a curve made of a single segment is 
that segment's midpoint.
      */
     @Test
     public void testGetRepresentativePoint() {
-        final double[] position = createLineString(CRS_2D, 
SEGMENT).getRepresentativePoint()
-                                                                   
.getPosition().toArrayDouble();
-        assertEquals(0, position[1], TOLERANCE, "The position shall lie on the 
segment.");
-        assertTrue(position[0] >= 0 && position[0] <= 10, "The position shall 
lie on the segment.");
+        assertPositionEquals(5, 0, createLineString(CRS_2D, 
SEGMENT).getRepresentativePoint());
+        assertPositionEquals(5, 5, createLineString(CRS_2D, 
DIAGONAL).getRepresentativePoint());
     }
 
     @Test
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/DefaultReversedCurveTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/DefaultReversedCurveTest.java
index 3cda2af249..6260a35a83 100644
--- 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/DefaultReversedCurveTest.java
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/DefaultReversedCurveTest.java
@@ -19,6 +19,8 @@ package org.apache.sis.geometries.internal.shared;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.GeometryFactory;
 import org.apache.sis.geometries.curve.LineString;
+import org.apache.sis.measure.Quantities;
+import org.apache.sis.measure.Units;
 
 // Test dependencies
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -186,6 +188,21 @@ public class DefaultReversedCurveTest extends GeometryTest 
{
     public void testSubCurve_Length_Length() {
     }
 
+    /**
+     * Distances along the reversed curve are measured from the end of the 
base curve.
+     */
+    @Test
+    public void testSubCurve_Length() {
+        final LineString base = GeometryFactory.DEFAULT.createLineString(
+                GeometryFactory.DEFAULT.createDataPoints(CRS_2D, 0,0, 10,0, 
10,10));
+        final Curve reversed = GeometryFactory.DEFAULT.createReversed(base);
+        assertPositionEquals(10, 10, reversed.subCurve(Quantities.create(  0, 
Units.PERCENT)));
+        assertPositionEquals(10,  5, reversed.subCurve(Quantities.create( 25, 
Units.PERCENT)));
+        assertPositionEquals( 0,  0, reversed.subCurve(Quantities.create(100, 
Units.PERCENT)));
+        assertPositionEquals(10,  5, reversed.subCurve(Quantities.create(  5, 
Units.DEGREE)));
+        assertPositionEquals( 5,  0, reversed.subCurve(Quantities.create( 15, 
Units.DEGREE)));
+    }
+
     @Test
     @Disabled("Not implemented yet.")
     public void testGetOrientationSign() {
@@ -272,8 +289,11 @@ public class DefaultReversedCurveTest extends GeometryTest 
{
     }
 
     @Test
-    @Disabled("Not implemented yet.")
     public void testGetRepresentativePoint() {
+        final LineString base = GeometryFactory.DEFAULT.createLineString(
+                GeometryFactory.DEFAULT.createDataPoints(CRS_2D, 0,0, 10,0));
+        final Curve reversed = GeometryFactory.DEFAULT.createReversed(base);
+        assertPositionEquals(5, 0, reversed.getRepresentativePoint());
     }
 
     @Test

Reply via email to