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