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 2cb66ce84ff1a90189d59540921bdc7efa28e0d8
Author: jsorel <[email protected]>
AuthorDate: Thu Oct 15 11:14:25 2026 +0200

    feat(Geometry): decompose curve to lines in GeometryProcessor.toPrimitive
---
 .../geometries/operation/GeometryProcessor.java    | 20 ++++--
 .../sis/geometries/operation/Intersection.java     |  2 +-
 .../sis/geometries/operation/ToPrimitive.java      | 24 +++++--
 .../sis/geometries/operation/ToPrimitiveTest.java  | 84 ++++++++++++++++++----
 4 files changed, 104 insertions(+), 26 deletions(-)

diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
index 6a1d27d284..fce4bccc90 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java
@@ -25,6 +25,7 @@ import java.util.Map.Entry;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import javax.measure.Quantity;
+import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.DE9IM;
 import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.geometries.Geometries;
@@ -470,17 +471,26 @@ public final class GeometryProcessor {
      * Convert this geometry to a Primitive geometry type.
      * This method is provided as a conversion to GPU geometric model.
      *
+     * <p>Curves are converted to line strips with {@link 
Curve#asLine(Quantity, Quantity)},
+     * which receives the given spacing and offset.</p>
+     *
+     * @param  geom     the geometry to convert.
+     * @param  spacing  maximal distance along a curve between two points of 
its line strip,
+     *                  or zero or {@code null} for no limit.
+     * @param  offset   maximal distance between a curve and its line strip, 
or zero or {@code null} for no limit.
+     *                  If both the spacing and the offset are zero, a default 
precision is used.
      * @return equivalent primitive, all attributes are copied.
      *  can be a Primitive or MultiPrimitive
      */
-    public Geometry toPrimitive(Geometry geom) {
+    public Geometry toPrimitive(Geometry geom, Quantity<?> spacing, 
Quantity<?> offset) {
         if (geom instanceof MeshPrimitive || geom instanceof 
MultiMeshPrimitive) {
             //Does nothing, geometry is already a Primitive.
             return geom;
         } else if (geom instanceof Point cdt) {
             return ToPrimitive.toPrimitive(cdt);
-        } else if (geom instanceof LineString cdt) {
-            return ToPrimitive.toPrimitive(cdt);
+        } else if (geom instanceof Curve cdt) {
+            //also handles line strings, which are densified if a spacing is 
given.
+            return ToPrimitive.toPrimitive(cdt.asLine(spacing, offset));
         } else if (geom instanceof Polygon cdt) {
             return ToPrimitive.toPrimitive(cdt);
         } else if (geom instanceof PolyhedralSurface<?> cdt) {
@@ -489,9 +499,9 @@ public final class GeometryProcessor {
         } else if (geom instanceof GeometryCollection<?> cdt) {
             final GeometryType elementType = cdt.getElementType();
             switch (elementType) {
-                case LINESTRING : return 
ToPrimitive.toPrimitiveLine((GeometryCollection<LineString>) cdt);
+                case LINESTRING : return 
ToPrimitive.toPrimitiveLine((GeometryCollection<LineString>) cdt, spacing, 
offset);
                 case POINT : return 
ToPrimitive.toPrimitivePoint((GeometryCollection<Point>) cdt);
-                default : return ToPrimitive.toPrimitive(cdt);
+                default : return ToPrimitive.toPrimitive(cdt, spacing, offset);
             }
         }
 
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/Intersection.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/Intersection.java
index 5fdf9f2687..652d15c6af 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/Intersection.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/Intersection.java
@@ -221,7 +221,7 @@ public final class Intersection {
         }
 
         final GeometryCollection<LineString> mline = 
GeometryFactory.DEFAULT.createMultiLineString(segments.toArray(LineString[]::new));
-        final MeshPrimitive intersection = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(mline);
+        final MeshPrimitive intersection = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(mline, null, null);
         return intersection;
     }
 
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ToPrimitive.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ToPrimitive.java
index 0d7e8877e2..d3ad3251da 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ToPrimitive.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ToPrimitive.java
@@ -21,6 +21,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import javax.measure.Quantity;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Geometry;
@@ -133,10 +134,16 @@ public final class ToPrimitive {
 
     /**
      * Transform MultiLineString to Primitive.
-     * If all LineString are Lines, the return a single Primitive.lines 
otherwise return a MultiPrimitive.
+     * If all LineString are Lines and no spacing is given, the return a 
single Primitive.lines
+     * otherwise return a MultiPrimitive.
+     *
+     * @param spacing maximal distance between two points of a line strip, or 
zero or {@code null} for no limit.
+     * @param offset  ignored for line strings, which are their own linear 
approximation.
      */
-    public static Geometry toPrimitiveLine(GeometryCollection<LineString> 
geometry) throws OperationException {
-        boolean allLines = true;
+    public static Geometry toPrimitiveLine(GeometryCollection<LineString> 
geometry, Quantity<?> spacing, Quantity<?> offset)
+            throws OperationException
+    {
+        boolean allLines = (spacing == null || 
spacing.getValue().doubleValue() == 0);
         final int numGeometries = geometry.getNumGeometries();
         for (int i = 0; i < numGeometries && allLines; i++) {
             allLines &= geometry.getGeometryN(i).isLine();
@@ -162,7 +169,7 @@ public final class ToPrimitive {
         } else {
             final MultiMeshPrimitive<MeshPrimitive> mp = new 
MultiMeshPrimitive(geometry.getCoordinateReferenceSystem());
             for (int i = 0; i < numGeometries; i++) {
-                final Geometry p = toPrimitive(geometry.getGeometryN(i));
+                final Geometry p = 
toPrimitive(geometry.getGeometryN(i).asLine(spacing, offset));
                 if (p instanceof 
org.apache.sis.geometries.mesh.MultiMeshPrimitive<?> subm) {
                     mp.append(subm.getComponents());
                 } else if (p instanceof 
org.apache.sis.geometries.mesh.MeshPrimitive cd){
@@ -175,11 +182,16 @@ public final class ToPrimitive {
 
     /**
      * Transform GeometryCollection to Primitive.
+     *
+     * @param spacing maximal distance along a curve between two points of its 
line strip, or zero or {@code null}.
+     * @param offset  maximal distance between a curve and its line strip, or 
zero or {@code null}.
      */
-    public static MultiMeshPrimitive toPrimitive(GeometryCollection geometry) 
throws OperationException {
+    public static MultiMeshPrimitive toPrimitive(GeometryCollection geometry, 
Quantity<?> spacing, Quantity<?> offset)
+            throws OperationException
+    {
         final MultiMeshPrimitive<MeshPrimitive> mp = new 
MultiMeshPrimitive(geometry.getCoordinateReferenceSystem());
         for (int i = 0, n = geometry.getNumGeometries(); i < n; i++) {
-            final Geometry p = new 
GeometryProcessor().toPrimitive(geometry.getGeometryN(i));
+            final Geometry p = new 
GeometryProcessor().toPrimitive(geometry.getGeometryN(i), spacing, offset);
             if (p instanceof 
org.apache.sis.geometries.mesh.MultiMeshPrimitive<?> subm) {
                 mp.append(subm.getComponents());
             } else if (p instanceof 
org.apache.sis.geometries.mesh.MeshPrimitive cd){
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java
index bb0e23be50..cc48e16072 100644
--- 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java
@@ -30,6 +30,8 @@ import org.apache.sis.geometries.mesh.MeshPrimitive;
 import org.apache.sis.geometries.mesh.MultiMeshPrimitive;
 import org.apache.sis.maths.NDArrays;
 import org.apache.sis.maths.Array;
+import org.apache.sis.measure.Quantities;
+import org.apache.sis.measure.Units;
 import org.apache.sis.referencing.CommonCRS;
 
 // Test dependencies
@@ -48,7 +50,7 @@ public class ToPrimitiveTest {
     @Test
     public void testPoint() {
         final Point point = GeometryFactory.DEFAULT.createPoint(CRS2D, 1, 2);
-        final Geometry result = new GeometryProcessor().toPrimitive(point);
+        final Geometry result = new GeometryProcessor().toPrimitive(point, 
null, null);
         assertTrue(result instanceof MeshPrimitive);
         final MeshPrimitive primitive = (MeshPrimitive) result;
         assertEquals(MeshPrimitive.Type.POINTS, primitive.getType());
@@ -59,17 +61,71 @@ public class ToPrimitiveTest {
         final Array positions = NDArrays.of(CRS2D, new double[]{0,1,2,3});
         final DataPoints points = 
GeometryFactory.DEFAULT.createDataPoints(positions);
         final LineString line = 
GeometryFactory.DEFAULT.createLineString(points);
-        final Geometry result = new GeometryProcessor().toPrimitive(line);
+        final Geometry result = new GeometryProcessor().toPrimitive(line, 
null, null);
         assertTrue(result instanceof MeshPrimitive);
         final MeshPrimitive primitive = (MeshPrimitive) result;
         assertEquals(MeshPrimitive.Type.LINE_STRIP, primitive.getType());
     }
 
+    /**
+     * Tests that a curve is converted to a line strip whose points are on the 
curve,
+     * with the precision given by the offset.
+     */
+    @Test
+    public void testCurve() {
+        final GeometryFactory f = GeometryFactory.DEFAULT;
+        final var arc = f.createCircularString(f.createDataPoints(CRS2D, 0,0, 
5,5, 10,0));
+        final MeshPrimitive coarse = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(arc, null, Quantities.create(0.1, 
Units.DEGREE));
+        final MeshPrimitive fine   = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(arc, null, Quantities.create(0.001, 
Units.DEGREE));
+        assertEquals(MeshPrimitive.Type.LINE_STRIP, coarse.getType());
+        final Array positions = fine.getPositions();
+        assertTrue(positions.getLength() > coarse.getPositions().getLength(), 
"A smaller offset shall give more points.");
+        for (long i = 0; i < positions.getLength(); i++) {
+            final double[] p = positions.get(i).toArrayDouble();
+            assertEquals(5, Math.hypot(p[0] - 5, p[1]), 1E-9, "Point not on 
the arc.");
+        }
+    }
+
+    /**
+     * Tests that the spacing and the offset are given to each curve of a 
collection.
+     */
+    @Test
+    public void testMultiCurve() {
+        final GeometryFactory f = GeometryFactory.DEFAULT;
+        final var arc1 = f.createCircularString(f.createDataPoints(CRS2D, 0,0, 
5,5, 10,0));
+        final var arc2 = f.createCircularString(f.createDataPoints(CRS2D, 
20,0, 25,5, 30,0));
+        final Geometry result = new 
GeometryProcessor().toPrimitive(f.createMultiCurve(CRS2D, arc1, arc2),
+                Quantities.create(1, Units.DEGREE), null);
+        assertTrue(result instanceof MultiMeshPrimitive);
+        for (final Object component : ((MultiMeshPrimitive<?>) 
result).getComponents()) {
+            final Array positions = ((MeshPrimitive) component).getPositions();
+            // A half circle of radius 5 is 15.7 long, so a spacing of 1 needs 
at least 16 segments.
+            assertTrue(positions.getLength() >= 17, "Spacing not applied: " + 
positions.getLength() + " points.");
+        }
+    }
+
+    /**
+     * Tests that a spacing densifies the line strings of a collection.
+     */
+    @Test
+    public void testMultiLineStringWithSpacing() {
+        final GeometryFactory f = GeometryFactory.DEFAULT;
+        final var lines = f.createMultiLineString(CRS2D,
+                f.createLineString(f.createDataPoints(CRS2D, 0,0, 10,0)),
+                f.createLineString(f.createDataPoints(CRS2D, 0,5, 10,5)));
+        assertEquals(MeshPrimitive.Type.LINES, ((MeshPrimitive) new 
GeometryProcessor().toPrimitive(lines, null, null)).getType());
+        final Geometry result = new GeometryProcessor().toPrimitive(lines, 
Quantities.create(2.5, Units.DEGREE), null);
+        assertTrue(result instanceof MultiMeshPrimitive);
+        for (final Object component : ((MultiMeshPrimitive<?>) 
result).getComponents()) {
+            assertEquals(5, ((MeshPrimitive) 
component).getPositions().getLength());
+        }
+    }
+
     @Test
     public void testPolygon() {
         final LinearRing exterior = ring(0,0, 2,0, 2,2, 0,2, 0,0);
         final Polygon polygon = 
GeometryFactory.DEFAULT.createPolygon(exterior, null);
-        final Geometry result = new GeometryProcessor().toPrimitive(polygon);
+        final Geometry result = new GeometryProcessor().toPrimitive(polygon, 
null, null);
         assertTrue(result instanceof MeshPrimitive);
         final MeshPrimitive primitive = (MeshPrimitive) result;
         assertEquals(MeshPrimitive.Type.TRIANGLES, primitive.getType());
@@ -86,7 +142,7 @@ public class ToPrimitiveTest {
         final LinearRing exterior = ring(0,0, 10,0, 10,10, 0,10, 0,0);
         final LinearRing hole     = ring(2,2, 2,4, 4,4, 4,2, 2,2);
         final Polygon polygon = 
GeometryFactory.DEFAULT.createPolygon(exterior, java.util.List.of(hole));
-        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon);
+        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon, null, null);
         assertEquals(MeshPrimitive.Type.TRIANGLES, primitive.getType());
         assertEquals(96, trianglesArea(primitive), 1E-9);
     }
@@ -100,7 +156,7 @@ public class ToPrimitiveTest {
         final LinearRing exterior = ring(0,0, 10,0, 10,10, 0,10, 0,0);
         final LinearRing hole     = ring(4,4, 4,2, 2,2, 2,4, 4,4);
         final Polygon polygon = 
GeometryFactory.DEFAULT.createPolygon(exterior, java.util.List.of(hole));
-        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon);
+        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon, null, null);
         assertEquals(96, trianglesArea(primitive), 1E-9);
     }
 
@@ -112,7 +168,7 @@ public class ToPrimitiveTest {
         final LinearRing exterior = 
GeometryFactory.DEFAULT.createLinearRing(GeometryFactory.DEFAULT.createDataPoints(
                 NDArrays.of(CommonCRS.WGS84.geographic3D(), 0,0,5, 2,0,5, 
2,2,7, 0,2,7, 0,0,5)));
         final Polygon polygon = 
GeometryFactory.DEFAULT.createPolygon(exterior, null);
-        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon);
+        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon, null, null);
         assertEquals(MeshPrimitive.Type.TRIANGLES, primitive.getType());
         assertEquals(3, primitive.getPositions().getDimension());
         assertEquals(6, primitive.getPositions().getLength(), "Expected 2 
triangles.");
@@ -132,7 +188,7 @@ public class ToPrimitiveTest {
         final LinearRing exterior = ring3D(crs, 0,0,0, 6,0,0, 6,0,4, 0,0,4, 
0,0,0);
         final LinearRing window   = ring3D(crs, 2,0,1, 2,0,3, 4,0,3, 4,0,1, 
2,0,1);
         final Polygon polygon = 
GeometryFactory.DEFAULT.createPolygon(exterior, java.util.List.of(window));
-        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon);
+        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon, null, null);
         assertEquals(20, trianglesArea3D(primitive), 1E-9);
         final double[] c = primitive.getPositions().toArrayDouble();
         for (int i = 1; i < c.length; i += 3) {
@@ -149,7 +205,7 @@ public class ToPrimitiveTest {
         final CoordinateReferenceSystem crs = CommonCRS.WGS84.geographic3D();
         final LinearRing exterior = ring3D(crs, 3,0,0, 3,5,0, 3,5,2, 3,0,2, 
3,0,0);
         final Polygon polygon = 
GeometryFactory.DEFAULT.createPolygon(exterior, null);
-        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon);
+        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon, null, null);
         assertEquals(10, trianglesArea3D(primitive), 1E-9);
         final double[] c = primitive.getPositions().toArrayDouble();
         for (int i = 0; i < c.length; i += 3) {
@@ -167,7 +223,7 @@ public class ToPrimitiveTest {
         final LinearRing exterior = ring3D(crs, 0,0,5, 10,0,5, 10,10,5, 
0,10,5, 0,0,5);
         final LinearRing hole     = ring3D(crs, 2,2,5, 2,4,5, 4,4,5, 4,2,5, 
2,2,5);
         final Polygon polygon = 
GeometryFactory.DEFAULT.createPolygon(exterior, java.util.List.of(hole));
-        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon);
+        final MeshPrimitive primitive = (MeshPrimitive) new 
GeometryProcessor().toPrimitive(polygon, null, null);
         assertEquals(96, trianglesArea3D(primitive), 1E-9);
         final double[] c = primitive.getPositions().toArrayDouble();
         for (int i = 2; i < c.length; i += 3) {
@@ -185,7 +241,7 @@ public class ToPrimitiveTest {
         final org.apache.sis.geometries.surface.TIN tin = f.createTIN(crs, new 
org.apache.sis.geometries.surface.Triangle[] {
             f.createTriangle(ring3D(crs, 0,0,0, 2,0,0, 0,2,1, 0,0,0)),
             f.createTriangle(ring3D(crs, 2,0,0, 2,2,1, 0,2,1, 2,0,0))});
-        final Geometry result = new GeometryProcessor().toPrimitive(tin);
+        final Geometry result = new GeometryProcessor().toPrimitive(tin, null, 
null);
         assertTrue(result instanceof MeshPrimitive.Triangles);
         final MeshPrimitive primitive = (MeshPrimitive) result;
         assertEquals(3, primitive.getPositions().getDimension());
@@ -207,7 +263,7 @@ public class ToPrimitiveTest {
             f.createPolygon(ring3D(crs, 0,0,0, 2,0,0, 2,0,2, 0,0,2, 0,0,0), 
null),
             f.createPolygon(ring3D(crs, 0,2,0, 0,2,2, 2,2,2, 2,2,0, 0,2,0), 
null),
             f.createPolygon(ring3D(crs, 0,0,0, 0,2,0, 2,2,0, 2,0,0, 0,0,0), 
null)};
-        final Geometry result = new 
GeometryProcessor().toPrimitive(f.createPolyhedralSurface(crs, faces));
+        final Geometry result = new 
GeometryProcessor().toPrimitive(f.createPolyhedralSurface(crs, faces), null, 
null);
         assertTrue(result instanceof MeshPrimitive.Triangles);
         final MeshPrimitive primitive = (MeshPrimitive) result;
         assertEquals(30, primitive.getPositions().getLength(), "Expected 2 
triangles per face.");
@@ -267,7 +323,7 @@ public class ToPrimitiveTest {
 
             final GeometryCollection<LineString> mlines = 
GeometryFactory.DEFAULT.createMultiLineString(line1, line2);
 
-            final Geometry result = new 
GeometryProcessor().toPrimitive(mlines);
+            final Geometry result = new 
GeometryProcessor().toPrimitive(mlines, null, null);
             assertTrue(result instanceof MultiMeshPrimitive);
             final MultiMeshPrimitive mp = (MultiMeshPrimitive) result;
             assertEquals(2, mp.getNumGeometries());
@@ -291,7 +347,7 @@ public class ToPrimitiveTest {
 
             final GeometryCollection<LineString> mlines = 
GeometryFactory.DEFAULT.createMultiLineString(line1, line2);
 
-            final Geometry result = new 
GeometryProcessor().toPrimitive(mlines);
+            final Geometry result = new 
GeometryProcessor().toPrimitive(mlines, null, null);
             assertTrue(result instanceof MeshPrimitive.Lines);
             final MeshPrimitive.Lines mp = (MeshPrimitive.Lines) result;
             assertEquals(2, mp.getNumGeometries());
@@ -308,7 +364,7 @@ public class ToPrimitiveTest {
         final DataPoints points = 
GeometryFactory.DEFAULT.createDataPoints(positions1);
         final GeometryCollection<Point> mpoints = 
GeometryFactory.DEFAULT.createMultiPoint(points);
 
-        final Geometry result = new GeometryProcessor().toPrimitive(mpoints);
+        final Geometry result = new GeometryProcessor().toPrimitive(mpoints, 
null, null);
         assertTrue(result instanceof MeshPrimitive);
         final MeshPrimitive primitive1 = (MeshPrimitive) result;
         assertEquals(MeshPrimitive.Type.POINTS, primitive1.getType());

Reply via email to