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 400f9bd950bcd1835ec3369ed85d0efc6ee086c3
Author: jsorel <[email protected]>
AuthorDate: Thu Oct 15 10:05:51 2026 +0200

    feat(Geometry): add GeometryEnvelope to work with Vector instead of 
DirectPosition, simplifying algorithms
---
 .../main/org/apache/sis/geometries/BBox.java       |  34 +---
 .../main/org/apache/sis/geometries/Geometries.java |   6 +-
 .../internal/shared/AbstractGeometry.java          |  41 ++---
 .../internal/shared/DefaultArcByCenterPoint.java   |  27 ++-
 .../internal/shared/DefaultBSplineCurve.java       |   2 +-
 .../internal/shared/DefaultBSplineSolid.java       |   6 +-
 .../internal/shared/DefaultBSplineSurface.java     |   6 +-
 .../internal/shared/DefaultBilinearGrid.java       |   4 +-
 .../geometries/internal/shared/DefaultConic.java   |   7 +-
 .../internal/shared/DefaultEllipsoid.java          |   4 +-
 .../internal/shared/DefaultNurbSurface.java        |   5 +-
 .../geometries/internal/shared/DefaultSphere.java  |   4 +-
 .../internal/shared/GeometryEnvelope.java          | 188 +++++++++++++++++++++
 .../sis/geometries/solid/ParametricCurveSolid.java |   4 +-
 .../geometries/surface/ParametricCurveSurface.java |   3 +-
 .../internal/shared/GeometryEnvelopeTest.java      |  92 ++++++++++
 16 files changed, 336 insertions(+), 97 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 873d348f15..63cee96c84 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
@@ -19,12 +19,11 @@ package org.apache.sis.geometries;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
-import org.opengis.coordinate.MismatchedDimensionException;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.geometries.internal.shared.GeometryEnvelope;
 import org.apache.sis.geometries.internal.shared.IndexedPoint;
 import org.apache.sis.geometries.surface.Polygon;
-import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
 import org.apache.sis.maths.ReadOnly;
@@ -39,7 +38,7 @@ import org.apache.sis.maths.Vector;
  *
  * @author Johann Sorel (Geomatys)
  */
-public final class BBox extends GeneralEnvelope implements Geometry {
+public final class BBox extends GeometryEnvelope implements Geometry {
 
     private Map<String,Object> properties;
 
@@ -63,8 +62,8 @@ public final class BBox extends GeneralEnvelope implements 
Geometry {
      * @param lower lower corner
      * @param upper upper corner
      */
-    public BBox(Vector lower, Vector upper) {
-        super(Vectors.asDirectPostion(lower), Vectors.asDirectPostion(upper));
+    public BBox(ReadOnly.Vector<?> lower, ReadOnly.Vector<?> upper) {
+        super(lower, upper);
         ensureCRS();
     }
 
@@ -125,24 +124,6 @@ public final class BBox extends GeneralEnvelope implements 
Geometry {
         return isEmpty() ? -1 : getDimension();
     }
 
-    public void add(Vector<?> position) throws MismatchedDimensionException {
-        add(Vectors.asDirectPostion(position));
-    }
-
-    /**
-     * {@inheritDoc }
-     */
-    public Vector<?> getLower() {
-        return Vectors.castOrWrap(super.getLowerCorner());
-    }
-
-    /**
-     * {@inheritDoc }
-     */
-    public Vector<?> getUpper() {
-        return Vectors.castOrWrap(super.getUpperCorner());
-    }
-
     /**
      * {@inheritDoc }
      */
@@ -290,12 +271,7 @@ public final class BBox extends GeneralEnvelope implements 
Geometry {
         @Override
         public Vector<?> getPosition(int index) {
             ensureValid(index);
-            final int dim = getDimension();
-            final Vector<?> position = 
Vectors.create(BBox.this.getCoordinateReferenceSystem(), DataType.DOUBLE);
-            for (int i = 0; i < dim; i++) {
-                position.set(i, (index == 0) ? getMinimum(i) : getMaximum(i));
-            }
-            return position;
+            return (index == 0) ? getLower() : getUpper();
         }
 
         @Override
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 9eb963ebc8..dcb32273f4 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
@@ -48,6 +48,7 @@ import org.opengis.util.InternationalString;
 import org.apache.sis.geometries.adapter.JTSAdapter;
 import org.apache.sis.geometries.adapter.ShapeAdapter;
 import org.apache.sis.geometries.adapter.ShapeConverter;
+import org.apache.sis.geometries.internal.shared.GeometryEnvelope;
 import org.apache.sis.geometries.mesh.MeshPrimitive;
 import org.apache.sis.geometries.mesh.MultiMeshPrimitive;
 import org.apache.sis.geometry.wrapper.jts.JTS;
@@ -262,8 +263,9 @@ public final class Geometries {
 
     public static MeshPrimitive createBox(Envelope geom) {
 
-        final Vector lower = Vectors.castOrCopy(geom.getLowerCorner());
-        final Vector upper = Vectors.castOrCopy(geom.getUpperCorner());
+        final var box = new GeometryEnvelope(geom);
+        final Vector lower = box.getLower();
+        final Vector upper = box.getUpper();
 
         final int dim = lower.getDimension();
 
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java
index af03496c60..80eb2052ce 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java
@@ -16,18 +16,16 @@
  */
 package org.apache.sis.geometries.internal.shared;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import org.opengis.geometry.Envelope;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Geometries;
 import org.apache.sis.geometries.Geometry;
-import org.apache.sis.geometry.GeneralDirectPosition;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
+import org.apache.sis.maths.Vectors;
 
 
 /**
@@ -103,26 +101,25 @@ public abstract non-sealed class AbstractGeometry 
implements Geometry {
      * hull of its control points, so the returned envelope contains it.
      *
      * @param  points         data points of the spline, or {@code null} if 
none.
-     * @param  controlPoints  coordinates of the control points, possibly 
empty.
+     * @param  controlPoints  the control points, or {@code null} if none.
      * @param  homogeneous    whether the control points have one more 
coordinate than the reference system,
      *                        the weight by which the other coordinates are 
divided. Weights are assumed positive.
      * @return envelope of the data points and of the control points, or 
{@code null} if there is none.
      */
-    protected static Envelope controlHullEnvelope(final DataPoints points, 
final List<double[]> controlPoints,
+    protected static Envelope controlHullEnvelope(final DataPoints points, 
final Array controlPoints,
                                                   final boolean homogeneous)
     {
-        GeneralEnvelope envelope = (points == null || points.isEmpty()) ? null 
: new GeneralEnvelope(points.getEnvelope());
-        for (double[] coordinates : controlPoints) {
+        GeometryEnvelope envelope = (points == null || points.isEmpty()) ? 
null : new GeometryEnvelope(points.getEnvelope());
+        final long n = (controlPoints != null) ? controlPoints.getLength() : 0;
+        for (long i = 0; i < n; i++) {
+            Vector<?> position = controlPoints.get(i);
             if (homogeneous) {
-                final double weight = coordinates[coordinates.length - 1];
-                coordinates = Arrays.copyOf(coordinates, coordinates.length - 
1);
-                for (int i = 0; i < coordinates.length; i++) {
-                    coordinates[i] /= weight;
-                }
+                final int dimension = position.getDimension() - 1;
+                final double weight = position.get(dimension);
+                position = 
Vectors.createDouble(dimension).set(position).scale(1 / weight);
             }
-            final var position = new GeneralDirectPosition(coordinates);
             if (envelope == null) {
-                envelope = new GeneralEnvelope(position, position);
+                envelope = new GeometryEnvelope(position, position);
                 if (points != null) {
                     
envelope.setCoordinateReferenceSystem(points.getCoordinateReferenceSystem());
                 }
@@ -133,20 +130,6 @@ public abstract non-sealed class AbstractGeometry 
implements Geometry {
         return envelope;
     }
 
-    /**
-     * Returns the coordinates of the given control points, or an empty list 
if {@code null}.
-     */
-    protected static List<double[]> coordinates(final Array controlPoints) {
-        if (controlPoints == null) {
-            return List.of();
-        }
-        final var coordinates = new ArrayList<double[]>();
-        for (long i = 0, n = controlPoints.getLength(); i < n; i++) {
-            coordinates.add(controlPoints.get(i).toArrayDouble());
-        }
-        return coordinates;
-    }
-
     /**
      * Adds the envelope of the given geometry to the given union, creating it 
if needed.
      * Geometries with no envelope at all (an empty one, typically) are 
skipped.
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByCenterPoint.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByCenterPoint.java
index cc4007e538..eb65f8e921 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByCenterPoint.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByCenterPoint.java
@@ -22,8 +22,8 @@ import org.opengis.geometry.Envelope;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.curve.ArcByCenterPoint;
-import org.apache.sis.geometry.GeneralDirectPosition;
-import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.Vector;
 
 
 /**
@@ -106,21 +106,20 @@ public non-sealed class DefaultArcByCenterPoint extends 
AbstractGeometry impleme
      */
     @Override
     public Envelope getEnvelope() {
-        final var envelope = new GeneralEnvelope(center.getEnvelope());
-        final var position = new 
GeneralDirectPosition(envelope.getLowerCorner());
-        final double cx = position.getCoordinate(0);
-        final double cy = position.getCoordinate(1);
+        // Axes after the first two keep the coordinates of the centre.
+        final Vector<?> position = center.getPosition().copy(DataType.DOUBLE);
+        final double cx = position.get(0);
+        final double cy = position.get(1);
         final double rx = radius(0);
         final double ry = radius(1);
         // The sweep direction does not change the positions covered by the 
arc.
         final double lo = Math.min(startAngle, endAngle);
         final double hi = Math.max(startAngle, endAngle);
-        // The centre is not on the arc: the first two axes start from an end 
point, not from the centre.
+        // The centre is not on the arc: the envelope starts from an end 
point, not from the centre.
         double t = Math.toRadians(lo);
-        final double x = cx + rx * Math.cos(t);
-        final double y = cy + ry * Math.sin(t);
-        envelope.setRange(0, x, x);
-        envelope.setRange(1, y, y);
+        position.set(0, cx + rx * Math.cos(t));
+        position.set(1, cy + ry * Math.sin(t));
+        final var envelope = new GeometryEnvelope(position, position);
         t = Math.toRadians(hi);
         add(envelope, position, cx + rx * Math.cos(t), cy + ry * Math.sin(t));
         // Any sweep of 360° or more contains the four extremes, so the loop 
stops after one turn.
@@ -140,11 +139,11 @@ public non-sealed class DefaultArcByCenterPoint extends 
AbstractGeometry impleme
      * Adds to the envelope the given position, with its first two coordinates 
replaced by the given ones.
      * The other coordinates are those of the centre.
      */
-    private static void add(final GeneralEnvelope envelope, final 
GeneralDirectPosition position,
+    private static void add(final GeometryEnvelope envelope, final Vector<?> 
position,
                             final double x, final double y)
     {
-        position.setCoordinate(0, x);
-        position.setCoordinate(1, y);
+        position.set(0, x);
+        position.set(1, y);
         envelope.add(position);
     }
 
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java
index 97c27f2e9d..0d9eb3be22 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineCurve.java
@@ -179,7 +179,7 @@ public non-sealed class DefaultBSplineCurve extends 
AbstractGeometry implements
      */
     @Override
     public Envelope getEnvelope() {
-        return controlHullEnvelope(points, coordinates(controlPoints), 
rational);
+        return controlHullEnvelope(points, controlPoints, rational);
     }
 
     /**
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java
index 95a6520574..634046920d 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSolid.java
@@ -17,7 +17,6 @@
 package org.apache.sis.geometries.internal.shared;
 
 import java.util.Objects;
-import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.geometries.Curve;
@@ -29,6 +28,7 @@ import org.apache.sis.geometries.GeometryCollection;
 import org.apache.sis.geometries.Surface;
 import org.apache.sis.geometries.solid.BSplineSolid;
 import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
 
 
 /**
@@ -148,7 +148,7 @@ public non-sealed class DefaultBSplineSolid extends 
AbstractGeometry implements
     }
 
     @Override
-    public DirectPosition getSurface(double a, double b, double c) {
+    public Vector<?> getSurface(double a, double b, double c) {
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
@@ -178,7 +178,7 @@ public non-sealed class DefaultBSplineSolid extends 
AbstractGeometry implements
      */
     @Override
     public Envelope getEnvelope() {
-        return controlHullEnvelope(points, coordinates(controlPoints), false);
+        return controlHullEnvelope(points, controlPoints, false);
     }
 
     @Override
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java
index 2db202201e..5666cc63e0 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBSplineSurface.java
@@ -19,7 +19,6 @@ package org.apache.sis.geometries.internal.shared;
 import java.util.List;
 import java.util.Objects;
 import javax.measure.Quantity;
-import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.DataPoints;
@@ -28,6 +27,7 @@ import org.apache.sis.geometries.curve.KnotType;
 import org.apache.sis.geometries.surface.BSplineSurface;
 import org.apache.sis.geometries.surface.BSplineSurfaceForm;
 import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
 
 
 /**
@@ -174,7 +174,7 @@ public non-sealed class DefaultBSplineSurface extends 
AbstractGeometry implement
     }
 
     @Override
-    public DirectPosition getSurface(double u, double v) {
+    public Vector<?> getSurface(double u, double v) {
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
@@ -189,7 +189,7 @@ public non-sealed class DefaultBSplineSurface extends 
AbstractGeometry implement
      */
     @Override
     public Envelope getEnvelope() {
-        return controlHullEnvelope(points, coordinates(controlPoints), 
!polynomial);
+        return controlHullEnvelope(points, controlPoints, !polynomial);
     }
 
     @Override
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java
index f744d08321..a4a18c5b07 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultBilinearGrid.java
@@ -19,7 +19,6 @@ package org.apache.sis.geometries.internal.shared;
 import java.util.List;
 import java.util.Objects;
 import javax.measure.Quantity;
-import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.geometries.Curve;
@@ -29,6 +28,7 @@ import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.geometries.surface.BilinearGrid;
 import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
 
 
 /**
@@ -138,7 +138,7 @@ public non-sealed class DefaultBilinearGrid extends 
AbstractGeometry implements
     }
 
     @Override
-    public DirectPosition getSurface(double u, double v) {
+    public Vector<?> getSurface(double u, double v) {
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConic.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConic.java
index b09ec81925..ae82eb2284 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConic.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConic.java
@@ -23,7 +23,6 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.geometries.curve.Conic;
-import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.maths.Array;
 import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.Vector;
@@ -116,7 +115,7 @@ public non-sealed class DefaultConic extends 
AbstractGeometry implements Conic {
      */
     @Override
     public Envelope getEnvelope() {
-        final var envelope = new GeneralEnvelope(points.getEnvelope());
+        final var envelope = new GeometryEnvelope(points.getEnvelope());
         if (points.isEmpty()) {
             return envelope;
         }
@@ -142,7 +141,7 @@ public non-sealed class DefaultConic extends 
AbstractGeometry implements Conic {
      * Expands the given envelope for containing the extremes along each axis 
of the conic arc
      * going through the five given points. The envelope shall already contain 
those points.
      */
-    private static void addArc(final GeneralEnvelope envelope, final 
Vector<?>[] p) {
+    private static void addArc(final GeometryEnvelope envelope, final 
Vector<?>[] p) {
         final int dimension = p[0].getDimension();
         /*
          * Builds an orthonormal basis (e1, e2) of the plane of the points, 
with the centroid as origin.
@@ -283,7 +282,7 @@ public non-sealed class DefaultConic extends 
AbstractGeometry implements Conic {
                     final Vector<?> position = origin.copy(DataType.DOUBLE)
                             .add(e1.copy(DataType.DOUBLE).scale(scale * xa))
                             .add(e2.copy(DataType.DOUBLE).scale(scale * xb));
-                    envelope.add(Vectors.asDirectPostion(position));
+                    envelope.add(position);
                 }
             }
         }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipsoid.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipsoid.java
index c4bb81d492..8a4992f9a4 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipsoid.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultEllipsoid.java
@@ -19,7 +19,6 @@ package org.apache.sis.geometries.internal.shared;
 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.Curve;
 import org.apache.sis.geometries.DataPoints;
@@ -31,6 +30,7 @@ import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.SampleSystem;
+import org.apache.sis.maths.Vector;
 import org.apache.sis.util.ArgumentChecks;
 
 
@@ -226,7 +226,7 @@ public non-sealed class DefaultEllipsoid extends 
AbstractGeometry implements Ell
     }
 
     @Override
-    public DirectPosition getSurface(double u, double v) {
+    public Vector<?> getSurface(double u, double v) {
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultNurbSurface.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultNurbSurface.java
index 3d66ba5d88..e026be2fb6 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
@@ -20,7 +20,6 @@ import java.util.ArrayList;
 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;
@@ -415,8 +414,8 @@ public non-sealed class DefaultNurbSurface extends 
AbstractGeometry implements N
      * Returns the position of this surface at the given parameters, computed 
by {@link #evaluate(double, double)}.
      */
     @Override
-    public DirectPosition getSurface(double u, double v) {
-        return Vectors.asDirectPostion(evaluate(u, v));
+    public Vector<?> getSurface(double u, double v) {
+        return evaluate(u, v);
     }
 
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSphere.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSphere.java
index a25f66c120..118f9dc4ec 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSphere.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultSphere.java
@@ -17,7 +17,6 @@
 package org.apache.sis.geometries.internal.shared;
 
 import java.util.List;
-import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.DataPoints;
@@ -29,6 +28,7 @@ import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.SampleSystem;
+import org.apache.sis.maths.Vector;
 import org.apache.sis.util.ArgumentChecks;
 
 
@@ -158,7 +158,7 @@ public non-sealed class DefaultSphere extends 
AbstractGeometry implements Sphere
     }
 
     @Override
-    public DirectPosition getSurface(double u, double v) {
+    public Vector<?> getSurface(double u, double v) {
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/GeometryEnvelope.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/GeometryEnvelope.java
new file mode 100644
index 0000000000..db98969084
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/GeometryEnvelope.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.geometries.internal.shared;
+
+import org.opengis.coordinate.MismatchedDimensionException;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.ReadOnly;
+import org.apache.sis.maths.Vector;
+import org.apache.sis.maths.Vectors;
+
+
+/**
+ * An envelope whose positions can be given and obtained as vectors. Each 
method of {@link GeneralEnvelope}
+ * taking a {@link DirectPosition} has an overload taking a {@link 
ReadOnly.Vector}, and each method producing
+ * a {@code DirectPosition} has a counterpart producing a {@link Vector}, so 
that geometry algorithms working
+ * on vectors do not have to convert them.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class GeometryEnvelope extends GeneralEnvelope {
+    /**
+     * Constructs an envelope of the given number of dimensions, with no 
reference system.
+     * All coordinates are initialized to 0.
+     *
+     * @param dimension number of dimensions.
+     */
+    public GeometryEnvelope(final int dimension) {
+        super(dimension);
+    }
+
+    /**
+     * Constructs an envelope in the given reference system.
+     * All coordinates are initialized to 0.
+     *
+     * @param crs reference system of the envelope.
+     */
+    public GeometryEnvelope(final CoordinateReferenceSystem crs) {
+        super(crs);
+    }
+
+    /**
+     * Constructs an envelope with the reference system and the coordinates of 
the given envelope.
+     *
+     * @param envelope the envelope to copy.
+     */
+    public GeometryEnvelope(final Envelope envelope) {
+        super(envelope);
+    }
+
+    /**
+     * Constructs an envelope defined by two corners given as direct positions.
+     *
+     * @param lower lower corner.
+     * @param upper upper corner.
+     * @throws MismatchedDimensionException if the two positions do not have 
the same dimension.
+     */
+    public GeometryEnvelope(final DirectPosition lower, final DirectPosition 
upper) throws MismatchedDimensionException {
+        super(lower, upper);
+    }
+
+    /**
+     * Constructs an envelope defined by two corners given as vectors.
+     * The reference system is the one of the vectors.
+     *
+     * @param lower lower corner.
+     * @param upper upper corner.
+     * @throws MismatchedDimensionException if the two vectors do not have the 
same dimension.
+     */
+    public GeometryEnvelope(final ReadOnly.Vector<?> lower, final 
ReadOnly.Vector<?> upper) throws MismatchedDimensionException {
+        super(asPosition(lower), asPosition(upper));
+    }
+
+    /**
+     * Returns a view of the given vector as a direct position.
+     */
+    private static DirectPosition asPosition(final ReadOnly.Vector<?> vector) {
+        return Vectors.asDirectPostion((vector instanceof Vector<?> v) ? v : 
vector.copy());
+    }
+
+    /**
+     * Adds a position given as a vector to this envelope.
+     *
+     * @param position the position to add.
+     * @throws MismatchedDimensionException if the vector does not have the 
dimension of this envelope.
+     *
+     * @see #add(DirectPosition)
+     */
+    public void add(final ReadOnly.Vector<?> position) throws 
MismatchedDimensionException {
+        add(asPosition(position));
+    }
+
+    /**
+     * Tests whether a position given as a vector is inside this envelope or 
on its border.
+     *
+     * @param position the position to test.
+     * @return whether the position is inside this envelope or on its border.
+     * @throws MismatchedDimensionException if the vector does not have the 
dimension of this envelope.
+     *
+     * @see #contains(DirectPosition)
+     */
+    public boolean contains(final ReadOnly.Vector<?> position) throws 
MismatchedDimensionException {
+        return contains(asPosition(position));
+    }
+
+    /**
+     * Returns a new vector of the dimension and reference system of this 
envelope.
+     */
+    private Vector<?> createVector() {
+        final CoordinateReferenceSystem crs = getCoordinateReferenceSystem();
+        return (crs != null) ? Vectors.create(crs, DataType.DOUBLE) : 
Vectors.createDouble(getDimension());
+    }
+
+    /**
+     * Returns the lower corner of this envelope as a vector.
+     * Changes to the returned vector do not change this envelope.
+     *
+     * @return a copy of the lower corner.
+     *
+     * @see #getLowerCorner()
+     */
+    public Vector<?> getLower() {
+        final Vector<?> corner = createVector();
+        for (int i = 0, n = getDimension(); i < n; i++) {
+            corner.set(i, getLower(i));
+        }
+        return corner;
+    }
+
+    /**
+     * Returns the upper corner of this envelope as a vector.
+     * Changes to the returned vector do not change this envelope.
+     *
+     * @return a copy of the upper corner.
+     *
+     * @see #getUpperCorner()
+     */
+    public Vector<?> getUpper() {
+        final Vector<?> corner = createVector();
+        for (int i = 0, n = getDimension(); i < n; i++) {
+            corner.set(i, getUpper(i));
+        }
+        return corner;
+    }
+
+    /**
+     * Returns the median of this envelope as a vector.
+     * Changes to the returned vector do not change this envelope.
+     *
+     * @return the median position.
+     *
+     * @see #getMedian()
+     */
+    public Vector<?> getCenter() {
+        final Vector<?> center = createVector();
+        for (int i = 0, n = getDimension(); i < n; i++) {
+            center.set(i, getMedian(i));
+        }
+        return center;
+    }
+
+    /**
+     * Returns a copy of this envelope.
+     *
+     * @return a copy of this envelope.
+     */
+    @Override
+    public GeometryEnvelope clone() {
+        return (GeometryEnvelope) super.clone();
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java
index 52e2d5582a..b28569b6a2 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/solid/ParametricCurveSolid.java
@@ -18,13 +18,13 @@ package org.apache.sis.geometries.solid;
 
 import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
-import org.opengis.geometry.DirectPosition;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Solid;
 import org.apache.sis.geometries.SolidInterpolation;
 import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
 
 
 /**
@@ -219,5 +219,5 @@ public sealed interface ParametricCurveSolid extends Solid
      *
      * @see ISO 19107:2019 - 9.3.1
      */
-    DirectPosition getSurface(double a, double b, double c);
+    Vector<?> getSurface(double a, double b, double c);
 }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java
index 0d0bd0e803..444c689a67 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/ParametricCurveSurface.java
@@ -28,6 +28,7 @@ import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Surface;
 import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.maths.Array;
+import org.apache.sis.maths.Vector;
 
 // Specific to the geoapi-4.0 branch:
 import org.opengis.metadata.Identifier;
@@ -232,7 +233,7 @@ public sealed interface ParametricCurveSurface extends 
Surface, ReferenceSystem
      * @see ISO 19107:2019 - 8.3.2.11
      */
     @UML(identifier="surface", specification=ISO_19107)
-    DirectPosition getSurface(double u, double v);
+    Vector<?> getSurface(double u, double v);
 
     /**
      * Returns the name of this surface used as a reference system for its 
parameter space.
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/GeometryEnvelopeTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/GeometryEnvelopeTest.java
new file mode 100644
index 0000000000..7e5ddbb360
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/GeometryEnvelopeTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.geometries.internal.shared;
+
+import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.Vector;
+import org.apache.sis.maths.Vectors;
+
+// Test dependencies
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
+import org.apache.sis.geometries.GeometryTest;
+
+
+/**
+ * Tests {@link GeometryEnvelope}.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class GeometryEnvelopeTest extends GeometryTest {
+
+    private static Vector<?> vector(final double x, final double y) {
+        return Vectors.create(CRS_2D, DataType.DOUBLE).set(new double[] {x, 
y});
+    }
+
+    @Test
+    public void testConstructFromVectors() {
+        final var envelope = new GeometryEnvelope(vector(1, 2), vector(3, 5));
+        assertEquals(CRS_2D, envelope.getCoordinateReferenceSystem());
+        assertEquals(1, envelope.getMinimum(0), TOLERANCE);
+        assertEquals(2, envelope.getMinimum(1), TOLERANCE);
+        assertEquals(3, envelope.getMaximum(0), TOLERANCE);
+        assertEquals(5, envelope.getMaximum(1), TOLERANCE);
+    }
+
+    @Test
+    public void testAddAndContains() {
+        final Vector<?> position = vector(1, 2);
+        final var envelope = new GeometryEnvelope(position, position);
+        envelope.add(vector(-4, 7));
+        envelope.add(Vectors.unmodifiable(vector(3, 0)));
+        assertArrayEquals(new double[] {-4, 0}, 
envelope.getLower().toArrayDouble(), TOLERANCE);
+        assertArrayEquals(new double[] { 3, 7}, 
envelope.getUpper().toArrayDouble(), TOLERANCE);
+        assertTrue (envelope.contains(vector(0, 5)));
+        assertTrue (envelope.contains(vector(3, 7)));
+        assertFalse(envelope.contains(vector(4, 5)));
+    }
+
+    @Test
+    public void testCorners() {
+        final var envelope = new GeometryEnvelope(vector(1, 2), vector(3, 6));
+        final Vector<?> lower  = envelope.getLower();
+        final Vector<?> upper  = envelope.getUpper();
+        final Vector<?> center = envelope.getCenter();
+        assertEquals(CRS_2D, lower.getCoordinateReferenceSystem());
+        assertArrayEquals(new double[] {1, 2}, lower .toArrayDouble(), 
TOLERANCE);
+        assertArrayEquals(new double[] {3, 6}, upper .toArrayDouble(), 
TOLERANCE);
+        assertArrayEquals(new double[] {2, 4}, center.toArrayDouble(), 
TOLERANCE);
+        // The returned vectors are copies.
+        lower.set(0, -100);
+        assertEquals(1, envelope.getMinimum(0), TOLERANCE);
+    }
+
+    @Test
+    public void testNoCRS() {
+        final var envelope = new GeometryEnvelope(3);
+        envelope.setEnvelope(0, 0, 0, 1, 2, 3);
+        assertArrayEquals(new double[] {1, 2, 3}, 
envelope.getUpper().toArrayDouble(), TOLERANCE);
+    }
+
+    @Test
+    public void testClone() {
+        final var envelope = new GeometryEnvelope(vector(1, 2), vector(3, 6));
+        final GeometryEnvelope clone = envelope.clone();
+        assertEquals(envelope, clone);
+        assertNotSame(envelope, clone);
+    }
+}

Reply via email to