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

Reply via email to