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());
