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 e2171bdbd82ba897ad4005e1276b9759767815ff Author: jsorel <[email protected]> AuthorDate: Wed Oct 14 14:32:43 2026 +0200 feat(Geometry): enfore Geometry always have a CRS, compute polyhedron volume --- .../main/org/apache/sis/geometries/BBox.java | 30 ++++++-- .../main/org/apache/sis/geometries/Geometries.java | 32 +++++--- .../main/org/apache/sis/geometries/HyperPlane.java | 2 +- .../main/org/apache/sis/geometries/Plane.java | 2 +- .../main/org/apache/sis/geometries/Point.java | 40 +++++++++- .../org/apache/sis/geometries/adapter/Srid.java | 5 +- .../sis/geometries/adapter/WellKnownBinary.java | 9 +-- .../sis/geometries/adapter/WellKnownText.java | 8 +- .../main/org/apache/sis/geometries/curve/Ray.java | 2 +- .../internal/shared/DefaultCompoundCurve.java | 5 +- .../geometries/internal/shared/DefaultEmpty.java | 5 +- .../internal/shared/DefaultGeometryCollection.java | 5 +- .../internal/shared/DefaultNurbCurve.java | 1 + .../internal/shared/DefaultNurbSurface.java | 12 +++ .../internal/shared/DefaultOffsetCurve.java | 4 +- .../internal/shared/DefaultPolyhedralSurface.java | 8 +- .../internal/shared/DefaultPolyhedron.java | 4 - .../geometries/internal/shared/DefaultPrism.java | 4 +- .../geometries/internal/shared/DefaultSolid.java | 3 +- .../sis/geometries/mesh/MultiMeshPrimitive.java | 1 + .../sis/geometries/operation/ProcessorUtils.java | 2 - .../operation/simplify/greedyinsert/WTriangle.java | 9 +-- .../triangulate/delaunay/OrientedEdge.java | 3 +- .../triangulate/delaunay/OrientedTriangle.java | 9 +-- .../apache/sis/geometries/solid/Polyhedron.java | 88 ++++++++++++++++++++++ .../org/apache/sis/geometries/surface/Capsule.java | 2 +- .../apache/sis/geometries/surface/Cylinder.java | 2 +- .../apache/sis/geometries/surface/Triangle.java | 10 +++ 28 files changed, 232 insertions(+), 75 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 50a71fd685..873d348f15 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 @@ -47,7 +47,7 @@ public final class BBox extends GeneralEnvelope implements Geometry { * @param dimension number of dimensions of the bbox, must be positive. */ public BBox(int dimension) { - super(dimension); + super(Geometries.getUndefinedCRS(dimension)); } /** @@ -55,7 +55,7 @@ public final class BBox extends GeneralEnvelope implements Geometry { * @param corners lower corner and higher corner, in crs axis order */ public BBox(int dimension, double ... corners) { - super(dimension); + super(Geometries.getUndefinedCRS(dimension)); setEnvelope(corners); } @@ -65,6 +65,7 @@ public final class BBox extends GeneralEnvelope implements Geometry { */ public BBox(Vector lower, Vector upper) { super(Vectors.asDirectPostion(lower), Vectors.asDirectPostion(upper)); + ensureCRS(); } /** @@ -88,6 +89,26 @@ public final class BBox extends GeneralEnvelope implements Geometry { */ public BBox(Envelope env) { super(env); + ensureCRS(); + } + + /** + * Assigns an undefined reference system if this box has none, + * since the reference system of a geometry is never null. + */ + private void ensureCRS() { + if (super.getCoordinateReferenceSystem() == null) { + super.setCoordinateReferenceSystem(Geometries.getUndefinedCRS(getDimension())); + } + } + + /** + * Sets the reference system of this box, or an undefined one if the given one is null. + * The coordinates are not transformed. + */ + @Override + public void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs) { + super.setCoordinateReferenceSystem(Geometries.orUndefinedCRS(crs, getDimension())); } @Override @@ -140,10 +161,7 @@ public final class BBox extends GeneralEnvelope implements Geometry { @Override public Geometry getBoundary() { final int dim = getDimension(); - CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); - if (crs == null) { - crs = Geometries.getUndefinedCRS(dim); - } + final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); final GeometryFactory factory = GeometryFactory.DEFAULT; if (isEmpty()) { return factory.createEmpty(crs); diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java index 73922149d2..9eb963ebc8 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Geometries.java @@ -129,6 +129,19 @@ public final class Geometries { */ public static final CoordinateReferenceSystem PSEUDOGEO_3D = createCartesianCRS3D(X_RIGHT, Y_FORWARD, Z_UP); + /** + * Returns the given reference system, or the undefined reference system of the given dimension + * if the given one is null. The reference system of a geometry is never null, so this method is + * used by geometries built on coordinates which may carry no reference system. + * + * @param crs the reference system, or {@code null}. + * @param dimension number of dimensions of the undefined reference system to use if {@code crs} is null. + * @return the given reference system, or an undefined one if {@code null}. + */ + public static CoordinateReferenceSystem orUndefinedCRS(final CoordinateReferenceSystem crs, final int dimension) { + return (crs != null) ? crs : getUndefinedCRS(dimension); + } + /** * Get an undefined CRS whic cannot be converted to any other CRS. * @param nbDim CRS dimension @@ -703,8 +716,7 @@ public final class Geometries { * {@linkplain Surface#getPerimeter() perimeter}, are expressed. * * @param geom the geometry from which to get the coordinate unit. - * @return unit of the first coordinate system axis, or {@link Units#UNITY} if the geometry - * has no coordinate reference system. + * @return unit of the first coordinate system axis. */ @SuppressWarnings("rawtypes") public static Unit getLinearUnit(final Geometry geom) { @@ -716,8 +728,8 @@ public final class Geometries { * geometry is expressed, as the product of the units of the two first coordinate system axes. * * @param geom the geometry from which to get the area unit. - * @return product of the units of the two first axes, or {@link Units#UNITY} if the geometry - * has no coordinate reference system or if that system is not at least 2-dimensional. + * @return product of the units of the two first axes, or {@link Units#UNITY} if the + * coordinate reference system is not at least 2-dimensional. */ @SuppressWarnings("rawtypes") public static Unit getAreaUnit(final Geometry geom) { @@ -729,8 +741,8 @@ public final class Geometries { * geometry is expressed, as the product of the units of the three first coordinate system axes. * * @param geom the geometry from which to get the volume unit. - * @return product of the units of the three first axes, or {@link Units#UNITY} if the geometry - * has no coordinate reference system or if that system is not at least 3-dimensional. + * @return product of the units of the three first axes, or {@link Units#UNITY} if the + * coordinate reference system is not at least 3-dimensional. */ @SuppressWarnings("rawtypes") public static Unit getVolumeUnit(final Geometry geom) { @@ -745,15 +757,11 @@ public final class Geometries { * @param geom the geometry from which to get the units. * @param n number of leading axes to multiply. * @return product of the units of the {@code n} first axes, or {@link Units#UNITY} if the - * geometry has no coordinate reference system or has less than {@code n} dimensions. + * geometry has less than {@code n} dimensions. */ @SuppressWarnings("rawtypes") private static Unit productOfAxisUnits(final Geometry geom, final int n) { - final CoordinateReferenceSystem crs = geom.getCoordinateReferenceSystem(); - if (crs == null) { - return Units.UNITY; - } - final CoordinateSystem cs = crs.getCoordinateSystem(); + final CoordinateSystem cs = geom.getCoordinateReferenceSystem().getCoordinateSystem(); if (cs.getDimension() < n) { return Units.UNITY; } diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/HyperPlane.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/HyperPlane.java index ecff5d2b03..72068f6ad6 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/HyperPlane.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/HyperPlane.java @@ -114,7 +114,7 @@ public final class HyperPlane extends AbstractGeometry{ public BBox getEnvelope() { final ReadOnly.Vector<?> position = getPosition(); final int dim = normal.getDimension(); - final BBox bbox = new BBox(dim); + final BBox bbox = new BBox(getCoordinateReferenceSystem()); /* * Search the axis the normal is aligned with. There is one only if every other * dimension of the normal is null, in which case the hyperplane is flat on it. diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Plane.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Plane.java index 3bd323154a..da8d2ee557 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Plane.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Plane.java @@ -175,7 +175,7 @@ public final class Plane extends AbstractGeometry{ public BBox getEnvelope() { final ReadOnly.Vector<?> position = getPosition(); final int dim = normal.getDimension(); - final BBox bbox = new BBox(dim); + final BBox bbox = new BBox(getCoordinateReferenceSystem()); /* * Search the axis the normal is aligned with. There is one only if every other * dimension of the normal is null, in which case the plane is flat on it. diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java index 59100d9cb9..dcb9d731b5 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/Point.java @@ -19,9 +19,14 @@ package org.apache.sis.geometries; import java.util.List; import static org.opengis.annotation.Specification.ISO_19107; import org.opengis.annotation.UML; +import org.opengis.coordinate.MismatchedDimensionException; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.crs.SingleCRS; +import org.opengis.referencing.cs.CoordinateSystem; +import org.opengis.referencing.cs.EllipsoidalCS; +import org.opengis.referencing.cs.SphericalCS; import org.apache.sis.geometries.cs.Bearing; import org.apache.sis.geometries.internal.shared.ArrayDataPoints; import org.apache.sis.geometries.internal.shared.DefaultPoint; @@ -29,6 +34,9 @@ import org.apache.sis.geometries.internal.shared.IndexedPoint; import org.apache.sis.geometries.mesh.MeshPrimitive; import org.apache.sis.maths.ReadOnly; import org.apache.sis.maths.Vector; +import org.apache.sis.maths.Vectors; +import org.apache.sis.referencing.CRS; +import org.apache.sis.util.Utilities; /** @@ -260,15 +268,43 @@ public sealed interface Point extends Primitive * Returns the vector, in the tangent space at this point, whose direction determines the * geodesic curve reaching the given position and whose length is the distance to it. * + * <p>In a reference system without ellipsoidal or spherical axes, the geodesic curve is the straight + * line, so the returned vector is the difference between the coordinates of the given position and + * the coordinates of this point.</p> + * * @param toPoint position to reach from this point. * @return vector from this point to the given position. + * @throws MismatchedDimensionException if the given position does not have the dimension of this point. + * @throws IllegalArgumentException if the given position is in another reference system. + * @throws UnsupportedOperationException if the reference system of this point has ellipsoidal or + * spherical axes, for which the geodesic curves are not yet supported. * * @see ISO 19107:2019 - 6.4.13.4 */ @UML(identifier="vectorToPoint", specification=ISO_19107) default Vector<?> vectorToPoint(DirectPosition toPoint) { - //TODO - throw new UnsupportedOperationException(); + final CoordinateReferenceSystem crs = getCoordinateReferenceSystem(); + for (final SingleCRS component : CRS.getSingleComponents(crs)) { + final CoordinateSystem cs = component.getCoordinateSystem(); + if (cs instanceof EllipsoidalCS || cs instanceof SphericalCS) { + throw new UnsupportedOperationException("Geodesic curves in " + component.getName() + " are not supported yet."); + } + } + final CoordinateReferenceSystem other = toPoint.getCoordinateReferenceSystem(); + if (other != null && !Utilities.equalsIgnoreMetadata(crs, other)) { + throw new IllegalArgumentException("The position is not in the reference system of this point."); + } + final Vector<?> position = getPosition(); + final int dimension = position.getDimension(); + if (toPoint.getDimension() != dimension) { + throw new MismatchedDimensionException("Expected a position of dimension " + dimension + + " but got " + toPoint.getDimension() + '.'); + } + final Vector<?> vector = Vectors.createDouble(dimension); + for (int i = 0; i < dimension; i++) { + vector.set(i, toPoint.getCoordinate(i) - position.get(i)); + } + return vector; } /** diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Srid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Srid.java index c84585611f..1e9fb91359 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Srid.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/Srid.java @@ -85,13 +85,10 @@ final class Srid { * to a two-dimensional one keeps no identifier — neither the combined system nor the * horizontal component extracted back from it carries the code the height was added to.</p> * - * @param crs the system whose identifier to return, or {@code null}. + * @param crs the system whose identifier to return. * @return the spatial reference identifier, or {@link #UNDEFINED} if none. */ static int of(final CoordinateReferenceSystem crs) { - if (crs == null) { - return UNDEFINED; - } int srid = declaredCode(crs); if (srid == UNDEFINED) { final SingleCRS horizontal = CRS.getHorizontalComponent(crs); diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java index 33ac4ffc3b..de6100c825 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownBinary.java @@ -523,14 +523,7 @@ public final class WellKnownBinary { * ordinate following the position ones. */ private boolean writeHeader(final Output out, final int code, final Geometry geometry) { - final CoordinateReferenceSystem crs = geometry.getCoordinateReferenceSystem(); - if (crs == null) { - throw new IllegalArgumentException("Cannot write a " + geometry.getGeometryType() - + " in Well-Known Binary:" - + " it has no coordinate reference system, therefore no known number of dimensions." - + " An empty collection takes one from the factory method which creates it."); - } - final int dimension = crs.getCoordinateSystem().getDimension(); + final int dimension = geometry.getCoordinateReferenceSystem().getCoordinateSystem().getDimension(); final boolean hasM = hasMeasure(geometry); final boolean hasZ; switch (dimension) { diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java index b79fdc1376..d574e8fc29 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/WellKnownText.java @@ -551,13 +551,7 @@ public final class WellKnownText { */ private static boolean appendHeader(final StringBuilder sb, final String keyword, final Geometry geometry) { sb.append(keyword); - final CoordinateReferenceSystem crs = geometry.getCoordinateReferenceSystem(); - if (crs == null) { - throw new IllegalArgumentException("Cannot write a " + keyword + " in Well-Known Text:" - + " it has no coordinate reference system, therefore no known number of dimensions." - + " An empty collection takes one from the factory method which creates it."); - } - final int dimension = crs.getCoordinateSystem().getDimension(); + final int dimension = geometry.getCoordinateReferenceSystem().getCoordinateSystem().getDimension(); final boolean hasM = hasMeasure(geometry); switch (dimension) { case 2: sb.append(hasM ? " M " : " "); break; diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Ray.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Ray.java index 7412d9ff32..acf844e8a7 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Ray.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/curve/Ray.java @@ -165,7 +165,7 @@ public sealed interface Ray extends Curve final ReadOnly.Vector<?> position = getPosition(); final ReadOnly.Vector<?> direction = getDirection(); final int dim = direction.getDimension(); - final BBox bbox = new BBox(dim); + final BBox bbox = new BBox(getCoordinateReferenceSystem()); for (int i=0;i<dim;i++){ double d = direction.get(i); if (d<0){ diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCompoundCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCompoundCurve.java index 5bab80f024..6b55998f98 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCompoundCurve.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCompoundCurve.java @@ -21,7 +21,9 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.Curve; import org.apache.sis.geometries.DataPointsType; import org.apache.sis.geometries.curve.CompoundCurve; +import org.apache.sis.geometries.Geometries; import org.apache.sis.maths.Array; +import org.apache.sis.util.ArgumentChecks; /** @@ -49,7 +51,7 @@ public non-sealed class DefaultCompoundCurve extends AbstractGeometry implements */ public DefaultCompoundCurve(CoordinateReferenceSystem fallbackCRS, Curve... curves) { this.curves = curves; - this.fallbackCRS = fallbackCRS; + this.fallbackCRS = (fallbackCRS != null) ? fallbackCRS : Geometries.getUndefinedCRS(2); } @Override @@ -79,6 +81,7 @@ public non-sealed class DefaultCompoundCurve extends AbstractGeometry implements @Override public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) throws IllegalArgumentException { + ArgumentChecks.ensureNonNull("crs", crs); this.fallbackCRS = crs; for (final Curve c : curves) { c.setCoordinateReferenceSystem(crs); diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEmpty.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEmpty.java index 96c9df9f0b..3beed3926b 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEmpty.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEmpty.java @@ -21,7 +21,9 @@ import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.Empty; import org.apache.sis.geometries.DataPointsType; +import org.apache.sis.geometries.Geometries; import org.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.maths.SampleSystem; /** @@ -38,7 +40,8 @@ public non-sealed class DefaultEmpty extends AbstractGeometry implements Empty { @Override public CoordinateReferenceSystem getCoordinateReferenceSystem() { - return attType.getAttributeSystem(DataPointsType.ATT_POSITION).getCoordinateReferenceSystem(); + final SampleSystem system = attType.getAttributeSystem(DataPointsType.ATT_POSITION); + return Geometries.orUndefinedCRS(system.getCoordinateReferenceSystem(), system.getSize()); } @Override diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultGeometryCollection.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultGeometryCollection.java index 9ab8087dd1..1fbe40671a 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultGeometryCollection.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultGeometryCollection.java @@ -22,6 +22,8 @@ import org.apache.sis.geometries.GeometryCollection; import org.apache.sis.geometries.GeometryFactory; import org.apache.sis.geometries.GeometryType; import org.apache.sis.geometries.Point; +import org.apache.sis.geometries.Geometries; +import org.apache.sis.util.ArgumentChecks; /** @@ -51,7 +53,7 @@ public non-sealed class DefaultGeometryCollection<T extends Geometry> extends Ab public DefaultGeometryCollection(GeometryType elementType, CoordinateReferenceSystem fallbackCRS, T[] geometries) { this.elementType = elementType; this.geometries = geometries; - this.fallbackCRS = fallbackCRS; + this.fallbackCRS = (fallbackCRS != null) ? fallbackCRS : Geometries.getUndefinedCRS(2); } @Override @@ -66,6 +68,7 @@ public non-sealed class DefaultGeometryCollection<T extends Geometry> extends Ab @Override public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) throws IllegalArgumentException { + ArgumentChecks.ensureNonNull("crs", crs); fallbackCRS = crs; for (T c : geometries) { c.setCoordinateReferenceSystem(crs); diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultNurbCurve.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultNurbCurve.java index c235e5f91a..90de4c52ed 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultNurbCurve.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultNurbCurve.java @@ -298,6 +298,7 @@ public non-sealed class DefaultNurbCurve extends AbstractGeometry implements Nur for (final Vector<?> p : controlPoints) { bbox.add(p); } + bbox.setCoordinateReferenceSystem(getCoordinateReferenceSystem()); return bbox; } 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 6c57b652fb..3d66ba5d88 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 @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.List; import javax.measure.Quantity; import org.opengis.geometry.DirectPosition; +import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.BBox; import org.apache.sis.geometries.Curve; import org.apache.sis.geometries.DataPoints; @@ -29,6 +30,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.geometries.Geometries; import org.apache.sis.maths.Array; import org.apache.sis.maths.NDArrays; import org.apache.sis.maths.Vector; @@ -299,6 +301,15 @@ public non-sealed class DefaultNurbSurface extends AbstractGeometry implements N } + /** + * Returns the reference system of the control points, or an undefined one if they have none. + */ + @Override + public CoordinateReferenceSystem getCoordinateReferenceSystem() { + final Vector<?> first = controlPoints[0][0]; + return Geometries.orUndefinedCRS(first.getCoordinateReferenceSystem(), first.getDimension()); + } + /** * Bounding box of the control points, which contains the surface (convex hull property). * @@ -311,6 +322,7 @@ public non-sealed class DefaultNurbSurface extends AbstractGeometry implements N bbox.add(p); } } + bbox.setCoordinateReferenceSystem(getCoordinateReferenceSystem()); return bbox; } 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 c92ef3f4da..38b2d9ae4f 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 @@ -132,11 +132,11 @@ public non-sealed class DefaultOffsetCurve extends AbstractGeometry implements O return null; } final var envelope = new GeneralEnvelope(baseEnvelope); - final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem(); + final CoordinateReferenceSystem crs = baseCurve.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; + final Unit<?> axisUnit = crs.getCoordinateSystem().getAxis(i).getUnit(); if (axisUnit != null && !Units.UNITY.equals(distance.getUnit())) { try { d = distance.getUnit().getConverterToAny(axisUnit).convert(value); diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedralSurface.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedralSurface.java index dde10dad89..2834bae517 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedralSurface.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedralSurface.java @@ -22,6 +22,8 @@ import org.apache.sis.geometries.DataPointsType; import org.apache.sis.geometries.Geometry; import org.apache.sis.geometries.surface.Polygon; import org.apache.sis.geometries.surface.PolyhedralSurface; +import org.apache.sis.geometries.Geometries; +import org.apache.sis.util.ArgumentChecks; /** @@ -38,7 +40,7 @@ public non-sealed class DefaultPolyhedralSurface<T extends Polygon> extends Abst * The coordinate reference system to report when this surface has no patch. * Ignored otherwise, in which case the first patch is authoritative. May be {@code null}. */ - private final CoordinateReferenceSystem fallbackCRS; + private CoordinateReferenceSystem fallbackCRS; @SafeVarargs public DefaultPolyhedralSurface(T... patches) { @@ -51,7 +53,7 @@ public non-sealed class DefaultPolyhedralSurface<T extends Polygon> extends Abst */ public DefaultPolyhedralSurface(CoordinateReferenceSystem fallbackCRS, T[] patches) { this.patches = patches; - this.fallbackCRS = fallbackCRS; + this.fallbackCRS = (fallbackCRS != null) ? fallbackCRS : Geometries.getUndefinedCRS(2); } @Override @@ -81,6 +83,8 @@ public non-sealed class DefaultPolyhedralSurface<T extends Polygon> extends Abst @Override public void setCoordinateReferenceSystem(CoordinateReferenceSystem cs) throws IllegalArgumentException { + ArgumentChecks.ensureNonNull("crs", cs); + fallbackCRS = cs; for (final T patch : patches) { patch.setCoordinateReferenceSystem(cs); } diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java index e850cfe4b9..0a8258f40b 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultPolyhedron.java @@ -101,9 +101,5 @@ public non-sealed class DefaultPolyhedron extends AbstractGeometry implements Po return area; } - @Override - public Quantity<?> getVolume() { - throw new UnsupportedOperationException("Not supported yet."); - } } 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 ea00a68ebc..f035562b4a 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 @@ -108,7 +108,7 @@ public non-sealed class DefaultPrism extends AbstractGeometry implements Prism { final double min = extrusionRange.getMinDouble(); final double max = extrusionRange.getMaxDouble(); final CoordinateReferenceSystem baseCRS = base.getCoordinateReferenceSystem(); - if (baseCRS != null && extrusionCrs != null) { + if (extrusionCrs != null) { int offset = 0; for (final SingleCRS component : CRS.getSingleComponents(baseCRS)) { final int dimension = component.getCoordinateSystem().getDimension(); @@ -125,7 +125,7 @@ public non-sealed class DefaultPrism extends AbstractGeometry implements Prism { } final int n = baseEnvelope.getDimension(); final GeneralEnvelope envelope; - if (baseCRS != null && extrusionCrs != null) { + if (extrusionCrs != null) { try { envelope = new GeneralEnvelope(CRS.compound(baseCRS, extrusionCrs)); } catch (FactoryException e) { diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSolid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSolid.java index 4c8b444488..f0c3709098 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSolid.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSolid.java @@ -28,6 +28,7 @@ import org.apache.sis.geometries.GeometryFactory; import org.apache.sis.geometries.GeometryType; import org.apache.sis.geometries.Solid; import org.apache.sis.geometries.Surface; +import org.apache.sis.geometries.Geometries; import org.apache.sis.geometry.GeneralEnvelope; @@ -56,7 +57,7 @@ public non-sealed class DefaultSolid extends AbstractGeometry implements Solid { @Override public CoordinateReferenceSystem getCoordinateReferenceSystem() { - return boundaries.get(0).getCoordinateReferenceSystem(); + return boundaries.isEmpty() ? Geometries.getUndefinedCRS(3) : boundaries.get(0).getCoordinateReferenceSystem(); } @Override diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MultiMeshPrimitive.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MultiMeshPrimitive.java index 2f2a1af339..176ae005d5 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MultiMeshPrimitive.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MultiMeshPrimitive.java @@ -88,6 +88,7 @@ public final class MultiMeshPrimitive<T extends MeshPrimitive> extends AbstractG @Override public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) { + ArgumentChecks.ensureNonNull("crs", crs); for (MeshPrimitive p : primitives) { p.setCoordinateReferenceSystem(crs); } diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ProcessorUtils.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ProcessorUtils.java index 22098cd059..efa2df09a0 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ProcessorUtils.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/ProcessorUtils.java @@ -86,8 +86,6 @@ public final class ProcessorUtils { * @see <a href="https://issues.apache.org/jira/browse/SIS-162">SIS-162</a> */ private static CoordinateReferenceSystem getOrCreateSubCRS(CoordinateReferenceSystem crs, int lower, int upper) { - if (crs == null) return crs; - int dimension = crs.getCoordinateSystem().getDimension(); if (lower < 0 || lower > upper || upper > dimension) { throw new IndexOutOfBoundsException("["+lower+".."+upper+"] range is not possible with crs dimension "+ dimension); diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/simplify/greedyinsert/WTriangle.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/simplify/greedyinsert/WTriangle.java index 7d34e79203..afabf3ac3d 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/simplify/greedyinsert/WTriangle.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/simplify/greedyinsert/WTriangle.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.function.BiFunction; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.DataPoints; -import org.apache.sis.geometries.Geometry; import org.apache.sis.geometries.GeometryFactory; import org.apache.sis.geometries.Point; import org.apache.sis.geometries.DataPointsType; @@ -30,6 +29,7 @@ import org.apache.sis.geometries.internal.shared.AbstractGeometry; import org.apache.sis.geometries.internal.shared.IndexedPoint; import org.apache.sis.geometries.operation.OperationException; import org.apache.sis.geometries.surface.Triangle; +import org.apache.sis.geometries.Geometries; import org.apache.sis.maths.Maths; import org.apache.sis.maths.ReadOnly; import org.apache.sis.maths.Vector; @@ -45,7 +45,7 @@ public final class WTriangle extends AbstractGeometry implements Triangle { private final LinearRing ring = GeometryFactory.DEFAULT.createLinearRing(new DataPoints() { @Override public CoordinateReferenceSystem getCoordinateReferenceSystem() { - return p0.getCoordinateReferenceSystem(); + return Geometries.orUndefinedCRS(p0.getCoordinateReferenceSystem(), p0.getDimension()); } @Override @@ -119,11 +119,6 @@ public final class WTriangle extends AbstractGeometry implements Triangle { return ring; } - @Override - public Geometry getBoundary() { - throw new UnsupportedOperationException("Not supported yet."); - } - private static class Candidate implements Comparable<Candidate>{ private final Vector tuple; diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/OrientedEdge.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/OrientedEdge.java index f9ec9d9b7d..1840eed752 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/OrientedEdge.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/OrientedEdge.java @@ -25,6 +25,7 @@ import org.apache.sis.geometries.curve.LineString; import org.apache.sis.geometries.internal.shared.AbstractGeometry; import org.apache.sis.geometries.mesh.MeshPrimitive; import org.apache.sis.geometries.mesh.MeshPrimitive.Vertex; +import org.apache.sis.geometries.Geometries; import org.apache.sis.maths.Maths; import org.apache.sis.maths.ReadOnly; import org.apache.sis.maths.Vector; @@ -203,7 +204,7 @@ public final class OrientedEdge extends AbstractGeometry implements LineString, @Override public CoordinateReferenceSystem getCoordinateReferenceSystem() { - return structure.start.getCoordinateReferenceSystem(); + return Geometries.orUndefinedCRS(structure.start.getCoordinateReferenceSystem(), structure.start.getDimension()); } @Override diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/OrientedTriangle.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/OrientedTriangle.java index 3bb630ae25..e20ab8dc93 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/OrientedTriangle.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/triangulate/delaunay/OrientedTriangle.java @@ -20,7 +20,6 @@ import java.awt.geom.Point2D; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.DataPoints; -import org.apache.sis.geometries.Geometry; import org.apache.sis.geometries.GeometryFactory; import org.apache.sis.geometries.Point; import org.apache.sis.geometries.DataPointsType; @@ -30,6 +29,7 @@ import org.apache.sis.geometries.mesh.MeshPrimitive; import org.apache.sis.geometries.mesh.MeshPrimitive.Vertex; import org.apache.sis.geometries.operation.OperationException; import org.apache.sis.geometries.surface.Triangle; +import org.apache.sis.geometries.Geometries; import org.apache.sis.maths.ReadOnly; import org.apache.sis.maths.Vector; import org.apache.sis.referencing.internal.shared.ShapeUtilities; @@ -232,7 +232,7 @@ public final class OrientedTriangle extends AbstractGeometry implements Triangle @Override public CoordinateReferenceSystem getCoordinateReferenceSystem() { - return a.getCoordinateReferenceSystem(); + return Geometries.orUndefinedCRS(a.getCoordinateReferenceSystem(), a.getDimension()); } @Override @@ -255,11 +255,6 @@ public final class OrientedTriangle extends AbstractGeometry implements Triangle return DataPoints.super.getEnvelope(); } - @Override - public Geometry getBoundary() { - throw new UnsupportedOperationException("Not supported yet."); - } - @Override public DataPointsType getType() { return DataPointsType.EMPTY; diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java index ff9686abb9..62219843b1 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/Polyhedron.java @@ -17,16 +17,21 @@ package org.apache.sis.geometries.solid; import java.util.List; +import javax.measure.Quantity; import org.apache.sis.geometries.DataPoints; import org.apache.sis.geometries.DataPointsType; +import org.apache.sis.geometries.Geometries; import org.apache.sis.geometries.GeometryCollection; import org.apache.sis.geometries.GeometryType; import org.apache.sis.geometries.Solid; import org.apache.sis.geometries.SolidInterpolation; +import org.apache.sis.geometries.curve.LinearRing; import org.apache.sis.geometries.internal.shared.ConcatenatedDataPoints; import org.apache.sis.geometries.internal.shared.DefaultPolyhedron; import org.apache.sis.geometries.solid.polyhedron.AbstractPolyhedron; import org.apache.sis.geometries.surface.Polygon; +import org.apache.sis.maths.Vector; +import org.apache.sis.measure.Quantities; /** @@ -80,6 +85,89 @@ public sealed interface Polyhedron extends Solid return SolidInterpolation.NONE; } + /** + * Returns the volume enclosed by the exterior shell minus the volumes enclosed by the interior shells. + * + * <p>The volume enclosed by a shell is computed with the divergence theorem: each face is split in + * triangles, and the signed volumes of the tetrahedra joining those triangles to a common origin are + * summed. The faces of a shell shall have consistent orientations, either all outward or all inward. + * The holes of a face are subtracted whatever their orientation.</p> + * + * @throws UnsupportedOperationException if this polyhedron is not 3-dimensional. + * + * @see ISO 19107:2019 - 6.4.28.4 + */ + @Override + default Quantity<?> getVolume() { + if (getDimension() != 3) { + throw new UnsupportedOperationException("The volume of a polyhedron is computed in 3 dimensions only."); + } + final GeometryCollection<Polygon> exterior = getExteriorShell(); + double volume = 0; + if (exterior.getNumGeometries() != 0) { + /* + * The volume enclosed by a closed shell does not depend on the origin of the tetrahedra. + * Taking a position of the shell avoids losing precision with large coordinate values. + */ + final double[] origin = exterior.getGeometryN(0).getExteriorRing().getDataPoints().getPosition(0).toArrayDouble(); + volume = Math.abs(signedVolume(exterior, origin)); + for (final GeometryCollection<Polygon> interior : getInteriorShells()) { + volume -= Math.abs(signedVolume(interior, origin)); + } + } + return Quantities.create(volume, Geometries.getVolumeUnit(this)); + } + + /** + * Returns the signed volume enclosed by the given shell, positive if the faces are oriented outward. + */ + private static double signedVolume(final GeometryCollection<Polygon> shell, final double[] origin) { + double volume = 0; + for (int i = 0, n = shell.getNumGeometries(); i < n; i++) { + final Polygon face = shell.getGeometryN(i); + double v = signedVolume(face.getExteriorRing().getDataPoints(), origin); + for (final LinearRing hole : face.getInteriorRings()) { + // A hole lies in the plane of its face, so its contribution has the sign of the face if both have the same orientation. + final double h = signedVolume(hole.getDataPoints(), origin); + v += (Math.signum(h) == Math.signum(v)) ? -h : h; + } + volume += v; + } + return volume; + } + + /** + * Returns the signed volume of the cone joining the given origin to the given planar ring. + * The ring is split in a fan of triangles from its first position, and the volumes of the + * tetrahedra joining the origin to those triangles are summed. + */ + private static double signedVolume(final DataPoints ring, final double[] origin) { + final int n = ring.size(); + if (n < 3) { + return 0; + } + Vector<?> p = ring.getPosition(0); + final double ax = p.get(0) - origin[0]; + final double ay = p.get(1) - origin[1]; + final double az = p.get(2) - origin[2]; + p = ring.getPosition(1); + double bx = p.get(0) - origin[0]; + double by = p.get(1) - origin[1]; + double bz = p.get(2) - origin[2]; + double sum = 0; + for (int i = 2; i < n; i++) { + p = ring.getPosition(i); + final double cx = p.get(0) - origin[0]; + final double cy = p.get(1) - origin[1]; + final double cz = p.get(2) - origin[2]; + sum += ax * (by*cz - bz*cy) + ay * (bz*cx - bx*cz) + az * (bx*cy - by*cx); + bx = cx; + by = cy; + bz = cz; + } + return sum / 6; + } + /** * Returns the exterior shell of this polyhedron. * diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Capsule.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Capsule.java index edc2769956..31407662c5 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Capsule.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Capsule.java @@ -147,7 +147,7 @@ public sealed interface Capsule extends Surface final double radiusBottom = getRadiusBottom(); final double radiusTop = getRadiusTop(); final int dim = getDataPoints().getDimension(); - final BBox bbox = new BBox(dim); + final BBox bbox = new BBox(getCoordinateReferenceSystem()); for (int i=0;i<dim;i++){ final double b = bottom.get(i); final double t = top.get(i); 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 08f6f758d2..15ade1d337 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 @@ -204,7 +204,7 @@ public sealed interface Cylinder extends Surface final Vector<?> axis = getAxis(); final double height = axis.length(); final int dim = getDataPoints().getDimension(); - final BBox bbox = new BBox(dim); + final BBox bbox = new BBox(getCoordinateReferenceSystem()); for (int i=0;i<dim;i++){ final double b = bottom.get(i); final double t = top.get(i); diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java index 30baca299e..e11c6b75a9 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Triangle.java @@ -26,6 +26,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.BBox; import org.apache.sis.geometries.DataPoints; import org.apache.sis.geometries.Geometries; +import org.apache.sis.geometries.Geometry; import org.apache.sis.geometries.GeometryType; import org.apache.sis.geometries.Point; import org.apache.sis.geometries.DataPointsType; @@ -114,6 +115,15 @@ public sealed interface Triangle extends Polygon getExteriorRing().setCoordinateReferenceSystem(cs); } + /** + * Returns the {@linkplain #getExteriorRing() exterior ring}: a triangle has no hole, + * so the ring through its three corners is its whole boundary. + */ + @Override + default Geometry getBoundary() { + return getExteriorRing(); + } + @Override default Envelope getEnvelope() { final DataPoints exterior = getExteriorRing().getDataPoints();
