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 7fd587c143600a00a0beee6734464ca69d53dae5 Author: jsorel <[email protected]> AuthorDate: Wed Oct 14 12:14:16 2026 +0200 feat(Geometry): implement getEnvelope on spline type geometries and getBoundary on BBox and OBBox --- .../main/org/apache/sis/geometries/BBox.java | 81 +++++++++++++++++++++- .../org/apache/sis/geometries/GeometryFactory.java | 7 +- .../main/org/apache/sis/geometries/OBBox.java | 14 +++- .../main/org/apache/sis/geometries/Solid.java | 8 +-- .../main/org/apache/sis/geometries/Surface.java | 7 +- .../internal/shared/AbstractGeometry.java | 56 +++++++++++++++ .../internal/shared/DefaultBSplineCurve.java | 6 +- .../internal/shared/DefaultBSplineSolid.java | 18 +++-- .../internal/shared/DefaultBSplineSurface.java | 17 +++-- .../internal/shared/DefaultBilinearGrid.java | 17 +++-- .../internal/shared/DefaultCylinder.java | 6 -- .../internal/shared/DefaultEllipsoid.java | 3 +- .../internal/shared/DefaultNurbSurface.java | 14 ++-- .../internal/shared/DefaultOffsetCurve.java | 33 ++++++++- .../geometries/internal/shared/DefaultPrism.java | 48 ++++++++++++- .../geometries/internal/shared/DefaultSphere.java | 3 +- .../org/apache/sis/geometries/solid/Frustrum.java | 17 ++++- .../sis/geometries/solid/ParametricCurveSolid.java | 8 +-- .../apache/sis/geometries/surface/Cylinder.java | 25 +++++++ .../geometries/surface/ParametricCurveSurface.java | 7 +- 20 files changed, 335 insertions(+), 60 deletions(-) diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/BBox.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/BBox.java index f188a6c71a..50a71fd685 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/BBox.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/BBox.java @@ -16,14 +16,17 @@ */ package org.apache.sis.geometries; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.opengis.coordinate.MismatchedDimensionException; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.internal.shared.IndexedPoint; +import org.apache.sis.geometries.surface.Polygon; import org.apache.sis.geometry.GeneralEnvelope; 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.Vectors; @@ -127,9 +130,85 @@ public final class BBox extends GeneralEnvelope implements Geometry { return this.clone(); } + /** + * Returns the boundary of this box: its two end points in 1 dimension, a counterclockwise ring + * in 2 dimensions, or its six faces oriented outward in 3 dimensions. An empty box has an empty + * boundary. + * + * @throws UnsupportedOperationException if this box has more than 3 dimensions. + */ @Override public Geometry getBoundary() { - throw new UnsupportedOperationException("Not supported yet."); + final int dim = getDimension(); + CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); + if (crs == null) { + crs = Geometries.getUndefinedCRS(dim); + } + final GeometryFactory factory = GeometryFactory.DEFAULT; + if (isEmpty()) { + return factory.createEmpty(crs); + } + switch (dim) { + case 1: { + return factory.createMultiPoint(crs, factory.createPoint(crs, getMinimum(0)), + factory.createPoint(crs, getMaximum(0))); + } + case 2: { + final double xmin = getMinimum(0), xmax = getMaximum(0); + final double ymin = getMinimum(1), ymax = getMaximum(1); + return factory.createLinearRing(factory.createDataPoints(NDArrays.of(SampleSystem.of(crs), + xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax, xmin, ymin))); + } + case 3: { + final var corners = new Vector<?>[8]; + for (int i = 0; i < corners.length; i++) { + final Vector<?> corner = Vectors.create(crs, DataType.DOUBLE); + for (int k = 0; k < 3; k++) { + corner.set(k, ((i & (1 << k)) != 0) ? getMaximum(k) : getMinimum(k)); + } + corners[i] = corner; + } + return boxFaces(crs, corners, false); + } + default: { + throw new UnsupportedOperationException("The boundary of a box is computed in 3 dimensions at most."); + } + } + } + + /** + * Returns the six faces of a 3-dimensional box, each one a quadrilateral whose vertices are in + * counterclockwise order when seen from outside the box, so that its up-normal points outward. + * + * @param crs reference system of the corners. + * @param corners the 8 corners of the box. The corner at index <var>i</var> is on the upper side + * of the box axis <var>k</var> if the bit <var>k</var> of <var>i</var> is set. + * @param reverse whether the box axes are a left-handed system, which reverses the orientation. + * @return the six faces of the box. + */ + static GeometryCollection<Polygon> boxFaces(final CoordinateReferenceSystem crs, + final ReadOnly.Vector<?>[] corners, final boolean reverse) + { + final int[][] faces = { + {0, 4, 6, 2}, // Lower side of axis 0. + {1, 3, 7, 5}, // Upper side of axis 0. + {0, 1, 5, 4}, // Lower side of axis 1. + {2, 6, 7, 3}, // Upper side of axis 1. + {0, 2, 3, 1}, // Lower side of axis 2. + {4, 5, 7, 6} // Upper side of axis 2. + }; + final GeometryFactory factory = GeometryFactory.DEFAULT; + final var polygons = new Polygon[faces.length]; + for (int f = 0; f < faces.length; f++) { + final var ring = new ArrayList<ReadOnly.Vector<?>>(5); + for (int k = 0; k < 4; k++) { + ring.add(corners[faces[f][reverse ? 3 - k : k]]); + } + ring.add(ring.get(0)); + polygons[f] = factory.createPolygon(factory.createLinearRing(factory.createDataPoints( + NDArrays.of(ring, SampleSystem.of(crs), DataType.DOUBLE))), null); + } + return factory.createMultiPolygon(crs, polygons); } @Override diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java index cbb8e18d00..3ab912a8e3 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.Map; import javax.measure.Quantity; import javax.measure.Unit; -import org.opengis.geometry.DirectPosition; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.SingleCRS; import org.apache.sis.geometries.cs.Bearing; @@ -573,7 +572,7 @@ public final class GeometryFactory { * @param columns number of columns in the parameter grid. * @param knots knot values, one sequence per surface parameter, or {@code null} if none. */ - public BilinearGrid createBilinearGrid(DataPoints points, List<DirectPosition> controlPoints, + public BilinearGrid createBilinearGrid(DataPoints points, Array controlPoints, int rows, int columns, List<double[]> knots) { return new DefaultBilinearGrid(points, controlPoints, rows, columns, knots); @@ -597,7 +596,7 @@ public final class GeometryFactory { * @param polynomial {@code true} if the surface is polynomial, {@code false} if the control * points are expressed in homogeneous coordinates, making it rational. */ - public BSplineSurface createBSplineSurface(DataPoints points, List<DirectPosition> controlPoints, + public BSplineSurface createBSplineSurface(DataPoints points, Array controlPoints, int rows, int columns, List<double[]> knots, int degree, KnotType knotSpec, BSplineSurfaceForm surfaceForm, boolean polynomial) { @@ -732,7 +731,7 @@ public final class GeometryFactory { * @param columns number of vertical columns in the parameter grid. * @param files number of depth files in the parameter grid. */ - public BSplineSolid createBSolidSpline(DataPoints points, List<DirectPosition> controlPoints, + public BSplineSolid createBSolidSpline(DataPoints points, Array controlPoints, int rows, int columns, int files) { return new DefaultBSplineSolid(points, controlPoints, rows, columns, files); diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/OBBox.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/OBBox.java index 5ebb9b4567..5af435198a 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/OBBox.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/OBBox.java @@ -141,9 +141,21 @@ public final class OBBox extends AbstractGeometry { return env; } + /** + * Returns the six faces of this box, oriented outward. + */ @Override public Geometry getBoundary() { - throw new UnsupportedOperationException("Not supported yet."); + final var corners = new Vector<?>[8]; + for (int i = 0; i < corners.length; i++) { + final Vector<?> corner = center.copy(); + corner.add(((i & 1) != 0) ? xAxis : xAxis.copy().scale(-1)); + corner.add(((i & 2) != 0) ? yAxis : yAxis.copy().scale(-1)); + corner.add(((i & 4) != 0) ? zAxis : zAxis.copy().scale(-1)); + corners[i] = corner; + } + final boolean leftHanded = xAxis.dot(yAxis.cross(zAxis)) < 0; + return BBox.boxFaces(getCoordinateReferenceSystem(), corners, leftHanded); } /** diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java index 11f965cc22..4c9bf77492 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Solid.java @@ -21,9 +21,9 @@ import javax.measure.Quantity; import org.apache.sis.geometries.internal.shared.DefaultSolid; import static org.opengis.annotation.Specification.ISO_19107; import org.opengis.annotation.UML; -import org.opengis.geometry.DirectPosition; import org.apache.sis.geometries.solid.ParametricCurveSolid; import org.apache.sis.geometries.solid.Polyhedron; +import org.apache.sis.maths.Array; /** @@ -161,13 +161,13 @@ public sealed interface Solid extends Primitive * {@linkplain #getBoundary() boundary} of this solid.</li> * </ul> * - * @return solid control points, possibly empty. + * @return solid control points, or {@code null} if none. * * @see ISO 19107:2019 - 6.4.28.6 */ @UML(identifier="controlPoint", specification=ISO_19107) - default List<DirectPosition> getControlPoints() { - return List.of(); + default Array getControlPoints() { + return null; } /** diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java index 00f3850044..7e10f416be 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Surface.java @@ -28,6 +28,7 @@ import org.apache.sis.geometries.surface.Cylinder; import org.apache.sis.geometries.surface.ParametricCurveSurface; import org.apache.sis.geometries.surface.Polygon; import org.apache.sis.geometries.surface.PolyhedralSurface; +import org.apache.sis.maths.Array; import org.apache.sis.maths.Vector; import org.apache.sis.measure.Quantities; @@ -266,13 +267,13 @@ public sealed interface Surface extends Orientable * Positions used to build the geometry of this surface, the way they are used depending on the interpolation. * Control points do not necessarily lie on the surface. * - * @return surface control points, possibly empty. + * @return surface control points, or {@code null} if none. * * @see ISO 19107:2019 - 6.4.25.9 */ @UML(identifier="controlPoint", specification=ISO_19107) - default List<DirectPosition> getControlPoints() { - return List.of(); + default Array getControlPoints() { + return null; } /** diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java index 88d7c74fac..af03496c60 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java @@ -16,12 +16,18 @@ */ package org.apache.sis.geometries.internal.shared; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.opengis.geometry.Envelope; +import org.apache.sis.geometries.DataPoints; import org.apache.sis.geometries.Geometries; import org.apache.sis.geometries.Geometry; +import org.apache.sis.geometry.GeneralDirectPosition; import org.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.maths.Array; /** @@ -91,6 +97,56 @@ public abstract non-sealed class AbstractGeometry implements Geometry { return union; } + /** + * Returns the envelope of the given data points and control points, or {@code null} if both are empty. + * A spline whose basis functions are a partition of unity, such as a b-spline, stays inside the convex + * hull of its control points, so the returned envelope contains it. + * + * @param points data points of the spline, or {@code null} if none. + * @param controlPoints coordinates of the control points, possibly empty. + * @param homogeneous whether the control points have one more coordinate than the reference system, + * the weight by which the other coordinates are divided. Weights are assumed positive. + * @return envelope of the data points and of the control points, or {@code null} if there is none. + */ + protected static Envelope controlHullEnvelope(final DataPoints points, final List<double[]> controlPoints, + final boolean homogeneous) + { + GeneralEnvelope envelope = (points == null || points.isEmpty()) ? null : new GeneralEnvelope(points.getEnvelope()); + for (double[] coordinates : controlPoints) { + if (homogeneous) { + final double weight = coordinates[coordinates.length - 1]; + coordinates = Arrays.copyOf(coordinates, coordinates.length - 1); + for (int i = 0; i < coordinates.length; i++) { + coordinates[i] /= weight; + } + } + final var position = new GeneralDirectPosition(coordinates); + if (envelope == null) { + envelope = new GeneralEnvelope(position, position); + if (points != null) { + envelope.setCoordinateReferenceSystem(points.getCoordinateReferenceSystem()); + } + } else { + envelope.add(position); + } + } + return envelope; + } + + /** + * Returns the coordinates of the given control points, or an empty list if {@code null}. + */ + protected static List<double[]> coordinates(final Array controlPoints) { + if (controlPoints == null) { + return List.of(); + } + final var coordinates = new ArrayList<double[]>(); + for (long i = 0, n = controlPoints.getLength(); i < n; i++) { + coordinates.add(controlPoints.get(i).toArrayDouble()); + } + return coordinates; + } + /** * Adds the envelope of the given geometry to the given union, creating it if needed. * Geometries with no envelope at all (an empty one, typically) are skipped. diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java index 661ab93fe1..97c27f2e9d 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java @@ -173,9 +173,13 @@ public non-sealed class DefaultBSplineCurve extends AbstractGeometry implements return points.isEmpty(); } + /** + * Returns the envelope of the data points and of the control points, which contains this curve + * since a b-spline stays inside the convex hull of its control points. + */ @Override public Envelope getEnvelope() { - throw new UnsupportedOperationException("Not supported yet."); + return controlHullEnvelope(points, coordinates(controlPoints), rational); } /** diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java index 0cdd9b695d..95a6520574 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java @@ -16,7 +16,6 @@ */ package org.apache.sis.geometries.internal.shared; -import java.util.List; import java.util.Objects; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.Envelope; @@ -29,6 +28,7 @@ import org.apache.sis.geometries.DataPointsType; import org.apache.sis.geometries.GeometryCollection; import org.apache.sis.geometries.Surface; import org.apache.sis.geometries.solid.BSplineSolid; +import org.apache.sis.maths.Array; /** @@ -44,9 +44,9 @@ public non-sealed class DefaultBSplineSolid extends AbstractGeometry implements protected final DataPoints points; /** - * Control points of the section curves, in row-major order, or an empty list if none. + * Control points in row-major order, or {@code null} if none. */ - protected final List<DirectPosition> controlPoints; + protected final Array controlPoints; /** * Number of horizontal rows in the parameter grid. @@ -73,11 +73,11 @@ public non-sealed class DefaultBSplineSolid extends AbstractGeometry implements * @param columns number of vertical columns in the parameter grid. * @param files number of depth files in the parameter grid. */ - public DefaultBSplineSolid(final DataPoints points, final List<DirectPosition> controlPoints, + public DefaultBSplineSolid(final DataPoints points, final Array controlPoints, final int rows, final int columns, final int files) { this.points = Objects.requireNonNull(points); - this.controlPoints = (controlPoints == null) ? List.of() : List.copyOf(controlPoints); + this.controlPoints = controlPoints; this.rows = rows; this.columns = columns; this.files = files; @@ -104,7 +104,7 @@ public non-sealed class DefaultBSplineSolid extends AbstractGeometry implements } @Override - public List<DirectPosition> getControlPoints() { + public Array getControlPoints() { return controlPoints; } @@ -172,9 +172,13 @@ public non-sealed class DefaultBSplineSolid extends AbstractGeometry implements return points.isEmpty(); } + /** + * Returns the envelope of the data points and of the control points, which contains this solid + * since a b-spline stays inside the convex hull of its control points. + */ @Override public Envelope getEnvelope() { - throw new UnsupportedOperationException("Not supported yet."); + return controlHullEnvelope(points, coordinates(controlPoints), false); } @Override diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java index 943d4c8207..2db202201e 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java @@ -27,6 +27,7 @@ import org.apache.sis.geometries.Geometry; import org.apache.sis.geometries.curve.KnotType; import org.apache.sis.geometries.surface.BSplineSurface; import org.apache.sis.geometries.surface.BSplineSurfaceForm; +import org.apache.sis.maths.Array; /** @@ -46,9 +47,9 @@ public non-sealed class DefaultBSplineSurface extends AbstractGeometry implement protected final DataPoints points; /** - * Control points of the section curves, in row-major order, or an empty list if none. + * Control points in row-major order, or {@code null} if none. */ - protected final List<DirectPosition> controlPoints; + protected final Array controlPoints; /** * Number of rows in the parameter grid. @@ -102,12 +103,12 @@ public non-sealed class DefaultBSplineSurface extends AbstractGeometry implement * control points are expressed in homogeneous coordinates, which makes * it rational. */ - public DefaultBSplineSurface(final DataPoints points, final List<DirectPosition> controlPoints, + public DefaultBSplineSurface(final DataPoints points, final Array controlPoints, final int rows, final int columns, final List<double[]> knots, final int degree, final KnotType knotSpec, final BSplineSurfaceForm surfaceForm, final boolean polynomial) { this.points = Objects.requireNonNull(points); - this.controlPoints = (controlPoints == null) ? List.of() : List.copyOf(controlPoints); + this.controlPoints = controlPoints; this.rows = rows; this.columns = columns; this.knots = (knots == null) ? List.of() : List.copyOf(knots); @@ -158,7 +159,7 @@ public non-sealed class DefaultBSplineSurface extends AbstractGeometry implement } @Override - public List<DirectPosition> getControlPoints() { + public Array getControlPoints() { return controlPoints; } @@ -182,9 +183,13 @@ public non-sealed class DefaultBSplineSurface extends AbstractGeometry implement throw new UnsupportedOperationException("Not supported yet."); } + /** + * Returns the envelope of the data points and of the control points, which contains this surface + * since a b-spline stays inside the convex hull of its control points. + */ @Override public Envelope getEnvelope() { - throw new UnsupportedOperationException("Not supported yet."); + return controlHullEnvelope(points, coordinates(controlPoints), !polynomial); } @Override diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java index 92dedded85..f744d08321 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java @@ -28,6 +28,7 @@ import org.apache.sis.geometries.Geometry; import org.apache.sis.geometries.GeometryType; import org.apache.sis.geometries.DataPointsType; import org.apache.sis.geometries.surface.BilinearGrid; +import org.apache.sis.maths.Array; /** @@ -44,9 +45,9 @@ public non-sealed class DefaultBilinearGrid extends AbstractGeometry implements protected final DataPoints points; /** - * Control points of the section curves, in row-major order, or an empty list if none. + * Control points of the section curves, in row-major order, or {@code null} if none. */ - protected final List<DirectPosition> controlPoints; + protected final Array controlPoints; /** * Number of rows in the parameter grid. @@ -75,11 +76,11 @@ public non-sealed class DefaultBilinearGrid extends AbstractGeometry implements * @param columns number of columns in the parameter grid. * @param knots knot values, one sequence per surface parameter, or {@code null} if none. */ - public DefaultBilinearGrid(final DataPoints points, final List<DirectPosition> controlPoints, + public DefaultBilinearGrid(final DataPoints points, final Array controlPoints, final int rows, final int columns, final List<double[]> knots) { this.points = Objects.requireNonNull(points); - this.controlPoints = (controlPoints == null) ? List.of() : List.copyOf(controlPoints); + this.controlPoints = controlPoints; this.rows = rows; this.columns = columns; this.knots = (knots == null) ? List.of() : List.copyOf(knots); @@ -101,7 +102,7 @@ public non-sealed class DefaultBilinearGrid extends AbstractGeometry implements } @Override - public List<DirectPosition> getControlPoints() { + public Array getControlPoints() { return controlPoints; } @@ -166,9 +167,13 @@ public non-sealed class DefaultBilinearGrid extends AbstractGeometry implements throw new UnsupportedOperationException("Not supported yet."); } + /** + * Returns the envelope of the grid points. Each cell is a bilinear interpolation of its four + * corners with positive weights, so it stays inside the box of those corners. + */ @Override public Envelope getEnvelope() { - throw new UnsupportedOperationException("Not supported yet."); + return points.isEmpty() ? null : points.getEnvelope(); } @Override diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCylinder.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCylinder.java index cffd3f385f..46077c3f8f 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCylinder.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCylinder.java @@ -17,7 +17,6 @@ package org.apache.sis.geometries.internal.shared; import java.util.Objects; -import javax.measure.Quantity; import org.apache.sis.geometries.DataPoints; import org.apache.sis.geometries.GeometryFactory; import org.apache.sis.geometries.surface.Cylinder; @@ -120,11 +119,6 @@ public non-sealed class DefaultCylinder extends AbstractGeometry implements Cyli this.radiusBottom = radius; } - @Override - public Quantity<?> getArea() { - throw new UnsupportedOperationException("Not supported yet."); - } - @Override public boolean isClosed() { return closed; diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipsoid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipsoid.java index 40b491e35b..c4bb81d492 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipsoid.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipsoid.java @@ -26,6 +26,7 @@ import org.apache.sis.geometries.DataPoints; import org.apache.sis.geometries.Geometries; import org.apache.sis.geometries.GeometryFactory; import org.apache.sis.geometries.surface.Ellipsoid; +import org.apache.sis.maths.Array; import org.apache.sis.maths.DataType; import org.apache.sis.maths.NDArrays; import org.apache.sis.maths.ReadOnly; @@ -205,7 +206,7 @@ public non-sealed class DefaultEllipsoid extends AbstractGeometry implements Ell } @Override - public List<DirectPosition> getControlPoints() { + public Array getControlPoints() { throw new UnsupportedOperationException("Not supported yet."); } diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultNurbSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultNurbSurface.java index e541aad4bb..6c57b652fb 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultNurbSurface.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultNurbSurface.java @@ -29,6 +29,7 @@ import org.apache.sis.geometries.curve.KnotType; import org.apache.sis.geometries.operation.DeBoorAlgorithm; import org.apache.sis.geometries.surface.BSplineSurfaceForm; import org.apache.sis.geometries.surface.NurbSurface; +import org.apache.sis.maths.Array; import org.apache.sis.maths.NDArrays; import org.apache.sis.maths.Vector; import org.apache.sis.maths.Vectors; @@ -371,17 +372,16 @@ public non-sealed class DefaultNurbSurface extends AbstractGeometry implements N } /** - * Returns views of the control points in row-major order, without their weights. + * Returns a copy of the control points in row-major order, without their weights. */ @Override - public List<DirectPosition> getControlPoints() { - final var positions = new ArrayList<DirectPosition>(getRows() * getColumns()); + public Array getControlPoints() { + final var positions = new ArrayList<Vector<?>>(getRows() * getColumns()); for (final Vector<?>[] row : controlPoints) { - for (final Vector<?> p : row) { - positions.add(Vectors.asDirectPostion(p)); - } + positions.addAll(Arrays.asList(row)); } - return positions; + final Vector<?> first = controlPoints[0][0]; + return NDArrays.of(positions, first.getSampleSystem(), first.getDataType()); } @Override diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultOffsetCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultOffsetCurve.java index ccb47a4c91..c92ef3f4da 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultOffsetCurve.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultOffsetCurve.java @@ -17,7 +17,9 @@ package org.apache.sis.geometries.internal.shared; import java.util.Objects; +import javax.measure.IncommensurableException; import javax.measure.Quantity; +import javax.measure.Unit; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.Curve; @@ -25,7 +27,9 @@ import org.apache.sis.geometries.DataPoints; import org.apache.sis.geometries.DataPointsType; import org.apache.sis.geometries.cs.Bearing; import org.apache.sis.geometries.curve.OffsetCurve; +import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.maths.Array; +import org.apache.sis.measure.Units; /** @@ -114,9 +118,36 @@ public non-sealed class DefaultOffsetCurve extends AbstractGeometry implements O return baseCurve.isEmpty(); } + /** + * Returns the envelope of the base curve expanded by the offset distance on every axis. + * This envelope contains this curve, but may be larger than needed since the curve is + * offset on one side only and not along every axis. + * + * @throws UnsupportedOperationException if the distance unit cannot be converted to the unit of an axis. + */ @Override public Envelope getEnvelope() { - throw new UnsupportedOperationException("Not supported yet."); + final Envelope baseEnvelope = baseCurve.getEnvelope(); + if (baseEnvelope == null) { + return null; + } + final var envelope = new GeneralEnvelope(baseEnvelope); + final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem(); + final double value = Math.abs(distance.getValue().doubleValue()); + for (int i = 0, n = envelope.getDimension(); i < n; i++) { + double d = value; + final Unit<?> axisUnit = (crs != null) ? crs.getCoordinateSystem().getAxis(i).getUnit() : null; + if (axisUnit != null && !Units.UNITY.equals(distance.getUnit())) { + try { + d = distance.getUnit().getConverterToAny(axisUnit).convert(value); + } catch (IncommensurableException e) { + throw new UnsupportedOperationException("The offset distance in " + distance.getUnit() + + " cannot be converted to the unit of axis " + i + " (" + axisUnit + ").", e); + } + } + envelope.setRange(i, envelope.getMinimum(i) - d, envelope.getMaximum(i) + d); + } + return envelope; } } diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPrism.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPrism.java index 90aeba25b4..ea00a68ebc 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPrism.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPrism.java @@ -20,9 +20,13 @@ import java.util.Objects; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.SingleCRS; +import org.opengis.util.FactoryException; import org.apache.sis.geometries.Geometry; import org.apache.sis.geometries.Prism; +import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.measure.NumberRange; +import org.apache.sis.referencing.CRS; +import org.apache.sis.util.Utilities; /** @@ -90,9 +94,51 @@ public non-sealed class DefaultPrism extends AbstractGeometry implements Prism { return base.isEmpty(); } + /** + * Returns the envelope of the base extended by the extrusion range. If the reference system of the base + * contains the extrusion reference system, the range extends the corresponding axis. Otherwise the + * extrusion adds a dimension, and the envelope is in the compound of the two reference systems. + */ @Override public Envelope getEnvelope() { - throw new UnsupportedOperationException("Not supported yet."); + final Envelope baseEnvelope = base.getEnvelope(); + if (baseEnvelope == null) { + return null; + } + final double min = extrusionRange.getMinDouble(); + final double max = extrusionRange.getMaxDouble(); + final CoordinateReferenceSystem baseCRS = base.getCoordinateReferenceSystem(); + if (baseCRS != null && extrusionCrs != null) { + int offset = 0; + for (final SingleCRS component : CRS.getSingleComponents(baseCRS)) { + final int dimension = component.getCoordinateSystem().getDimension(); + if (Utilities.equalsIgnoreMetadata(component, extrusionCrs)) { + final var envelope = new GeneralEnvelope(baseEnvelope); + for (int i = offset; i < offset + dimension; i++) { + envelope.setRange(i, Math.min(envelope.getMinimum(i), min), + Math.max(envelope.getMaximum(i), max)); + } + return envelope; + } + offset += dimension; + } + } + final int n = baseEnvelope.getDimension(); + final GeneralEnvelope envelope; + if (baseCRS != null && extrusionCrs != null) { + try { + envelope = new GeneralEnvelope(CRS.compound(baseCRS, extrusionCrs)); + } catch (FactoryException e) { + throw new IllegalStateException("Cannot combine the base and extrusion reference systems.", e); + } + } else { + envelope = new GeneralEnvelope(n + 1); + } + for (int i = 0; i < n; i++) { + envelope.setRange(i, baseEnvelope.getMinimum(i), baseEnvelope.getMaximum(i)); + } + envelope.setRange(n, min, max); + return envelope; } @Override diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSphere.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSphere.java index 90a9c7d109..a25f66c120 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSphere.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSphere.java @@ -24,6 +24,7 @@ import org.apache.sis.geometries.DataPoints; import org.apache.sis.geometries.Geometries; import org.apache.sis.geometries.GeometryFactory; import org.apache.sis.geometries.surface.Sphere; +import org.apache.sis.maths.Array; import org.apache.sis.maths.DataType; import org.apache.sis.maths.NDArrays; import org.apache.sis.maths.ReadOnly; @@ -137,7 +138,7 @@ public non-sealed class DefaultSphere extends AbstractGeometry implements Sphere } @Override - public List<DirectPosition> getControlPoints() { + public Array getControlPoints() { throw new UnsupportedOperationException("Not supported yet."); } diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Frustrum.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Frustrum.java index da5c29511c..4cc00bc52b 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Frustrum.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Frustrum.java @@ -55,19 +55,30 @@ public final class Frustrum extends AbstractGeometry { return planes; } + /** + * Returns the reference system of the planes, taken from the first one. + */ @Override public CoordinateReferenceSystem getCoordinateReferenceSystem() { - throw new UnsupportedOperationException("Not supported yet."); + return planes[0].getCoordinateReferenceSystem(); } + /** + * Sets the reference system of all the planes. + */ @Override public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) throws IllegalArgumentException { - throw new UnsupportedOperationException("Not supported yet."); + for (final Plane plane : planes) { + plane.setCoordinateReferenceSystem(crs); + } } + /** + * Returns the type of the data points of the planes, taken from the first one. + */ @Override public DataPointsType getDataPointsType() { - throw new UnsupportedOperationException("Not supported yet."); + return planes[0].getDataPointsType(); } @Override diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java index 15aa333019..52e2d5582a 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java @@ -16,7 +16,6 @@ */ package org.apache.sis.geometries.solid; -import java.util.List; import static org.opengis.annotation.Specification.ISO_19107; import org.opengis.annotation.UML; import org.opengis.geometry.DirectPosition; @@ -25,6 +24,7 @@ import org.apache.sis.geometries.DataPoints; import org.apache.sis.geometries.GeometryType; import org.apache.sis.geometries.Solid; import org.apache.sis.geometries.SolidInterpolation; +import org.apache.sis.maths.Array; /** @@ -163,18 +163,18 @@ public sealed interface ParametricCurveSolid extends Solid * * <p>Constraints:</p> * <ul> - * <li>The list holds {@link #getRows()} × {@link #getColumns()} × {@link #getFiles()} + * <li>The array holds {@link #getRows()} × {@link #getColumns()} × {@link #getFiles()} * positions, stored in row-major order.</li> * <li>Control points do not necessarily lie inside this solid.</li> * </ul> * - * @return solid control points, possibly empty. + * @return solid control points, or {@code null} if none. * * @see ISO 19107:2019 - 9.3.1.3 */ @UML(identifier="controlPoints", specification=ISO_19107) @Override - List<DirectPosition> getControlPoints(); + Array getControlPoints(); /** * Returns the curve traversing this solid horizontally at the given vertical and depth parameters. diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Cylinder.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Cylinder.java index 73b4b4d232..08f6f758d2 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Cylinder.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Cylinder.java @@ -18,11 +18,13 @@ package org.apache.sis.geometries.surface; import java.util.ArrayList; import java.util.List; +import javax.measure.Quantity; import static org.opengis.annotation.Specification.ISO_12113; import org.opengis.annotation.UML; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.BBox; import org.apache.sis.geometries.DataPointsType; +import org.apache.sis.geometries.Geometries; import org.apache.sis.geometries.Geometry; import org.apache.sis.geometries.GeometryFactory; import org.apache.sis.geometries.GeometryType; @@ -35,6 +37,7 @@ import org.apache.sis.maths.NDArrays; import org.apache.sis.maths.ReadOnly; import org.apache.sis.maths.Vectors; import org.apache.sis.maths.Vector; +import org.apache.sis.measure.Quantities; /** @@ -218,6 +221,28 @@ public sealed interface Cylinder extends Surface return bbox; } + /** + * Returns the area of the lateral surface, π(r₁ + r₂)⋅√(h² + (r₁ − r₂)²) where <var>h</var> is the + * {@linkplain #getHeight() height}, plus the areas π<var>r</var>² of the two end disks if this + * cylinder is {@linkplain #isClosed() closed}. + * + * @throws UnsupportedOperationException if this cylinder is not 3-dimensional. + */ + @Override + default Quantity<?> getArea() { + if (getDataPoints().getDimension() != 3) { + throw new UnsupportedOperationException("The area of a cylinder is computed in 3 dimensions only."); + } + final double r1 = getRadiusBottom(); + final double r2 = getRadiusTop(); + final double h = getHeight(); + double area = Math.PI * (r1 + r2) * Math.hypot(h, r1 - r2); + if (isClosed()) { + area += Math.PI * (r1*r1 + r2*r2); + } + return Quantities.create(area, Geometries.getAreaUnit(this)); + } + /** * Returns the empty geometry if this cylinder is {@linkplain #isClosed() closed}, * or the circles at its ends otherwise. diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java index 6e61608751..0d0bd0e803 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java @@ -27,6 +27,7 @@ import org.apache.sis.geometries.DataPoints; import org.apache.sis.geometries.GeometryType; import org.apache.sis.geometries.Surface; import org.apache.sis.geometries.SurfaceInterpolation; +import org.apache.sis.maths.Array; // Specific to the geoapi-4.0 branch: import org.opengis.metadata.Identifier; @@ -110,18 +111,18 @@ public sealed interface ParametricCurveSurface extends Surface, ReferenceSystem * * <p>Constraints:</p> * <ul> - * <li>The list holds {@link #getRows()} × {@link #getColumns()} positions, + * <li>The array holds {@link #getRows()} × {@link #getColumns()} positions, * stored in row-major order.</li> * <li>Control points do not necessarily lie on this surface.</li> * </ul> * - * @return surface control points, possibly empty. + * @return surface control points, or {@code null} if none. * * @see ISO 19107:2019 - 8.3.2.6 */ @UML(identifier="controlPoints", specification=ISO_19107) @Override - List<DirectPosition> getControlPoints(); + Array getControlPoints(); /** * Positions of this surface at the knots of the parameter grid, so that the data point at
