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 2f4415ce21da12a330d96c8c36ceaa181459cc9a
Author: jsorel <[email protected]>
AuthorDate: Thu Oct 15 16:24:58 2026 +0200

    feat(Math): add NDRange as a new Envelope subclass, remove GeometryEnvelope
---
 .../main/org/apache/sis/geometries/BBox.java       |  53 ++-
 .../main/org/apache/sis/geometries/DataPoints.java |  12 +-
 .../main/org/apache/sis/geometries/Geometries.java |   4 +-
 .../main/org/apache/sis/geometries/HyperPlane.java |   6 +-
 .../main/org/apache/sis/geometries/OBBox.java      |   5 +-
 .../main/org/apache/sis/geometries/Plane.java      |   6 +-
 .../main/org/apache/sis/geometries/Point.java      |   8 +-
 .../apache/sis/geometries/adapter/JTSAdapter.java  |   5 +-
 .../main/org/apache/sis/geometries/curve/Ray.java  |   7 +-
 .../internal/shared/AbstractGeometry.java          |  12 +-
 .../internal/shared/ArrayDataPoints.java           |   4 +-
 .../internal/shared/CurveLinearizer.java           |   5 +-
 .../sis/geometries/internal/shared/Curves.java     |   6 +-
 .../sis/geometries/internal/shared/DefaultArc.java |   5 +-
 .../internal/shared/DefaultArcByBulge.java         |   4 +-
 .../internal/shared/DefaultArcByCenterPoint.java   |   5 +-
 .../internal/shared/DefaultCircularString.java     |   4 +-
 .../geometries/internal/shared/DefaultConic.java   |   5 +-
 .../internal/shared/DefaultConicArc.java           |   3 +-
 .../internal/shared/DefaultNurbCurve.java          |  10 +-
 .../internal/shared/DefaultNurbSurface.java        |  10 +-
 .../internal/shared/GeometryEnvelope.java          | 188 ---------
 .../apache/sis/geometries/mesh/MeshPrimitive.java  |   4 +-
 .../org/apache/sis/geometries/surface/Capsule.java |   7 +-
 .../apache/sis/geometries/surface/Cylinder.java    |   7 +-
 .../apache/sis/geometries/surface/Ellipsoid.java   |  11 +-
 .../org/apache/sis/geometries/surface/Sphere.java  |  15 +-
 .../apache/sis/geometries/surface/Triangle.java    |   9 +-
 .../main/org/apache/sis/maths/NDArrays.java        |  10 +-
 .../main/org/apache/sis/maths/NDRange.java         | 421 +++++++++++++++++++++
 .../org/apache/sis/geometries/DataPointsTest.java  |   5 +-
 .../internal/shared/GeometryEnvelopeTest.java      |  92 -----
 .../test/org/apache/sis/maths/NDRangeTest.java     | 255 +++++++++++++
 33 files changed, 833 insertions(+), 370 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 63cee96c84..9130229dbe 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,11 +19,13 @@ 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.DirectPosition;
 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;
@@ -38,7 +40,7 @@ import org.apache.sis.maths.Vector;
  *
  * @author Johann Sorel (Geomatys)
  */
-public final class BBox extends GeometryEnvelope implements Geometry {
+public final class BBox extends GeneralEnvelope implements Geometry {
 
     private Map<String,Object> properties;
 
@@ -63,7 +65,7 @@ public final class BBox extends GeometryEnvelope implements 
Geometry {
      * @param upper upper corner
      */
     public BBox(ReadOnly.Vector<?> lower, ReadOnly.Vector<?> upper) {
-        super(lower, upper);
+        super(asPosition(lower), asPosition(upper));
         ensureCRS();
     }
 
@@ -91,6 +93,51 @@ public final class BBox extends GeometryEnvelope implements 
Geometry {
         ensureCRS();
     }
 
+    /**
+     * 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 box.
+     *
+     * @param  position  the position to add.
+     * @throws MismatchedDimensionException if the vector does not have the 
dimension of this box.
+     */
+    public void add(final ReadOnly.Vector<?> position) throws 
MismatchedDimensionException {
+        add(asPosition(position));
+    }
+
+    /**
+     * Returns the lower corner of this box as a vector.
+     * Changes to the returned vector do not change this box.
+     *
+     * @return a copy of the lower corner.
+     */
+    public Vector<?> getLower() {
+        final Vector<?> corner = 
Vectors.create(getCoordinateReferenceSystem(), DataType.DOUBLE);
+        for (int i = 0, n = getDimension(); i < n; i++) {
+            corner.set(i, getLower(i));
+        }
+        return corner;
+    }
+
+    /**
+     * Returns the upper corner of this box as a vector.
+     * Changes to the returned vector do not change this box.
+     *
+     * @return a copy of the upper corner.
+     */
+    public Vector<?> getUpper() {
+        final Vector<?> corner = 
Vectors.create(getCoordinateReferenceSystem(), DataType.DOUBLE);
+        for (int i = 0, n = getDimension(); i < n; i++) {
+            corner.set(i, getUpper(i));
+        }
+        return corner;
+    }
+
     /**
      * Assigns an undefined reference system if this box has none,
      * since the reference system of a geometry is never null.
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/DataPoints.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/DataPoints.java
index 97d283b74f..43ee21e73f 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/DataPoints.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/DataPoints.java
@@ -24,9 +24,11 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.maths.Array;
 import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.SampleSystem;
 import org.apache.sis.maths.Vector;
+import org.apache.sis.maths.Vectors;
 
 
 /**
@@ -281,18 +283,18 @@ public interface DataPoints {
      * Get attribute values range
      *
      * @param name
-     * @return BBox in attribute sample system, or {@code null} if this {@code 
DataPoints} is empty.
+     * @return envelope in the attribute sample system and data type, or 
{@code null} if this {@code DataPoints} is empty.
      */
-    default BBox getAttributeRange(String name) {
+    default NDRange getAttributeRange(String name) {
         if (isEmpty()) {
             return null;
         }
-        final Vector<?> start = getAttribute(0, name);
-        final BBox env = new BBox(start, start);
+        final Vector<?> start = 
Vectors.create(getType().getAttributeSystem(name), 
getType().getAttributeType(name))
+                                       .set(getAttribute(0, name));
+        final NDRange env = new NDRange(start, start);
         for (int i = 1, n = size(); i < n; i++) {
             env.add(getAttribute(i, name));
         }
-        
env.setCoordinateReferenceSystem(getType().getAttributeSystem(name).getCoordinateReferenceSystem());
         return env;
     }
 
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 dcb32273f4..29de0df002 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,13 +48,13 @@ 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;
 import org.apache.sis.maths.Array;
 import org.apache.sis.maths.Matrix3D;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.SampleSystem;
 import org.apache.sis.maths.Vector3D;
 import org.apache.sis.maths.Vectors;
@@ -263,7 +263,7 @@ public final class Geometries {
 
     public static MeshPrimitive createBox(Envelope geom) {
 
-        final var box = new GeometryEnvelope(geom);
+        final var box = new NDRange(geom);
         final Vector lower = box.getLower();
         final Vector upper = box.getUpper();
 
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 72068f6ad6..d39d29c51c 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
@@ -22,8 +22,10 @@ import org.opengis.annotation.UML;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.Vector;
+import org.opengis.geometry.Envelope;
 
 
 /**
@@ -111,10 +113,10 @@ public final class HyperPlane extends AbstractGeometry{
      * its dimensions, the hyperplane spans the whole space on every axis.</p>
      */
     @Override
-    public BBox getEnvelope() {
+    public Envelope getEnvelope() {
         final ReadOnly.Vector<?> position = getPosition();
         final int dim = normal.getDimension();
-        final BBox bbox = new BBox(getCoordinateReferenceSystem());
+        final NDRange bbox = new NDRange(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/OBBox.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/OBBox.java
index 5af435198a..337c2ad803 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/OBBox.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/OBBox.java
@@ -20,6 +20,8 @@ import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.geometries.internal.shared.SinglePositionDataPoints;
+import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vectors;
 import org.apache.sis.maths.Vector;
 
@@ -128,7 +130,8 @@ public final class OBBox extends AbstractGeometry {
         final Vector<?> nxAxis = xAxis.copy().scale(-1);
         final Vector<?> nyAxis = yAxis.copy().scale(-1);
         final Vector<?> nzAxis = zAxis.copy().scale(-1);
-        final BBox env = new BBox(center,center);
+        final Vector<?> start = Vectors.create(getCoordinateReferenceSystem(), 
DataType.DOUBLE).set(center);
+        final NDRange env = new NDRange(start, start);
         final Vector<?> corner = center.copy();
         env.add(corner.set(center).add( xAxis).add( yAxis).add( zAxis));
         env.add(corner.set(center).add( xAxis).add( yAxis).add(nzAxis));
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 da8d2ee557..733491c1cc 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
@@ -22,8 +22,10 @@ import org.opengis.annotation.UML;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.geometries.internal.shared.AbstractGeometry;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.Vector;
+import org.opengis.geometry.Envelope;
 
 
 /**
@@ -172,10 +174,10 @@ public final class Plane extends AbstractGeometry{
      * box is allowed to be larger than the geometry it contains, never 
smaller.</p>
      */
     @Override
-    public BBox getEnvelope() {
+    public Envelope getEnvelope() {
         final ReadOnly.Vector<?> position = getPosition();
         final int dim = normal.getDimension();
-        final BBox bbox = new BBox(getCoordinateReferenceSystem());
+        final NDRange bbox = new NDRange(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 dcb9d731b5..5ae7de3749 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
@@ -32,6 +32,8 @@ import 
org.apache.sis.geometries.internal.shared.ArrayDataPoints;
 import org.apache.sis.geometries.internal.shared.DefaultPoint;
 import org.apache.sis.geometries.internal.shared.IndexedPoint;
 import org.apache.sis.geometries.mesh.MeshPrimitive;
+import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.Vector;
 import org.apache.sis.maths.Vectors;
@@ -258,10 +260,8 @@ public sealed interface Point extends Primitive
 
     @Override
     default Envelope getEnvelope() {
-        final Vector<?> first = getPosition();
-        final BBox env = new BBox(first, first);
-        env.setCoordinateReferenceSystem(getCoordinateReferenceSystem());
-        return env;
+        final Vector<?> position = 
Vectors.create(getCoordinateReferenceSystem(), 
DataType.DOUBLE).set(getPosition());
+        return new NDRange(position, position);
     }
 
     /**
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/JTSAdapter.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/JTSAdapter.java
index 74e0b6acab..9a0d841294 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/JTSAdapter.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/adapter/JTSAdapter.java
@@ -18,7 +18,6 @@ package org.apache.sis.geometries.adapter;
 
 import java.util.ArrayList;
 import java.util.List;
-import org.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.geometries.Geometries;
@@ -36,6 +35,7 @@ import org.apache.sis.maths.Array;
 import org.apache.sis.maths.Cursor;
 import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.SampleSystem;
 import org.apache.sis.maths.Vector;
@@ -43,6 +43,7 @@ import org.apache.sis.maths.Vectors;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.CoordinateSequence;
 import org.locationtech.jts.geom.CoordinateXY;
+import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 
@@ -370,7 +371,7 @@ public final class JTSAdapter {
 
         @Override
         public org.locationtech.jts.geom.Envelope 
expandEnvelope(org.locationtech.jts.geom.Envelope env) {
-            final BBox bbox = 
points.getAttributeRange(DataPointsType.ATT_POSITION);
+            final Envelope bbox = 
points.getAttributeRange(DataPointsType.ATT_POSITION);
             env.expandToInclude(bbox.getMinimum(0), bbox.getMinimum(1));
             env.expandToInclude(bbox.getMaximum(0), bbox.getMaximum(1));
             return env;
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 acf844e8a7..0a38ac50a4 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
@@ -18,7 +18,6 @@ package org.apache.sis.geometries.curve;
 
 import javax.measure.Quantity;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.CurveInterpolation;
 import org.apache.sis.geometries.DataPointsType;
@@ -27,8 +26,10 @@ import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.internal.shared.DefaultRay;
 import org.apache.sis.maths.Array;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.measure.Quantities;
+import org.opengis.geometry.Envelope;
 
 
 /**
@@ -161,11 +162,11 @@ public sealed interface Ray extends Curve
      * Returns the envelope of this ray, infinite on each axis where the 
direction is not zero.
      */
     @Override
-    default BBox getEnvelope() {
+    default Envelope getEnvelope() {
         final ReadOnly.Vector<?> position = getPosition();
         final ReadOnly.Vector<?> direction = getDirection();
         final int dim = direction.getDimension();
-        final BBox bbox = new BBox(getCoordinateReferenceSystem());
+        final NDRange bbox = new NDRange(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/AbstractGeometry.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/AbstractGeometry.java
index 80eb2052ce..6eb87eb6a2 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
@@ -24,6 +24,8 @@ import org.apache.sis.geometries.Geometries;
 import org.apache.sis.geometries.Geometry;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.maths.Array;
+import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vector;
 import org.apache.sis.maths.Vectors;
 
@@ -109,7 +111,7 @@ public abstract non-sealed class AbstractGeometry 
implements Geometry {
     protected static Envelope controlHullEnvelope(final DataPoints points, 
final Array controlPoints,
                                                   final boolean homogeneous)
     {
-        GeometryEnvelope envelope = (points == null || points.isEmpty()) ? 
null : new GeometryEnvelope(points.getEnvelope());
+        NDRange envelope = (points == null || points.isEmpty()) ? null : new 
NDRange(points.getEnvelope());
         final long n = (controlPoints != null) ? controlPoints.getLength() : 0;
         for (long i = 0; i < n; i++) {
             Vector<?> position = controlPoints.get(i);
@@ -119,10 +121,10 @@ public abstract non-sealed class AbstractGeometry 
implements Geometry {
                 position = 
Vectors.createDouble(dimension).set(position).scale(1 / weight);
             }
             if (envelope == null) {
-                envelope = new GeometryEnvelope(position, position);
-                if (points != null) {
-                    
envelope.setCoordinateReferenceSystem(points.getCoordinateReferenceSystem());
-                }
+                final Vector<?> start = (points != null)
+                        ? 
Vectors.create(points.getCoordinateReferenceSystem(), 
DataType.DOUBLE).set(position)
+                        : position.copy(DataType.DOUBLE);
+                envelope = new NDRange(start, start);
             } else {
                 envelope.add(position);
             }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/ArrayDataPoints.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/ArrayDataPoints.java
index 7293639601..de767cfe89 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/ArrayDataPoints.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/ArrayDataPoints.java
@@ -22,7 +22,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.DataPointsType;
@@ -30,6 +29,7 @@ import org.apache.sis.maths.Array;
 import org.apache.sis.maths.Cursor;
 import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.SampleSystem;
 import org.apache.sis.maths.Vector;
@@ -159,7 +159,7 @@ public final class ArrayDataPoints implements DataPoints, 
DataPointsType {
     }
 
     @Override
-    public BBox getAttributeRange(String name) {
+    public NDRange getAttributeRange(String name) {
         return NDArrays.computeRange(attributes.get(name));
     }
 
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/CurveLinearizer.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/CurveLinearizer.java
index ec0a2dbff4..3ef811015a 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/CurveLinearizer.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/CurveLinearizer.java
@@ -33,6 +33,7 @@ import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.curve.LineString;
 import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.SampleSystem;
 import org.apache.sis.maths.Vector;
@@ -312,7 +313,7 @@ public final class CurveLinearizer {
      * Returns the diagonal of the box containing the given pieces, estimated 
from a few positions of each interval.
      */
     private static double extent(final List<Piece> pieces) {
-        GeometryEnvelope box = null;
+        NDRange box = null;
         for (final Piece piece : pieces) {
             final double[] t = piece.parameters();
             for (int i = 0; i < t.length; i++) {
@@ -321,7 +322,7 @@ public final class CurveLinearizer {
                     final ReadOnly.Vector<?> p = (k == 0) ? piece.position(i)
                             : piece.function().apply(t[i] + (t[i-1] - t[i]) * 
k / samples);
                     if (box == null) {
-                        box = new GeometryEnvelope(p, p);
+                        box = new NDRange(p, p);
                     } else {
                         box.add(p);
                     }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Curves.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Curves.java
index d68a9a3a61..1f08ac8141 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Curves.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Curves.java
@@ -21,8 +21,8 @@ import javax.measure.Quantity;
 import javax.measure.Unit;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Point;
-import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.Vector;
 import org.apache.sis.measure.Units;
@@ -121,7 +121,7 @@ public final class Curves {
      * @throws IllegalStateException if the arc is a full circle in more than 
two dimensions,
      *         since its plane is then not determined.
      */
-    public static void addArc(final GeneralEnvelope envelope, final 
ReadOnly.Vector<?> start,
+    public static void addArc(final NDRange envelope, final ReadOnly.Vector<?> 
start,
                               final ReadOnly.Vector<?> middle, final 
ReadOnly.Vector<?> end)
     {
         final int dimension = start.getDimension();
@@ -177,7 +177,7 @@ public final class Curves {
     /**
      * Expands the range of the given envelope axis for containing the given 
value.
      */
-    private static void extend(final GeneralEnvelope envelope, final int axis, 
final double value) {
+    private static void extend(final NDRange envelope, final int axis, final 
double value) {
         envelope.setRange(axis, Math.min(envelope.getMinimum(axis), value),
                                 Math.max(envelope.getMaximum(axis), value));
     }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArc.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArc.java
index 248465d222..50c8dc6708 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArc.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArc.java
@@ -26,6 +26,7 @@ import org.apache.sis.geometries.curve.Arc;
 import org.apache.sis.geometries.curve.LineString;
 import org.apache.sis.maths.Array;
 import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vector;
 
 
@@ -83,11 +84,11 @@ public non-sealed class DefaultArc extends DefaultConic 
implements Arc {
     @Override
     public Envelope getEnvelope() {
         if (points.isEmpty()) {
-            final var envelope = new 
GeometryEnvelope(getCoordinateReferenceSystem());
+            final var envelope = new NDRange(getCoordinateReferenceSystem());
             envelope.setToNaN();
             return envelope;
         }
-        final var envelope = new GeometryEnvelope(points.getEnvelope());
+        final var envelope = new NDRange(points.getEnvelope());
         for (final Vector<?>[] arc : circularArcs()) {
             Curves.addArc(envelope, arc[0], arc[1], arc[2]);
         }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByBulge.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByBulge.java
index cceec44f38..9c5f94369c 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByBulge.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultArcByBulge.java
@@ -22,8 +22,8 @@ import org.opengis.geometry.Envelope;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.curve.ArcByBulge;
 import org.apache.sis.geometries.curve.LineString;
-import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vector;
 
 
@@ -88,7 +88,7 @@ public non-sealed class DefaultArcByBulge extends 
AbstractGeometry implements Ar
      */
     @Override
     public Envelope getEnvelope() {
-        final var envelope = new GeneralEnvelope(points.getEnvelope());
+        final var envelope = new NDRange(points.getEnvelope());
         if (bulge != 0) {
             final Vector<?> start = 
points.getPosition(0).copy(DataType.DOUBLE);
             final Vector<?> end   = 
points.getPosition(1).copy(DataType.DOUBLE);
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 8f1c206938..7383ef599e 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
@@ -27,6 +27,7 @@ import org.apache.sis.geometries.Point;
 import org.apache.sis.geometries.curve.ArcByCenterPoint;
 import org.apache.sis.geometries.curve.LineString;
 import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.Vector;
 
@@ -124,7 +125,7 @@ public non-sealed class DefaultArcByCenterPoint extends 
AbstractGeometry impleme
         double t = Math.toRadians(lo);
         position.set(0, cx + rx * Math.cos(t));
         position.set(1, cy + ry * Math.sin(t));
-        final var envelope = new GeometryEnvelope(position, position);
+        final var envelope = new NDRange(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.
@@ -167,7 +168,7 @@ 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 GeometryEnvelope envelope, final Vector<?> 
position,
+    private static void add(final NDRange envelope, final Vector<?> position,
                             final double x, final double y)
     {
         position.set(0, x);
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircularString.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircularString.java
index f4dd587056..f7bba01cec 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircularString.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultCircularString.java
@@ -23,8 +23,8 @@ import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.GeometryFactory;
 import org.apache.sis.geometries.curve.CircularString;
 import org.apache.sis.geometries.curve.LineString;
-import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vector;
 
 
@@ -66,7 +66,7 @@ public non-sealed class DefaultCircularString extends 
AbstractGeometry implement
      */
     @Override
     public Envelope getEnvelope() {
-        final var envelope = new GeneralEnvelope(points.getEnvelope());
+        final var envelope = new NDRange(points.getEnvelope());
         Vector<?> start = (points.size() != 0) ? 
points.getPosition(0).copy(DataType.DOUBLE) : null;
         for (int i = 2; i < points.size(); i += 2) {
             final Vector<?> end = points.getPosition(i).copy(DataType.DOUBLE);
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 3e10240c0b..5faa08027d 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
@@ -29,6 +29,7 @@ import org.apache.sis.geometries.curve.Conic;
 import org.apache.sis.geometries.curve.LineString;
 import org.apache.sis.maths.Array;
 import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vector;
 
 
@@ -119,11 +120,11 @@ public non-sealed class DefaultConic extends 
AbstractGeometry implements Conic {
     @Override
     public Envelope getEnvelope() {
         if (points.isEmpty()) {
-            final var envelope = new 
GeometryEnvelope(getCoordinateReferenceSystem());
+            final var envelope = new NDRange(getCoordinateReferenceSystem());
             envelope.setToNaN();
             return envelope;
         }
-        final var envelope = new GeometryEnvelope(points.getEnvelope());
+        final var envelope = new NDRange(points.getEnvelope());
         for (final DefaultConicArc arc : arcs()) {
             arc.addExtremes(envelope);
         }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConicArc.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConicArc.java
index 1fdaed1aa6..95e23b70e6 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConicArc.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultConicArc.java
@@ -18,6 +18,7 @@ package org.apache.sis.geometries.internal.shared;
 
 import org.apache.sis.geometries.curve.ConicArc;
 import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vector;
 import org.apache.sis.maths.Vectors;
 
@@ -243,7 +244,7 @@ public final class DefaultConicArc implements ConicArc {
      * Expands the given envelope for containing the extremes of this arc 
along each axis.
      * The envelope shall already contain the data points.
      */
-    void addExtremes(final GeometryEnvelope envelope) {
+    void addExtremes(final NDRange envelope) {
         if (linear) {
             return;
         }
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 c97d14f19e..33fc4d7c7e 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
@@ -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.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.geometries.Geometries;
@@ -29,10 +28,13 @@ import org.apache.sis.geometries.curve.LineString;
 import org.apache.sis.geometries.curve.NurbCurve;
 import org.apache.sis.geometries.operation.DeBoorAlgorithm;
 import org.apache.sis.maths.Array;
+import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vector;
 import org.apache.sis.maths.Vectors;
 import org.apache.sis.measure.Quantities;
+import org.opengis.geometry.Envelope;
 
 
 /**
@@ -311,12 +313,12 @@ public non-sealed class DefaultNurbCurve extends 
AbstractGeometry implements Nur
      * @return the bounding box of this curve
      */
     @Override
-    public BBox getEnvelope() {
-        final BBox bbox = new BBox(controlPoints[0], controlPoints[0]);
+    public Envelope getEnvelope() {
+        final Vector<?> start = Vectors.create(getCoordinateReferenceSystem(), 
DataType.DOUBLE).set(controlPoints[0]);
+        final NDRange bbox = new NDRange(start, start);
         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 e026be2fb6..39c436dd1c 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,7 +21,6 @@ import java.util.Arrays;
 import java.util.List;
 import javax.measure.Quantity;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.Curve;
 import org.apache.sis.geometries.DataPoints;
 import org.apache.sis.geometries.Geometry;
@@ -31,9 +30,12 @@ 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.DataType;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vector;
 import org.apache.sis.maths.Vectors;
+import org.opengis.geometry.Envelope;
 
 
 /**
@@ -314,14 +316,14 @@ public non-sealed class DefaultNurbSurface extends 
AbstractGeometry implements N
      *
      * @return the bounding box of this surface
      */
-    public BBox getEnvelope() {
-        final BBox bbox = new BBox(controlPoints[0][0], controlPoints[0][0]);
+    public Envelope getEnvelope() {
+        final Vector<?> start = Vectors.create(getCoordinateReferenceSystem(), 
DataType.DOUBLE).set(controlPoints[0][0]);
+        final NDRange bbox = new NDRange(start, start);
         for (final Vector<?>[] row : controlPoints) {
             for (final Vector<?> p : row) {
                 bbox.add(p);
             }
         }
-        bbox.setCoordinateReferenceSystem(getCoordinateReferenceSystem());
         return bbox;
     }
 
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
deleted file mode 100644
index db98969084..0000000000
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/GeometryEnvelope.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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/mesh/MeshPrimitive.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MeshPrimitive.java
index f8f4dd92ce..c9b292c11a 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MeshPrimitive.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MeshPrimitive.java
@@ -32,7 +32,6 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.opengis.geometry.Envelope;
 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;
@@ -50,6 +49,7 @@ import org.apache.sis.maths.Cursor;
 import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.Maths;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.SampleSystem;
 import org.apache.sis.maths.Vector;
@@ -1139,7 +1139,7 @@ public sealed interface MeshPrimitive extends Geometry
         }
 
         @Override
-        public BBox getAttributeRange(String name) {
+        public NDRange getAttributeRange(String name) {
             return 
NDArrays.computeRange(NDArrays.subset(primitive.getAttribute(name),index));
         }
     }
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 31407662c5..0b2f4abc9b 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
@@ -20,7 +20,6 @@ import java.util.List;
 import static org.opengis.annotation.Specification.ISO_12113;
 import org.opengis.annotation.UML;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.geometries.Geometry;
 import org.apache.sis.geometries.GeometryFactory;
@@ -29,9 +28,11 @@ import org.apache.sis.geometries.Surface;
 import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.internal.shared.DefaultCapsule;
 import org.apache.sis.maths.DataType;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.Vectors;
 import org.apache.sis.maths.Vector;
+import org.opengis.geometry.Envelope;
 
 
 /**
@@ -141,13 +142,13 @@ public sealed interface Capsule extends Surface
      * by the union of the two boxes bounding its spheres.</p>
      */
     @Override
-    default BBox getEnvelope() {
+    default Envelope getEnvelope() {
         final ReadOnly.Vector<?> bottom = getBottom();
         final ReadOnly.Vector<?> top = getTop();
         final double radiusBottom = getRadiusBottom();
         final double radiusTop = getRadiusTop();
         final int dim = getDataPoints().getDimension();
-        final BBox bbox = new BBox(getCoordinateReferenceSystem());
+        final NDRange bbox = new NDRange(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 15ade1d337..1bdca5a48a 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
@@ -22,7 +22,6 @@ import javax.measure.Quantity;
 import static org.opengis.annotation.Specification.ISO_12113;
 import org.opengis.annotation.UML;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.geometries.Geometries;
 import org.apache.sis.geometries.Geometry;
@@ -34,10 +33,12 @@ import org.apache.sis.geometries.curve.Circle;
 import org.apache.sis.geometries.internal.shared.DefaultCylinder;
 import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.Vectors;
 import org.apache.sis.maths.Vector;
 import org.apache.sis.measure.Quantities;
+import org.opengis.geometry.Envelope;
 
 
 /**
@@ -196,7 +197,7 @@ public sealed interface Cylinder extends Surface
      * has no axis to speak of, and is then bounded by its radii on every 
axis.</p>
      */
     @Override
-    default BBox getEnvelope() {
+    default Envelope getEnvelope() {
         final ReadOnly.Vector<?> bottom = getBottom();
         final ReadOnly.Vector<?> top = getTop();
         final double radiusBottom = getRadiusBottom();
@@ -204,7 +205,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(getCoordinateReferenceSystem());
+        final NDRange bbox = new NDRange(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/Ellipsoid.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Ellipsoid.java
index bdebeedc99..4e59033ccd 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Ellipsoid.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Ellipsoid.java
@@ -21,13 +21,13 @@ import static 
org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.geometries.Geometry;
 import org.apache.sis.geometries.GeometryFactory;
 import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.internal.shared.DefaultEllipsoid;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.Vector;
 
@@ -176,15 +176,12 @@ public sealed interface Ellipsoid extends 
ParametricCurveSurface
      */
     @Override
     default Envelope getEnvelope() {
-        final Vector<?> center = getCenter().copy();
-        final BBox env = new BBox(center, center);
-        env.setCoordinateReferenceSystem(getCoordinateReferenceSystem());
+        final ReadOnly.Vector<?> center = getCenter();
+        final NDRange env = new NDRange(getCoordinateReferenceSystem());
         for (int i = 0, n = getDimension(); i < n; i++) {
             final double c = center.get(i);
             final double a = getSemiAxis(i);
-            if (a > 0) {
-                env.setRange(i, c-a, c+a);
-            }
+            env.setRange(i, c-a, c+a);
         }
         return env;
     }
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Sphere.java
 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Sphere.java
index e070990a28..5a2f8334f4 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Sphere.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/surface/Sphere.java
@@ -22,7 +22,6 @@ import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.DataPointsType;
 import org.apache.sis.geometries.Geometries;
 import org.apache.sis.geometries.Geometry;
@@ -30,6 +29,7 @@ import org.apache.sis.geometries.GeometryFactory;
 import org.apache.sis.geometries.GeometryType;
 import org.apache.sis.geometries.SurfaceInterpolation;
 import org.apache.sis.geometries.internal.shared.DefaultSphere;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.ReadOnly;
 import org.apache.sis.maths.Vector;
 import org.apache.sis.measure.Quantities;
@@ -190,15 +190,12 @@ public sealed interface Sphere extends 
ParametricCurveSurface
      */
     @Override
     default Envelope getEnvelope() {
-        final Vector<?> center = getCenter().copy();
+        final ReadOnly.Vector<?> center = getCenter();
         final double radius = getRadius();
-        final BBox env = new BBox(center, center);
-        env.setCoordinateReferenceSystem(getCoordinateReferenceSystem());
-        if (radius > 0) {
-            for (int i = 0, n = getDimension(); i < n; i++) {
-                double c = center.get(i);
-                env.setRange(i, c-radius, c+radius);
-            }
+        final NDRange env = new NDRange(getCoordinateReferenceSystem());
+        for (int i = 0, n = getDimension(); i < n; i++) {
+            final double c = center.get(i);
+            env.setRange(i, c-radius, c+radius);
         }
         return env;
     }
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 e11c6b75a9..fffbf44c9c 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
@@ -23,7 +23,6 @@ import static org.opengis.annotation.Specification.ISO_19107;
 import org.opengis.annotation.UML;
 import org.opengis.geometry.Envelope;
 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;
@@ -35,10 +34,13 @@ import org.apache.sis.geometries.internal.shared.Curves;
 import org.apache.sis.geometries.internal.shared.DefaultTriangle;
 import org.apache.sis.geometries.operation.simplify.greedyinsert.WTriangle;
 import 
org.apache.sis.geometries.operation.triangulate.delaunay.OrientedTriangle;
+import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.Maths;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vector;
 import org.apache.sis.maths.Vector2D;
 import org.apache.sis.maths.Vector3D;
+import org.apache.sis.maths.Vectors;
 import org.apache.sis.measure.Quantities;
 
 
@@ -127,11 +129,10 @@ public sealed interface Triangle extends Polygon
     @Override
     default Envelope getEnvelope() {
         final DataPoints exterior = getExteriorRing().getDataPoints();
-        final Vector<?> first = exterior.getPosition(0);
-        final BBox env = new BBox(first, first);
+        final Vector<?> first = Vectors.create(getCoordinateReferenceSystem(), 
DataType.DOUBLE).set(exterior.getPosition(0));
+        final NDRange env = new NDRange(first, first);
         env.add(exterior.getPosition(1));
         env.add(exterior.getPosition(2));
-        env.setCoordinateReferenceSystem(getCoordinateReferenceSystem());
         return env;
     }
 
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/maths/NDArrays.java 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/maths/NDArrays.java
index 93508e1cde..1a05da025a 100644
--- 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/maths/NDArrays.java
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/maths/NDArrays.java
@@ -22,10 +22,10 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.Random;
 import java.util.concurrent.RecursiveAction;
+import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.util.FactoryException;
 import org.apache.sis.coverage.SampleDimension;
-import org.apache.sis.geometries.BBox;
 import org.apache.sis.geometries.Geometries;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.referencing.CRS;
@@ -200,7 +200,7 @@ public final class NDArrays {
      * @param array to compute bbox from
      * @return vectors with the same crs as the array, lower and upper bounds.
      */
-    public static BBox computeRange(Array array) {
+    public static NDRange computeRange(Array array) {
 
         final int dim = array.getDimension();
         final double[] min = new double[dim];
@@ -227,7 +227,7 @@ public final class NDArrays {
         }
 
         final CoordinateReferenceSystem crs = 
array.getCoordinateReferenceSystem();
-        final BBox bbox = crs == null ? new BBox(array.getDimension()) : new 
BBox(crs);
+        final NDRange bbox = crs == null ? new NDRange(array.getDimension()) : 
new NDRange(crs);
         for (i = 0; i < dim; i++) {
             bbox.setRange(i, min[i], max[i]);
         }
@@ -255,7 +255,7 @@ public final class NDArrays {
             if (array.isEmpty()) {
                 dt = DataType.UBYTE;
             } else {
-                BBox range = computeRange(array);
+                NDRange range = computeRange(array);
                 dt = bestIntegerDataType(range);
             }
             if (dataType != dt) {
@@ -500,7 +500,7 @@ public final class NDArrays {
     }
 
 
-    private static DataType bestIntegerDataType(BBox box) {
+    private static DataType bestIntegerDataType(Envelope box) {
         double min = box.getMinimum(0);
         double max = box.getMaximum(0);
         for (int i = 1, n = box.getDimension(); i < n; i++) {
diff --git 
a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/maths/NDRange.java 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/maths/NDRange.java
new file mode 100644
index 0000000000..9293d42f9a
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/maths/NDRange.java
@@ -0,0 +1,421 @@
+/*
+ * 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.maths;
+
+import java.util.Objects;
+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.referencing.CRS;
+import org.apache.sis.util.ArgumentChecks;
+
+
+/**
+ * A range of values in <var>n</var> dimensions, defined by a lower and an 
upper bound in each dimension.
+ * The bounds are stored as two {@link Vector}s, which keep their data type 
and their sample system.
+ *
+ * <p>Unlike {@link org.apache.sis.geometry.GeneralEnvelope}, this range does 
not wrap around: in each dimension,
+ * the lower bound is less than or equal to the upper bound. A NaN bound makes 
the range empty in its dimension.</p>
+ *
+ * <p>The range can be expanded with {@link #add(ReadOnly.Vector)}. Otherwise 
its bounds are not shared:
+ * the bounds given to the constructor are copied, and the bounds returned by 
the methods are copies.</p>
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class NDRange implements Envelope {
+    /**
+     * Lower and upper bounds, of the same dimension and sample system.
+     */
+    private final Vector<?> lower, upper;
+
+    /**
+     * Creates a range between the given bounds.
+     *
+     * @param  lower  lower bound in each dimension.
+     * @param  upper  upper bound in each dimension.
+     * @throws MismatchedDimensionException if the two bounds do not have the 
same dimension.
+     * @throws IllegalArgumentException if the two bounds are not in the same 
reference system,
+     *         or if a lower bound is greater than the upper bound.
+     */
+    public NDRange(final ReadOnly.Vector<?> lower, final ReadOnly.Vector<?> 
upper) {
+        ArgumentChecks.ensureNonNull("lower", lower);
+        ArgumentChecks.ensureNonNull("upper", upper);
+        final int dimension = lower.getDimension();
+        if (upper.getDimension() != dimension) {
+            throw new MismatchedDimensionException("The lower bound has " + 
dimension
+                    + " dimensions but the upper bound has " + 
upper.getDimension() + '.');
+        }
+        if (!CRS.equivalent(lower.getCoordinateReferenceSystem(), 
upper.getCoordinateReferenceSystem())) {
+            throw new IllegalArgumentException("The lower and upper bounds are 
in different reference systems.");
+        }
+        for (int i = 0; i < dimension; i++) {
+            if (lower.get(i) > upper.get(i)) {
+                throw new IllegalArgumentException("The lower bound " + 
lower.get(i) + " is greater than the upper bound "
+                        + upper.get(i) + " in dimension " + i + '.');
+            }
+        }
+        this.lower = lower.copy();
+        this.upper = upper.copy();
+    }
+
+    /**
+     * Creates a range of the given number of dimensions, with bounds of type 
{@code DOUBLE} all set to 0.
+     *
+     * @param  dimension  number of dimensions, greater than zero.
+     */
+    public NDRange(final int dimension) {
+        this(dimension, DataType.DOUBLE);
+    }
+
+    /**
+     * Creates a range of the given number of dimensions, with bounds of the 
given type all set to 0.
+     *
+     * @param  dimension  number of dimensions, greater than zero.
+     * @param  dataType   data type of the bounds.
+     */
+    public NDRange(final int dimension, final DataType dataType) {
+        this(Vectors.create(positive(dimension), 
Objects.requireNonNull(dataType, "dataType")));
+    }
+
+    /**
+     * Creates a range in the given reference system, with bounds of type 
{@code DOUBLE} all set to 0.
+     *
+     * @param  crs  reference system of the bounds.
+     */
+    public NDRange(final CoordinateReferenceSystem crs) {
+        this(crs, DataType.DOUBLE);
+    }
+
+    /**
+     * Creates a range in the given reference system, with bounds of the given 
type all set to 0.
+     *
+     * @param  crs       reference system of the bounds.
+     * @param  dataType  data type of the bounds.
+     */
+    public NDRange(final CoordinateReferenceSystem crs, final DataType 
dataType) {
+        this(Vectors.create(Objects.requireNonNull(crs, "crs"), 
Objects.requireNonNull(dataType, "dataType")));
+    }
+
+    /**
+     * Creates a range in the given sample system, with bounds of type {@code 
DOUBLE} all set to 0.
+     *
+     * @param  system  sample system of the bounds.
+     */
+    public NDRange(final SampleSystem system) {
+        this(system, DataType.DOUBLE);
+    }
+
+    /**
+     * Creates a range in the given sample system, with bounds of the given 
type all set to 0.
+     *
+     * @param  system    sample system of the bounds.
+     * @param  dataType  data type of the bounds.
+     */
+    public NDRange(final SampleSystem system, final DataType dataType) {
+        this(Vectors.create(Objects.requireNonNull(system, "system"), 
Objects.requireNonNull(dataType, "dataType")));
+    }
+
+    /**
+     * Returns the given number of dimensions after verifying that it is 
positive.
+     */
+    private static int positive(final int dimension) {
+        ArgumentChecks.ensureStrictlyPositive("dimension", dimension);
+        return dimension;
+    }
+
+    /**
+     * Creates a range whose two bounds are the given vector, which is used 
directly for the lower bound.
+     */
+    private NDRange(final Vector<?> bound) {
+        lower = bound;
+        upper = bound.copy();
+    }
+
+    /**
+     * Creates a range with the bounds of the given envelope, in double 
precision.
+     * An envelope spanning the anti-meridian gives the whole range of its 
axis in that dimension.
+     *
+     * @param  envelope  the envelope to copy.
+     */
+    public NDRange(final Envelope envelope) {
+        final CoordinateReferenceSystem crs = 
envelope.getCoordinateReferenceSystem();
+        final int dimension = envelope.getDimension();
+        lower = (crs != null) ? Vectors.create(crs, DataType.DOUBLE) : 
Vectors.createDouble(dimension);
+        upper = lower.copy();
+        for (int i = 0; i < dimension; i++) {
+            lower.set(i, envelope.getMinimum(i));
+            upper.set(i, envelope.getMaximum(i));
+        }
+    }
+
+    /**
+     * Returns the reference system of the bounds, or {@code null} if 
unspecified.
+     */
+    @Override
+    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+        return lower.getCoordinateReferenceSystem();
+    }
+
+    /**
+     * Returns the number of dimensions of this range.
+     */
+    @Override
+    public int getDimension() {
+        return lower.getDimension();
+    }
+
+    /**
+     * Returns a copy of the lower bound.
+     *
+     * @return the lower bound in each dimension.
+     */
+    public Vector<?> getLower() {
+        return lower.copy();
+    }
+
+    /**
+     * Returns a copy of the upper bound.
+     *
+     * @return the upper bound in each dimension.
+     */
+    public Vector<?> getUpper() {
+        return upper.copy();
+    }
+
+    /**
+     * Returns a copy of the lower bound as a direct position.
+     */
+    @Override
+    public DirectPosition getLowerCorner() {
+        return Vectors.asDirectPostion(lower.copy());
+    }
+
+    /**
+     * Returns a copy of the upper bound as a direct position.
+     */
+    @Override
+    public DirectPosition getUpperCorner() {
+        return Vectors.asDirectPostion(upper.copy());
+    }
+
+    @Override
+    public double getMinimum(final int dimension) {
+        return lower.get(dimension);
+    }
+
+    @Override
+    public double getMaximum(final int dimension) {
+        return upper.get(dimension);
+    }
+
+    @Override
+    public double getMedian(final int dimension) {
+        return (lower.get(dimension) + upper.get(dimension)) / 2;
+    }
+
+    @Override
+    public double getSpan(final int dimension) {
+        return upper.get(dimension) - lower.get(dimension);
+    }
+
+    /**
+     * Sets the range in the given dimension. The bounds keep their data type: 
a value which cannot be
+     * represented exactly is rounded outward, toward negative infinity for 
the lower bound and positive
+     * infinity for the upper bound, so that the range contains the given 
values.
+     *
+     * @param  dimension  the dimension to set.
+     * @param  min        the lower bound, less than or equal to the upper 
bound, or NaN for an empty range.
+     * @param  max        the upper bound, greater than or equal to the lower 
bound, or NaN for an empty range.
+     * @throws IndexOutOfBoundsException if the dimension is not valid.
+     * @throws IllegalArgumentException if the lower bound is greater than the 
upper bound.
+     */
+    public void setRange(final int dimension, final double min, final double 
max) {
+        Objects.checkIndex(dimension, lower.getDimension());
+        if (min > max) {
+            throw new IllegalArgumentException("The lower bound " + min + " is 
greater than the upper bound " + max + '.');
+        }
+        widen(lower, dimension, min, false);
+        widen(upper, dimension, max, true);
+    }
+
+    /**
+     * Sets all bounds to NaN, which makes this range empty in all dimensions.
+     * NaN is representable only by the {@code FLOAT} and {@code DOUBLE} data 
types:
+     * integer types store 0 instead.
+     */
+    public void setToNaN() {
+        for (int i = 0; i < lower.getDimension(); i++) {
+            lower.set(i, Double.NaN);
+            upper.set(i, Double.NaN);
+        }
+    }
+
+    /**
+     * Expands this range for containing the given position.
+     * A NaN coordinate is ignored, and a NaN bound stays NaN, as in {@link 
org.apache.sis.geometry.GeneralEnvelope}.
+     * The bounds keep their data type: they are widened to the nearest 
integer, or to the nearest {@code float},
+     * when the data type cannot represent the coordinate exactly, so that the 
position is contained.
+     *
+     * @param  position  the position to add.
+     * @throws MismatchedDimensionException if the position does not have the 
dimension of this range.
+     *
+     * @see #add(DirectPosition)
+     */
+    public void add(final ReadOnly.Vector<?> position) throws 
MismatchedDimensionException {
+        ensureDimensionMatches(position.getDimension());
+        for (int i = 0; i < lower.getDimension(); i++) {
+            add(i, position.get(i));
+        }
+    }
+
+    /**
+     * Expands this range for containing the given position.
+     *
+     * @param  position  the position to add.
+     * @throws MismatchedDimensionException if the position does not have the 
dimension of this range.
+     *
+     * @see #add(ReadOnly.Vector)
+     */
+    public void add(final DirectPosition position) throws 
MismatchedDimensionException {
+        ensureDimensionMatches(position.getDimension());
+        for (int i = 0; i < lower.getDimension(); i++) {
+            add(i, position.getCoordinate(i));
+        }
+    }
+
+    /**
+     * Expands this range in the given dimension for containing the given 
value.
+     */
+    private void add(final int dimension, final double value) {
+        if (value < lower.get(dimension)) widen(lower, dimension, value, 
false);
+        if (value > upper.get(dimension)) widen(upper, dimension, value, true);
+    }
+
+    /**
+     * Sets a bound to the given value, rounded away from the inside of the 
range if the data type of the bound
+     * cannot represent the value exactly.
+     *
+     * @param  bound      the lower or the upper bound.
+     * @param  dimension  the dimension of the value.
+     * @param  value      the new bound value.
+     * @param  up         whether to round toward positive infinity (upper 
bound) or negative infinity (lower bound).
+     */
+    private static void widen(final Vector<?> bound, final int dimension, 
final double value, final boolean up) {
+        switch (bound.getDataType()) {
+            case DOUBLE: {
+                bound.set(dimension, value);
+                break;
+            }
+            case FLOAT: {
+                float f = (float) value;
+                if (up ? f < value : f > value) {
+                    f = up ? Math.nextUp(f) : Math.nextDown(f);
+                }
+                bound.set(dimension, f);
+                break;
+            }
+            default: {
+                bound.set(dimension, up ? Math.ceil(value) : 
Math.floor(value));
+                break;
+            }
+        }
+    }
+
+    /**
+     * Returns whether the given position is inside this range or on its 
border.
+     * A position with a NaN coordinate is not contained.
+     *
+     * @param  position  the position to test.
+     * @return whether the position is inside this range or on its border.
+     * @throws MismatchedDimensionException if the position does not have the 
dimension of this range.
+     *
+     * @see #contains(DirectPosition)
+     */
+    public boolean contains(final ReadOnly.Vector<?> position) throws 
MismatchedDimensionException {
+        ensureDimensionMatches(position.getDimension());
+        for (int i = 0; i < lower.getDimension(); i++) {
+            if (!contains(i, position.get(i))) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns whether the given position is inside this range or on its 
border.
+     *
+     * @param  position  the position to test.
+     * @return whether the position is inside this range or on its border.
+     * @throws MismatchedDimensionException if the position does not have the 
dimension of this range.
+     *
+     * @see #contains(ReadOnly.Vector)
+     */
+    public boolean contains(final DirectPosition position) throws 
MismatchedDimensionException {
+        ensureDimensionMatches(position.getDimension());
+        for (int i = 0; i < lower.getDimension(); i++) {
+            if (!contains(i, position.getCoordinate(i))) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns whether the given value is inside this range in the given 
dimension. False if a value is NaN.
+     */
+    private boolean contains(final int dimension, final double value) {
+        return value >= lower.get(dimension) && value <= upper.get(dimension);
+    }
+
+    /**
+     * Verifies that a position has the dimension of this range.
+     */
+    private void ensureDimensionMatches(final int dimension) {
+        if (dimension != lower.getDimension()) {
+            throw new MismatchedDimensionException("The position has " + 
dimension
+                    + " dimensions but the range has " + lower.getDimension() 
+ '.');
+        }
+    }
+
+    /**
+     * Returns whether the given object is a range with the same bounds.
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        return (obj instanceof NDRange other) && lower.equals(other.lower) && 
upper.equals(other.upper);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(lower, upper);
+    }
+
+    /**
+     * Returns the lower and upper bounds, for example {@code NDRange[(1.0, 
2.0) … (4.0, 6.0)]}.
+     */
+    @Override
+    public String toString() {
+        final var sb = new StringBuilder("NDRange[");
+        for (final Vector<?> bound : new Vector<?>[] {lower, upper}) {
+            if (bound == upper) sb.append(" … ");
+            sb.append('(');
+            for (int i = 0; i < bound.getDimension(); i++) {
+                if (i != 0) sb.append(", ");
+                sb.append(bound.get(i));
+            }
+            sb.append(')');
+        }
+        return sb.append(']').toString();
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/DataPointsTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/DataPointsTest.java
index 982b2f4d5e..6e3b0e219f 100644
--- 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/DataPointsTest.java
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/DataPointsTest.java
@@ -24,6 +24,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.maths.Array;
 import org.apache.sis.maths.DataType;
 import org.apache.sis.maths.NDArrays;
+import org.apache.sis.maths.NDRange;
 import org.apache.sis.maths.Vector;
 import org.apache.sis.referencing.CommonCRS;
 
@@ -211,14 +212,14 @@ public class DataPointsTest {
     @Test
     public void testGetAttributeRange() {
         final DataPoints points = createSample();
-        final BBox positions = 
points.getAttributeRange(DataPointsType.ATT_POSITION);
+        final NDRange positions = 
points.getAttributeRange(DataPointsType.ATT_POSITION);
         assertEquals(GeometryTest.CRS_2D, 
positions.getCoordinateReferenceSystem());
         assertEquals( 0, positions.getMinimum(0));
         assertEquals( 0, positions.getMinimum(1));
         assertEquals(10, positions.getMaximum(0));
         assertEquals(20, positions.getMaximum(1));
 
-        final BBox temperature = points.getAttributeRange("temperature");
+        final NDRange temperature = points.getAttributeRange("temperature");
         assertEquals(1, temperature.getDimension());
         assertEquals(10, temperature.getMinimum(0));
         assertEquals(30, temperature.getMaximum(0));
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
deleted file mode 100644
index 7e5ddbb360..0000000000
--- 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/GeometryEnvelopeTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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);
-    }
-}
diff --git 
a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/maths/NDRangeTest.java
 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/maths/NDRangeTest.java
new file mode 100644
index 0000000000..156abbd09b
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/maths/NDRangeTest.java
@@ -0,0 +1,255 @@
+/*
+ * 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.maths;
+
+import org.opengis.coordinate.MismatchedDimensionException;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.referencing.CommonCRS;
+
+// Test dependencies
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * Tests {@link NDRange}.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class NDRangeTest {
+
+    private static final CoordinateReferenceSystem CRS = 
CommonCRS.WGS84.geographic3D();
+
+    private static Vector<?> vector(double... values) {
+        return Vectors.create(CRS, DataType.DOUBLE).set(values);
+    }
+
+    private static NDRange range() {
+        return new NDRange(vector(1, 2, 3), vector(4, 6, 3));
+    }
+
+    @Test
+    public void testEnvelope() {
+        final NDRange range = range();
+        assertEquals(CRS, range.getCoordinateReferenceSystem());
+        assertEquals(3, range.getDimension());
+        assertEquals(1, range.getMinimum(0));
+        assertEquals(6, range.getMaximum(1));
+        assertEquals(2.5, range.getMedian(0));
+        assertEquals(4, range.getSpan(1));
+        assertEquals(0, range.getSpan(2));
+        assertThrows(IndexOutOfBoundsException.class, () -> 
range.getMinimum(3));
+    }
+
+    @Test
+    public void testCorners() {
+        final NDRange range = range();
+        final DirectPosition lower = range.getLowerCorner();
+        final DirectPosition upper = range.getUpperCorner();
+        assertArrayEquals(new double[] {1, 2, 3}, lower.getCoordinates());
+        assertArrayEquals(new double[] {4, 6, 3}, upper.getCoordinates());
+        assertEquals(CRS, lower.getCoordinateReferenceSystem());
+        assertArrayEquals(new double[] {1, 2, 3}, 
range.getLower().toArrayDouble());
+        assertArrayEquals(new double[] {4, 6, 3}, 
range.getUpper().toArrayDouble());
+    }
+
+    /**
+     * The range keeps its own copy of the bounds, and gives copies of them.
+     */
+    @Test
+    public void testCopies() {
+        final Vector<?> lower = vector(1, 2, 3);
+        final NDRange range = new NDRange(lower, vector(4, 6, 3));
+        lower.set(0, -100);
+        range.getLower().set(0, -100);
+        range.getLowerCorner().setCoordinate(0, -100);
+        assertEquals(1, range.getMinimum(0));
+    }
+
+    /**
+     * The bounds keep their data type.
+     */
+    @Test
+    public void testDataType() {
+        final Vector<?> lower = Vectors.createInt(2).set(new double[] {1, 2});
+        final Vector<?> upper = Vectors.createInt(2).set(new double[] {5, 7});
+        final NDRange range = new NDRange(lower, upper);
+        assertEquals(DataType.INT, range.getLower().getDataType());
+        assertEquals(5, range.getMaximum(0));
+    }
+
+    @Test
+    public void testFromEnvelope() {
+        final GeneralEnvelope envelope = new GeneralEnvelope(CRS);
+        envelope.setEnvelope(1, 2, 3, 4, 6, 3);
+        final NDRange range = new NDRange(envelope);
+        assertEquals(range(), range);
+        assertEquals(range().hashCode(), range.hashCode());
+        assertEquals(CRS, range.getCoordinateReferenceSystem());
+    }
+
+    @Test
+    public void testInvalid() {
+        assertThrows(MismatchedDimensionException.class, () -> new 
NDRange(vector(1, 2, 3), Vectors.createDouble(2)));
+        assertThrows(IllegalArgumentException.class, () -> new 
NDRange(vector(5, 2, 3), vector(4, 6, 3)));
+        assertThrows(IllegalArgumentException.class, () -> new 
NDRange(vector(1, 2, 3),
+                Vectors.create(CommonCRS.WGS84.geocentric(), 
DataType.DOUBLE).set(new double[] {4, 6, 3})));
+    }
+
+    /**
+     * NaN bounds make an empty range.
+     */
+    @Test
+    public void testEmpty() {
+        final NDRange range = new NDRange(vector(Double.NaN, 2, 3), 
vector(Double.NaN, 6, 3));
+        assertTrue(Double.isNaN(range.getSpan(0)));
+    }
+
+    @Test
+    public void testAdd() {
+        final NDRange range = range();
+        range.add(vector(0, 3, 10));
+        assertArrayEquals(new double[] {0, 2, 3}, 
range.getLower().toArrayDouble());
+        assertArrayEquals(new double[] {4, 6, 10}, 
range.getUpper().toArrayDouble());
+        // A position inside the range does not change it.
+        range.add(vector(2, 3, 5));
+        assertEquals(new NDRange(vector(0, 2, 3), vector(4, 6, 10)), range);
+        // A NaN coordinate is ignored.
+        range.add(vector(Double.NaN, 8, 5));
+        assertArrayEquals(new double[] {4, 8, 10}, 
range.getUpper().toArrayDouble());
+        // The same with a direct position.
+        range.add(Vectors.asDirectPostion(vector(-1, 2, 3)));
+        assertEquals(-1, range.getMinimum(0));
+        assertThrows(MismatchedDimensionException.class, () -> 
range.add(Vectors.createDouble(2)));
+    }
+
+    /**
+     * A NaN bound makes the range empty in its dimension, and adding a 
position does not change that,
+     * as in {@link GeneralEnvelope}.
+     */
+    @Test
+    public void testAddToEmpty() {
+        final NDRange range = new NDRange(vector(Double.NaN, 2, 3), 
vector(Double.NaN, 6, 3));
+        range.add(vector(1, 1, 3));
+        assertTrue(Double.isNaN(range.getMinimum(0)));
+        assertEquals(1, range.getMinimum(1));
+    }
+
+    /**
+     * Bounds of integer or float types are widened, so that the added 
position is contained.
+     */
+    @Test
+    public void testAddRounding() {
+        final NDRange integers = new NDRange(Vectors.createInt(1).set(new 
double[] {1}), Vectors.createInt(1).set(new double[] {5}));
+        integers.add(Vectors.createDouble(1).set(new double[] {7.5}));
+        integers.add(Vectors.createDouble(1).set(new double[] {0.5}));
+        assertEquals(0, integers.getMinimum(0));
+        assertEquals(8, integers.getMaximum(0));
+        assertTrue(integers.contains(Vectors.createDouble(1).set(new double[] 
{7.5})));
+
+        final NDRange floats = new NDRange(Vectors.createFloat(1).set(new 
double[] {1}), Vectors.createFloat(1).set(new double[] {2}));
+        floats.add(Vectors.createDouble(1).set(new double[] {0.1}));
+        floats.add(Vectors.createDouble(1).set(new double[] {2.3}));
+        assertTrue(floats.getMinimum(0) <= 0.1);
+        assertTrue(floats.getMaximum(0) >= 2.3);
+        assertTrue(floats.contains(Vectors.createDouble(1).set(new double[] 
{0.1})));
+    }
+
+    @Test
+    public void testContains() {
+        final NDRange range = range();
+        assertTrue (range.contains(vector(2, 3, 3)));
+        assertTrue (range.contains(vector(1, 6, 3)), "The border is 
contained.");
+        assertFalse(range.contains(vector(0, 3, 3)));
+        assertFalse(range.contains(vector(2, 3, 4)));
+        assertFalse(range.contains(vector(Double.NaN, 3, 3)));
+        assertTrue (range.contains(Vectors.asDirectPostion(vector(4, 2, 3))));
+        assertThrows(MismatchedDimensionException.class, () -> 
range.contains(Vectors.createDouble(2)));
+    }
+
+    /**
+     * The constructors without bounds create a range of the given data type, 
{@code DOUBLE} by default,
+     * with all bounds at zero.
+     */
+    @Test
+    public void testConstructors() {
+        NDRange range = new NDRange(2);
+        assertEquals(2, range.getDimension());
+        assertEquals(DataType.DOUBLE, range.getLower().getDataType());
+        assertArrayEquals(new double[] {0, 0}, 
range.getUpper().toArrayDouble());
+
+        range = new NDRange(4, DataType.INT);
+        assertEquals(4, range.getDimension());
+        assertEquals(DataType.INT, range.getUpper().getDataType());
+
+        range = new NDRange(CRS);
+        assertEquals(CRS, range.getCoordinateReferenceSystem());
+        assertEquals(3, range.getDimension());
+        assertEquals(DataType.DOUBLE, range.getLower().getDataType());
+
+        range = new NDRange(CRS, DataType.FLOAT);
+        assertEquals(CRS, range.getCoordinateReferenceSystem());
+        assertEquals(DataType.FLOAT, range.getLower().getDataType());
+
+        range = new NDRange(SampleSystem.of(CRS));
+        assertEquals(CRS, range.getCoordinateReferenceSystem());
+        assertEquals(DataType.DOUBLE, range.getLower().getDataType());
+
+        range = new NDRange(SampleSystem.of(CRS), DataType.SHORT);
+        assertEquals(DataType.SHORT, range.getLower().getDataType());
+        assertEquals(new NDRange(SampleSystem.of(CRS), DataType.SHORT), range);
+
+        assertThrows(IllegalArgumentException.class, () -> new NDRange(0));
+    }
+
+    /**
+     * Setting a range keeps the data type of the bounds, rounding outward the 
values it cannot represent.
+     */
+    @Test
+    public void testSetRange() {
+        final NDRange integers = new NDRange(3, DataType.INT);
+        integers.setRange(0, 1, 5);
+        integers.setRange(1, 1.5, 4.5);
+        integers.setRange(2, 3L, 9L);
+        assertEquals(DataType.INT, integers.getLower().getDataType());
+        assertArrayEquals(new double[] {1, 1, 3}, 
integers.getLower().toArrayDouble());
+        assertArrayEquals(new double[] {5, 5, 9}, 
integers.getUpper().toArrayDouble());
+
+        final NDRange floats = new NDRange(2, DataType.FLOAT);
+        floats.setRange(0, 0.1, 2.3);
+        floats.setRange(1, 0.5f, 1.5f);
+        assertEquals(DataType.FLOAT, floats.getLower().getDataType());
+        assertTrue(floats.getMinimum(0) <= 0.1 && floats.getMaximum(0) >= 2.3);
+        assertEquals(0.5, floats.getMinimum(1));
+        assertEquals(1.5, floats.getMaximum(1));
+
+        final NDRange doubles = new NDRange(1);
+        doubles.setRange(0, 0.1, 2.3);
+        assertEquals(0.1, doubles.getMinimum(0));
+        assertEquals(2.3, doubles.getMaximum(0));
+
+        assertThrows(IllegalArgumentException.class, () -> doubles.setRange(0, 
3, 2));
+        assertThrows(IndexOutOfBoundsException.class, () -> 
doubles.setRange(1, 0, 1));
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("NDRange[(1.0, 2.0, 3.0) … (4.0, 6.0, 3.0)]", 
range().toString());
+    }
+}

Reply via email to