This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 2cebe32d40 Refactor the code for filtering the dimensions of a CRS.
2cebe32d40 is described below
commit 2cebe32d400ba9014befd6918c4feb22847abfb3
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Jan 20 17:47:07 2026 +0100
Refactor the code for filtering the dimensions of a CRS.
- A new `CRS.selectDimensions(CoordinateReferenceSystem, BitSet,
SeparationMode)` method is added.
- A new `GridGeometry.getConstantCoordinates()` method is added, which uses
the above-cited method.
- The GeoTIFF `MultiResolutionImage` class uses the above for accepting 2D
request on 3D data.
---
.../apache/sis/coverage/grid/DefaultEvaluator.java | 4 +-
.../apache/sis/coverage/grid/DimensionReducer.java | 7 +-
.../apache/sis/coverage/grid/GridDerivation.java | 47 +-
.../org/apache/sis/coverage/grid/GridGeometry.java | 96 ++++-
.../sis/coverage/grid/GridDerivationTest.java | 5 +-
.../apache/sis/coverage/grid/GridGeometryTest.java | 24 ++
.../sis/geometry/AbstractDirectPosition.java | 34 +-
.../apache/sis/geometry/GeneralDirectPosition.java | 39 +-
.../sis/geometry/ImmutableDirectPosition.java | 161 +++++++
.../org/apache/sis/geometry/ImmutableEnvelope.java | 15 +-
.../main/org/apache/sis/referencing/CRS.java | 477 +++++++++++++++++----
.../internal/shared/DirectPositionView.java | 3 +-
.../referencing/operation/SubOperationInfo.java | 10 +-
.../test/org/apache/sis/referencing/CRSTest.java | 11 +
.../sis/storage/geotiff/MultiResolutionImage.java | 22 +-
netbeans-project/nbproject/project.xml | 1 +
16 files changed, 789 insertions(+), 167 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
index e1198e6489..3662d73a6e 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
@@ -626,7 +626,7 @@ next: while (--numPoints >= 0) {
MathTransform crsToGrid =
TranslatedTransform.resolveNaN(gridToCRS.inverse(), gridGeometry);
if (crs != null) {
final CoordinateReferenceSystem stepCRS =
coverage.getCoordinateReferenceSystem();
- final GeographicBoundingBox areaOfInterest =
gridGeometry.geographicBBox();
+ final GeographicBoundingBox areaOfInterest =
gridGeometry.getGeographicExtent().orElse(null);
try {
CoordinateOperation op = CRS.findOperation(crs, stepCRS,
areaOfInterest);
crsToGrid = MathTransforms.concatenate(op.getMathTransform(),
crsToGrid);
@@ -670,7 +670,7 @@ next: while (--numPoints >= 0) {
} else {
final Long value = slice.get(j);
if (value == null) {
- final GridExtent extent = gridGeometry.extent;
+ final GridExtent extent =
gridGeometry.getExtent();
throw new
FactoryException(Resources.format(Resources.Keys.NoNDimensionalSlice_3,
crsDim,
extent.getAxisIdentification(j, j), extent.getSize(j)));
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
index daa2eebcc5..2724153a6e 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
@@ -17,6 +17,7 @@
package org.apache.sis.coverage.grid;
import java.util.Arrays;
+import java.util.BitSet;
import org.opengis.util.FactoryException;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
@@ -55,10 +56,10 @@ final class DimensionReducer {
/**
* Requests to retain only the axes in the specified <abbr>CRS</abbr>
dimensions.
*
- * @param dimensions the <abbr>CRS</abbr> dimensions to keep, or {@code
null} for keeping them all.
+ * @param mask the <abbr>CRS</abbr> dimensions to keep.
*/
- DimensionReducer(final int... dimensions) {
- this.dimensions = dimensions;
+ DimensionReducer(final BitSet mask) {
+ dimensions = mask.stream().toArray();
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
index e4840736a9..052b2cf796 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
@@ -35,7 +35,9 @@ import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.AxisDirection;
import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.operation.MissingSourceDimensionsException;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.referencing.operation.transform.TransformSeparator;
@@ -233,7 +235,7 @@ public class GridDerivation {
* Indexes of <abbr>CRS</abbr> axes to keep, or {@code null} if no
filtering will be applied.
* A non-null value may cause a reduction in the number of dimensions of
the grid.
*
- * @see #project(Set)
+ * @see #selectDimensions(Set)
*/
private BitSet dimensionsToKeepInCRS;
@@ -1202,16 +1204,17 @@ public class GridDerivation {
}
/**
- * Requests a projection where only a subset of the <abbr>CRS</abbr>
dimensions will be kept.
+ * Requests a grid where only a subset of the <abbr>CRS</abbr> dimensions
will be kept.
* The real world dimensions to keep are specified by a filter applied on
the coordinate system axes.
* This method may reduce the number of dimensions of the grid if, as a
result of this filtering,
* some grid dimensions become unrelated to any <abbr>CRS</abbr> axis.
*
* <h4>Example</h4>
- * The following code removes the temporal dimension of a grid geometry:
+ * The following code keeps only the axes having a linear unit of
measurement such as metres or kilometres:
*
* {@snippet lang="java" :
- * gridGeometry.derive().project((axis) -> axis.getDirection() !=
AxisDirection.FUTURE).build();
+ * GridGeometry gg = ...;
+ * gg = gg.derive().selectDimensions((axis) ->
Units.isLinear(axis.getUnit())).build();
* }
*
* @param filter a predicate which returns {@code true} for coordinate
system axes to keep.
@@ -1220,7 +1223,7 @@ public class GridDerivation {
*
* @since 1.6
*/
- public GridDerivation project(final Predicate<CoordinateSystemAxis>
filter) {
+ public GridDerivation selectDimensions(final
Predicate<CoordinateSystemAxis> filter) {
final var dimensions = new BitSet();
final CoordinateSystem cs =
base.getCoordinateReferenceSystem().getCoordinateSystem();
for (int i = cs.getDimension(); --i >= 0;) {
@@ -1236,6 +1239,38 @@ public class GridDerivation {
return this;
}
+ /**
+ * Requests a grid where some <abbr>CRS</abbr> dimensions are excluded.
+ * The real world dimensions to exclude are specified by the axis
directions.
+ * This method may reduce the number of dimensions of the grid if, as a
result of this filtering,
+ * some grid dimensions become unrelated to any <abbr>CRS</abbr> axis.
+ *
+ * <h4>Example</h4>
+ * This method is provided for convenience in the handling of {@link
MissingSourceDimensionsException}.
+ * A usage pattern can be as below:
+ *
+ * {@snippet lang="java" :
+ * GridGeometry gg = ...;
+ * try {
+ * doSomeStuff(gg);
+ * } catch (MissingSourceDimensionsException e) {
+ * gg =
gg.gridDerivation().excludeDimensions(e.getMissingAxes()).build();
+ * doSomeStuff(gg); // Try again.
+ * }
+ * }
+ *
+ * @param exclusion the dimensions to remove, identified by their axis
directions.
+ * @return {@code this} for method call chaining.
+ * @throws IncompleteGridGeometryException if the base grid geometry has
no <abbr>CRS</abbr>.
+ *
+ * @see MissingSourceDimensionsException#getMissingAxes()
+ *
+ * @since 1.6
+ */
+ public GridDerivation excludeDimensions(final Set<AxisDirection>
exclusion) {
+ return selectDimensions((axis) ->
!exclusion.contains(axis.getDirection()));
+ }
+
/**
* Builds a grid geometry with the configuration specified by the other
methods in this {@code GridDerivation} class.
*
@@ -1296,7 +1331,7 @@ public class GridDerivation {
throw new IllegalGridGeometryException(e, "envelope");
}
if (dimensionsToKeepInCRS != null) try {
- grid = new
DimensionReducer(dimensionsToKeepInCRS.stream().toArray()).apply(grid);
+ grid = new DimensionReducer(dimensionsToKeepInCRS).apply(grid);
} catch (FactoryException e) {
throw new IllegalGridGeometryException(e, "gridToCRS");
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
index a4d4d39e06..3aca8de747 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
@@ -16,6 +16,8 @@
*/
package org.apache.sis.coverage.grid;
+import java.util.Arrays;
+import java.util.BitSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
@@ -30,6 +32,7 @@ import org.opengis.util.FactoryException;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.geometry.Envelope;
+import org.opengis.geometry.DirectPosition;
import org.opengis.coordinate.CoordinateMetadata;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
@@ -38,8 +41,8 @@ import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.DerivedCRS;
-import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.measure.AngleFormat;
import org.apache.sis.measure.Latitude;
@@ -48,9 +51,11 @@ import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.AbstractEnvelope;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.ImmutableEnvelope;
+import org.apache.sis.geometry.ImmutableDirectPosition;
import org.apache.sis.coordinate.DefaultCoordinateMetadata;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.crs.AbstractCRS;
+import org.apache.sis.referencing.operation.CoordinateOperationContext;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.matrix.MatrixSIS;
import org.apache.sis.referencing.operation.transform.MathTransforms;
@@ -85,6 +90,7 @@ import org.apache.sis.io.TableAppender;
import org.apache.sis.xml.NilObject;
import org.apache.sis.xml.NilReason;
import static org.apache.sis.referencing.CRS.findOperation;
+import static org.apache.sis.referencing.CRS.SeparationMode;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.coordinate.MismatchedDimensionException;
@@ -301,6 +307,14 @@ public class GridGeometry implements LenientComparable,
Serializable {
@SuppressWarnings("VolatileArrayField") // Safe because
array will not be modified after construction.
private transient volatile Instant[] timeRange;
+ /**
+ * Coordinates that may be considered as constants, or {@code null} if not
yet computed.
+ * By convention, a position of dimension 0 means that there is no
constant coordinates.
+ *
+ * @see #getConstantCoordinates()
+ */
+ private transient volatile DirectPosition constantCoordinates;
+
/**
* An "empty" grid geometry with no value defined. All getter methods
invoked on this instance will cause
* {@link IncompleteGridGeometryException} to be thrown. This instance can
be used as a place-holder when
@@ -601,7 +615,7 @@ public class GridGeometry implements LenientComparable,
Serializable {
* @param caller the method where exception occurred.
* @param exception the exception that occurred.
*/
- static void recoverableException(final String caller, final
TransformException exception) {
+ static void recoverableException(final String caller, final Exception
exception) {
Logging.recoverableException(GridExtent.LOGGER, GridGeometry.class,
caller, exception);
}
@@ -1120,7 +1134,7 @@ public class GridGeometry implements LenientComparable,
Serializable {
* <h4>API note</h4>
* This method does not throw {@link IncompleteGridGeometryException}
because the geographic extent
* may be absent even with a complete grid geometry. Grid geometries are
not required to have a
- * spatial component on Earth surface; a raster could be a vertical
profile for example.
+ * spatial component on Earth surface, since a raster could be a vertical
profile for example.
*
* @return the geographic bounding box in "real world" coordinates.
*/
@@ -1132,7 +1146,7 @@ public class GridGeometry implements LenientComparable,
Serializable {
* Returns the {@link #geographicBBox} value or {@code null} if none.
* This method computes the box when first needed.
*/
- final GeographicBoundingBox geographicBBox() {
+ private final GeographicBoundingBox geographicBBox() {
GeographicBoundingBox bbox = geographicBBox;
if (bbox == null) {
if (getCoordinateReferenceSystem(envelope) != null &&
!envelope.isAllNaN()) {
@@ -1188,6 +1202,80 @@ public class GridGeometry implements LenientComparable,
Serializable {
return times;
}
+ /**
+ * If the envelope has some coordinates that may be considered as
constant, returns these coordinates.
+ * A constant coordinates is a coordinate where the lower bound is equal
to the upper bound.
+ * All non-constant coordinates are set to NaN. If this method returns a
non-empty value,
+ * then it is guaranteed to contain at least one non-NaN value.
+ *
+ * <h4>Coordinate Reference System</h4>
+ * The <abbr>CRS</abbr> of the returned position may be {@link
#getCoordinateReferenceSystem()}.
+ * But it may also be a subset of the <abbr>CRS</abbr> components
containing only the components
+ * having at least one non-NaN value. For example, if only the time
coordinate is constant, then
+ * the <abbr>CRS</abbr> of the returned position may contain only the
temporal component.
+ *
+ * <h4>Usage</h4>
+ * This is a helper method for computing coordinate operations with grid
geometries that are,
+ * for example, two-dimensional slices in a three- or four-dimensional
data cube.
+ * The algorithm should work with any number of dimensions, the
two-dimensional slice is only an example.
+ * This method is intended to be used with {@link
CoordinateOperationContext} as below:
+ *
+ * {@snippet lang="java" :
+ * GridGeometry gg = ...;
+ * var context = new CoordinateOperationContext();
+ * gg.getGeographicExtent().ifPresent(context::addAreaOfInterest);
+ *
gg.getConstantCoordinates().ifPresent(context::setConstantCoordinates);
+ * CoordinateOperation op = CRS.findOperation(...,
targetGrid.getCoordinateMetadata(), context);
+ * }
+ *
+ * @return the constant coordinates, or empty if none.
+ *
+ * @see #getGeographicExtent()
+ * @see CoordinateOperationContext#getConstantCoordinates()
+ * @since 1.6
+ */
+ public Optional<DirectPosition> getConstantCoordinates() {
+ DirectPosition constants = constantCoordinates;
+ if (constants == null && envelope != null) {
+ double[] coordinates = new double[envelope.getDimension()];
+ Arrays.fill(coordinates, Double.NaN);
+ final var selected = new BitSet();
+ for (int i=0; i<coordinates.length; i++) {
+ double lower = envelope.getLower(i);
+ double upper = envelope.getUpper(i);
+ if (Double.isNaN(lower)) lower = upper;
+ if (Double.isNaN(upper)) upper = lower;
+ if (lower == upper) {
+ coordinates[i] = lower;
+ selected.set(i);
+ }
+ }
+ CoordinateReferenceSystem crs;
+ if (selected.isEmpty()) {
+ crs = null;
+ coordinates = ArraysExt.EMPTY_DOUBLE;
+ } else {
+ crs = envelope.getCoordinateReferenceSystem();
+ try {
+ crs = org.apache.sis.referencing.CRS.selectDimensions(crs,
selected, SeparationMode.WHOLE_UNSEPARABLE);
+ int count = 0;
+ for (int i : selected.stream().toArray()) {
+ coordinates[count++] = coordinates[i];
+ }
+ coordinates = ArraysExt.resize(coordinates, count);
+ } catch (FactoryException e) {
+ recoverableException("getConstantCoordinates", e);
+ }
+ }
+ constants = new ImmutableDirectPosition(crs, coordinates);
+ constantCoordinates = constants;
+ }
+ if (constants != null && constants.getDimension() == 0) {
+ constants = null;
+ }
+ return Optional.ofNullable(constants);
+ }
+
/**
* Returns the "real world" coordinates of the cell at indices (0, 0, … 0).
* The returned coordinates map the {@linkplain PixelInCell#CELL_CORNER
cell corner}.
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
index 582e7566ad..98aef0e5c7 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
@@ -17,6 +17,7 @@
package org.apache.sis.coverage.grid;
import java.util.Map;
+import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
@@ -739,7 +740,7 @@ public final class GridDerivationTest extends TestCase {
}
/**
- * Tests {@link GridDerivation#project(Predicate)}.
+ * Tests {@link GridDerivation#selectDimensions(Predicate)}.
*/
@Test
public void testProject() {
@@ -751,7 +752,7 @@ public final class GridDerivationTest extends TestCase {
0, 0, 2, 3,
0, 0, 0, 1)), HardCodedCRS.WGS84_3D);
- GridGeometry projected = grid.derive().project((axis) ->
axis.getDirection() != AxisDirection.UP).build();
+ GridGeometry projected =
grid.derive().excludeDimensions(Set.of(AxisDirection.UP)).build();
assertNotSame(grid, projected);
assertEquals(2, projected.getDimension());
assertTrue(CRS.equivalent(HardCodedCRS.WGS84,
projected.getCoordinateReferenceSystem()));
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
index 1cb5e7bf9b..0f7c2a0c69 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -572,6 +572,30 @@ public final class GridGeometryTest extends TestCase {
new double[] { 1, -3.0, 0}), envelope);
}
+ /**
+ * Tests {@link GridGeometry#getConstantCoordinates()}.
+ */
+ @Test
+ public void testGetConstantCoordinates() {
+ for (double sy = 0; sy <= 1; sy++) {
+ final var grid = new GridGeometry(
+ new GridExtent(12, 1),
+ PixelInCell.CELL_CORNER,
+ MathTransforms.linear(new Matrix3(
+ 0.25, 0, -2,
+ 0, sy, -3,
+ 0, 0, 1)),
+ HardCodedCRS.WGS84);
+
+ final var constant = grid.getConstantCoordinates();
+ assertEquals(constant, grid.getConstantCoordinates()); //
Verify the cache.
+ assertEquals(sy == 0, constant.isPresent());
+ if (sy == 0) {
+ assertArrayEquals(new double[] {Double.NaN, -3},
constant.orElseThrow().getCoordinates());
+ }
+ }
+ }
+
/**
* Tests {@link GridGeometry#upsample(long...)}.
*/
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
index ee791a9b88..7f317671cd 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/AbstractDirectPosition.java
@@ -58,7 +58,7 @@ import static
org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
* serializable, is left to subclasses.</p>
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.5
+ * @version 1.6
* @since 0.3
*/
public abstract class AbstractDirectPosition extends FormattableObject
implements DirectPosition {
@@ -71,10 +71,10 @@ public abstract class AbstractDirectPosition extends
FormattableObject implement
/**
* Returns the given position as an {@code AbstractDirectPosition}
instance.
* If the given position is already an instance of {@code
AbstractDirectPosition},
- * then it is returned unchanged. Otherwise the coordinate values and the
CRS
+ * then it is returned unchanged. Otherwise, the coordinate values and the
<abbr>CRS</abbr>
* of the given position are copied in a new position.
*
- * @param position the position to cast, or {@code null}.
+ * @param position the position to cast or copy, or {@code null}.
* @return the values of the given position as an {@code
AbstractDirectPosition} instance.
*
* @since 1.0
@@ -102,21 +102,25 @@ public abstract class AbstractDirectPosition extends
FormattableObject implement
*/
@Override
public void setCoordinate(int dimension, double value) {
- throw new
UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1,
getClass()));
+ // Be tolerant if the coordinate is the same for allowing
`normalize()` to be a no-operation.
+ if (!Numerics.equals(getCoordinate(dimension), value)) {
+ throw new
UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1,
getClass()));
+ }
}
/**
* Sets this direct position to the given position. If the given position
is
* {@code null}, then all coordinate values are set to {@link Double#NaN
NaN}.
*
- * <p>If this position and the given position have a non-null CRS, then
the default implementation
- * requires the CRS to be {@linkplain CRS#equivalent equivalent},
+ * <p>If this position and the given position have a non-null
<abbr>CRS</abbr>,
+ * then the default implementation requires the <abbr>CRS</abbr> to be
{@linkplain CRS#equivalent equivalent},
* otherwise a {@code MismatchedCoordinateMetadataException} is thrown.
However, subclass may choose
- * to assign the CRS of this position to the CRS of the given position.</p>
+ * to assign the <abbr>CRS</abbr> of this position to the <abbr>CRS</abbr>
of the given position.</p>
*
* @param position the new position, or {@code null}.
* @throws MismatchedDimensionException if the given position doesn't have
the expected dimension.
* @throws MismatchedCoordinateMetadataException if the given position
doesn't use the expected CRS.
+ * @throws UnsupportedOperationException if this direct position is
immutable.
*/
public void setLocation(final DirectPosition position)
throws MismatchedDimensionException,
MismatchedCoordinateMetadataException
@@ -196,7 +200,7 @@ public abstract class AbstractDirectPosition extends
FormattableObject implement
}
/**
- * Formats this position in the <i>Well Known Text</i> (WKT) format.
+ * Formats this position in the <i>Well Known Text</i> (<abbr>WKT</abbr>)
format.
* The format is like below, where {@code x₀}, {@code x₁}, {@code x₂},
<i>etc.</i>
* are the coordinate values at index 0, 1, 2, <i>etc.</i>:
*
@@ -209,7 +213,7 @@ public abstract class AbstractDirectPosition extends
FormattableObject implement
* adjusted for the axis unit of measurement and the planet size if
different than Earth).
*
* @param formatter the formatter where to format the inner content of
this point.
- * @return the WKT keyword, which is {@code "Point"} for this element.
+ * @return the <abbr>WKT</abbr> keyword, which is {@code "Point"} for this
element.
*
* @since 1.0
*/
@@ -245,11 +249,11 @@ public abstract class AbstractDirectPosition extends
FormattableObject implement
/**
* Implementation of the public {@link #toString()} and {@link
DirectPosition2D#toString()} methods
* for formatting a {@code POINT} element from a direct position in
<i>Well Known Text</i>
- * (WKT) format.
+ * (<abbr>WKT</abbr>) format.
*
* @param position the position to format.
* @param isSinglePrecision {@code true} if every coordinate values can
be cast to {@code float}.
- * @return the point as a {@code POINT} in WKT format.
+ * @return the point as a {@code POINT} in <abbr>WKT</abbr> format.
*
* @see ArraysExt#isSinglePrecision(double[])
*/
@@ -277,9 +281,9 @@ public abstract class AbstractDirectPosition extends
FormattableObject implement
}
/**
- * Parses the given WKT.
+ * Parses the given <abbr>WKT</abbr>.
*
- * @param wkt the WKT to parse.
+ * @param wkt the <abbr>WKT</abbr> to parse.
* @return the coordinates, or {@code null} if none.
* @throws NumberFormatException if a number cannot be parsed.
* @throws IllegalArgumentException if the parenthesis are not balanced.
@@ -394,7 +398,7 @@ parse: while (i < length) {
/**
* Returns {@code true} if the specified object is also a {@code
DirectPosition}
- * with equal coordinates and equal CRS.
+ * with equal coordinates and equal <abbr>CRS</abbr>.
*
* This method performs the comparison as documented in the {@link
DirectPosition#equals(Object)}
* javadoc. In particular, the given object is not required to be of the
same implementation class.
@@ -410,7 +414,7 @@ parse: while (i < length) {
return true;
}
if (object instanceof DirectPosition) {
- final DirectPosition that = (DirectPosition) object;
+ final var that = (DirectPosition) object;
final int dimension = getDimension();
if (dimension == that.getDimension()) {
for (int i=0; i<dimension; i++) {
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java
index 2fa37ecbac..4957278c50 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/GeneralDirectPosition.java
@@ -30,10 +30,9 @@ import org.opengis.geometry.DirectPosition;
import org.opengis.coordinate.MismatchedDimensionException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ArraysExt;
-import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
-
/**
* A mutable {@code DirectPosition} (the coordinates of a position) of
arbitrary dimension.
@@ -72,8 +71,8 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
private static volatile Field coordinatesField;
/**
- * The coordinates of the direct position. The length of this array is the
- * {@linkplain #getDimension() dimension} of this direct position.
+ * The coordinates of the direct position. The length of this array is
+ * the {@linkplain #getDimension() dimension} of this direct position.
*/
public final double[] coordinates;
@@ -137,12 +136,12 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
public GeneralDirectPosition(final DirectPosition point) {
coordinates = point.getCoordinates(); //
Should already be cloned.
crs = point.getCoordinateReferenceSystem();
- ensureDimensionMatches("crs", coordinates.length, crs);
+ ArgumentChecks.ensureDimensionMatches("crs", coordinates.length, crs);
}
/**
- * Constructs a position initialized to the values parsed
- * from the given string in <i>Well Known Text</i> (WKT) format.
+ * Constructs a position initialized to the values parsed from the
+ * given string in <i>Well Known Text</i> (<abbr>WKT</abbr>) format.
* The given string is typically a {@code POINT} element like below:
*
* {@snippet lang="wkt" :
@@ -176,9 +175,9 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
}
/**
- * Returns the coordinate reference system in which the coordinate is
given.
+ * Returns the coordinate reference system in which the coordinates are
given.
* May be {@code null} if this particular {@code DirectPosition} is
included
- * in a larger object with such a reference to a CRS.
+ * in a larger object with such a reference to a <abbr>CRS</abbr>.
*
* @return the coordinate reference system, or {@code null}.
*/
@@ -188,7 +187,7 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
}
/**
- * Sets the coordinate reference system in which the coordinate is given.
+ * Sets the coordinate reference system in which the coordinates are given.
*
* @param crs the new coordinate reference system, or {@code null}.
* @throws MismatchedDimensionException if the specified CRS does not have
the expected number of dimensions.
@@ -196,12 +195,12 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
public void setCoordinateReferenceSystem(final CoordinateReferenceSystem
crs)
throws MismatchedDimensionException
{
- ensureDimensionMatches("crs", getDimension(), crs);
+ ArgumentChecks.ensureDimensionMatches("crs", getDimension(), crs);
this.crs = crs;
}
/**
- * Returns a sequence of numbers that hold the coordinate of this position
in its reference system.
+ * Returns a sequence of numbers that hold the coordinates of this
position in its reference system.
*
* <div class="note"><b>API note:</b>
* This method is final for ensuring consistency with the {@link
#coordinates}, array field, which is public.</div>
@@ -216,7 +215,7 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
}
/**
- * Sets the coordinate values along all dimensions.
+ * Sets the coordinate values in all dimensions.
*
* @param coordinates the new coordinates values, or a {@code null}
array for
* setting all coordinate values to {@link Double#NaN
NaN}.
@@ -229,7 +228,7 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
if (coordinates == null) {
Arrays.fill(this.coordinates, Double.NaN);
} else {
- ensureDimensionMatches("coordinates", this.coordinates.length,
coordinates);
+ ArgumentChecks.ensureDimensionMatches("coordinates",
this.coordinates.length, coordinates);
System.arraycopy(coordinates, 0, this.coordinates, 0,
coordinates.length);
}
}
@@ -267,8 +266,8 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
/**
* Sets this coordinate to the specified direct position. If the specified
position
- * contains a coordinate reference system (CRS), then the CRS for this
position will
- * be set to the CRS of the specified position.
+ * contains a coordinate reference system (<abbr>CRS</abbr>), then the
<abbr>CRS</abbr>
+ * for this position will be set to the <abbr>CRS</abbr> of the specified
position.
*
* @param position the new position for this point,
* or {@code null} for setting all coordinate values to
{@link Double#NaN NaN}.
@@ -279,7 +278,7 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
if (position == null) {
Arrays.fill(coordinates, Double.NaN);
} else {
- ensureDimensionMatches("position", coordinates.length, position);
+ ArgumentChecks.ensureDimensionMatches("position",
coordinates.length, position);
setCoordinateReferenceSystem(position.getCoordinateReferenceSystem());
for (int i=0; i<coordinates.length; i++) {
coordinates[i] = position.getCoordinate(i);
@@ -317,7 +316,7 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
if (field == null) {
coordinatesField = field =
getCoordinatesField(GeneralDirectPosition.class);
}
- GeneralDirectPosition e = (GeneralDirectPosition) super.clone();
+ var e = (GeneralDirectPosition) super.clone();
field.set(e, coordinates.clone());
return e;
} catch (ReflectiveOperationException | CloneNotSupportedException
exception) {
@@ -335,7 +334,7 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
*/
@Override
public int hashCode() {
- final int code = Arrays.hashCode(coordinates) +
Objects.hashCode(getCoordinateReferenceSystem());
+ final int code = Arrays.hashCode(coordinates) + Objects.hashCode(crs);
assert code == super.hashCode();
return code;
}
@@ -349,7 +348,7 @@ public class GeneralDirectPosition extends
AbstractDirectPosition implements Ser
return true;
}
if (object instanceof GeneralDirectPosition) {
- final GeneralDirectPosition that = (GeneralDirectPosition) object;
+ final var that = (GeneralDirectPosition) object;
return Arrays.equals(coordinates, that.coordinates) &&
Objects.equals(crs, that.crs);
}
return super.equals(object); // Comparison of other
implementation classes.
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableDirectPosition.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableDirectPosition.java
new file mode 100644
index 0000000000..5df93bb69f
--- /dev/null
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableDirectPosition.java
@@ -0,0 +1,161 @@
+/*
+ * 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.geometry;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.io.Serializable;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.coordinate.MismatchedDimensionException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
+
+
+/**
+ * An immutable {@code DirectPosition} (the coordinates of a position) of
arbitrary dimension.
+ * This final class is immutable and thus inherently thread-safe if the {@link
CoordinateReferenceSystem}
+ * instance given to the constructor is immutable. This is usually the case in
Apache <abbr>SIS</abbr>.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.6
+ * @since 1.6
+ */
+public final class ImmutableDirectPosition extends AbstractDirectPosition
implements Serializable {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -4275832076346637274L;
+
+ /**
+ * The coordinate reference system, or {@code null}.
+ */
+ @SuppressWarnings("serial") // Most SIS implementations are
serializable.
+ private final CoordinateReferenceSystem crs;
+
+ /**
+ * The coordinates of the direct position. The length of this array is
+ * the {@linkplain #getDimension() dimension} of this direct position.
+ */
+ private final double[] coordinates;
+
+ /**
+ * Constructs a position defined by a sequence of coordinate values.
+ *
+ * @param crs the <abbr>CRS</abbr> to assign to this direct
position, or {@code null}.
+ * @param coordinates the coordinate values for each dimension.
+ * @throws MismatchedDimensionException if the CRS dimension is not equal
to the number of coordinates.
+ */
+ public ImmutableDirectPosition(final CoordinateReferenceSystem crs, final
double... coordinates)
+ throws MismatchedDimensionException
+ {
+ this.crs = crs;
+ this.coordinates = coordinates.clone();
+ ArgumentChecks.ensureDimensionMatches("crs", coordinates.length, crs);
+ }
+
+ /**
+ * Returns the given position as an {@code ImmutableDirectPosition}
instance.
+ * If the given position is already an instance of {@code
ImmutableDirectPosition},
+ * then it is returned unchanged. Otherwise, the coordinate values and the
<abbr>CRS</abbr>
+ * of the given position are copied in a new position.
+ *
+ * @param position the position to cast or copy, or {@code null}.
+ * @return the values of the given position as an {@code
ImmutableDirectPosition} instance.
+ */
+ public static ImmutableDirectPosition castOrCopy(final DirectPosition
position) {
+ if (position == null || position instanceof ImmutableDirectPosition) {
+ return (ImmutableDirectPosition) position;
+ }
+ return new
ImmutableDirectPosition(position.getCoordinateReferenceSystem(),
position.getCoordinates());
+ }
+
+ /**
+ * The length of coordinate sequence (the number of entries).
+ *
+ * @return the dimensionality of this position.
+ */
+ @Override
+ public int getDimension() {
+ return coordinates.length;
+ }
+
+ /**
+ * Returns the coordinate reference system in which the coordinates are
given.
+ * May be {@code null} if this particular {@code DirectPosition} is
included
+ * in a larger object with such a reference to a <abbr>CRS</abbr>.
+ *
+ * @return the coordinate reference system, or {@code null}.
+ */
+ @Override
+ public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+ return crs;
+ }
+
+ /**
+ * Returns a sequence of numbers that hold the coordinates of this
position in its reference system.
+ *
+ * @return a copy of the coordinates array.
+ */
+ @Override
+ public double[] getCoordinates() {
+ return coordinates.clone();
+ }
+
+ /**
+ * Returns the coordinate at the specified dimension.
+ *
+ * @param dimension the dimension in the range 0 to {@linkplain
#getDimension() dimension}-1.
+ * @return the coordinate at the specified dimension.
+ * @throws IndexOutOfBoundsException if the specified dimension is out of
bounds.
+ */
+ @Override
+ public double getCoordinate(final int dimension) throws
IndexOutOfBoundsException {
+ return coordinates[dimension];
+ }
+
+ /**
+ * @hidden because nothing new to said.
+ */
+ @Override
+ public String toString() {
+ return toString(this, ArraysExt.isSinglePrecision(coordinates));
+ }
+
+ /**
+ * @hidden because nothing new to said.
+ */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(coordinates) + Objects.hashCode(crs);
+ }
+
+ /**
+ * @hidden because nothing new to said.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (object instanceof ImmutableDirectPosition) {
+ final var that = (ImmutableDirectPosition) object;
+ return Arrays.equals(coordinates, that.coordinates) &&
Objects.equals(crs, that.crs);
+ }
+ return super.equals(object); // Comparison of other
implementation classes.
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java
index a47b29e745..33e8e65e75 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/ImmutableEnvelope.java
@@ -29,18 +29,13 @@ import org.opengis.coordinate.MismatchedDimensionException;
import org.opengis.coordinate.MismatchedCoordinateMetadataException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.metadata.extent.GeographicBoundingBox;
-
-import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
+import org.apache.sis.util.ArgumentChecks;
/**
* An immutable {@code Envelope} (a minimum bounding box or rectangle) of
arbitrary dimension.
- * This class is final in order to ensure that the immutability contract
cannot be broken
- * (assuming not using <i>Java Native Interface</i> or reflections).
- *
- * <h2>Immutability and thread safety</h2>
* This final class is immutable and thus inherently thread-safe if the {@link
CoordinateReferenceSystem}
- * instance given to the constructor is immutable. This is usually the case in
Apache SIS.
+ * instance given to the constructor is immutable. This is usually the case in
Apache <abbr>SIS</abbr>.
*
* @author Cédric Briançon (Geomatys)
* @author Martin Desruisseaux (IRD, Geomatys)
@@ -82,7 +77,7 @@ public final class ImmutableEnvelope extends ArrayEnvelope
implements Serializab
{
super(lowerCorner, upperCorner);
this.crs = crs;
- ensureDimensionMatches("crs", getDimension(), crs);
+ ArgumentChecks.ensureDimensionMatches("crs", getDimension(), crs);
}
/**
@@ -130,7 +125,7 @@ public final class ImmutableEnvelope extends ArrayEnvelope
implements Serializab
{
super(envelope);
this.crs = crs;
- ensureDimensionMatches("crs", getDimension(), crs);
+ ArgumentChecks.ensureDimensionMatches("crs", getDimension(), crs);
}
/**
@@ -157,7 +152,7 @@ public final class ImmutableEnvelope extends ArrayEnvelope
implements Serializab
{
super(wkt);
this.crs = crs;
- ensureDimensionMatches("crs", getDimension(), crs);
+ ArgumentChecks.ensureDimensionMatches("crs", getDimension(), crs);
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
index a55de2d6ad..16b670c870 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CRS.java
@@ -19,6 +19,7 @@ package org.apache.sis.referencing;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
+import java.util.BitSet;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Filter;
@@ -93,7 +94,6 @@ import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.OptionalCandidate;
import org.apache.sis.util.Utilities;
-import org.apache.sis.util.internal.shared.Numerics;
import org.apache.sis.util.internal.shared.Constants;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.Logging;
@@ -165,6 +165,11 @@ public final class CRS {
*/
static final int BIDIMENSIONAL = 2;
+ /**
+ * The {@value} value, for identifying code that assume three-dimensional
objects.
+ */
+ static final int TRIDIMENSIONAL = 3;
+
/**
* Do not allow instantiation of this class.
*/
@@ -1229,22 +1234,125 @@ public final class CRS {
}
/**
- * Gets or creates a coordinate reference system with a subset of the
dimensions of the given CRS.
+ * Returns a mask of the dimensions where a {@code subCRS} element is
found in the given <abbr>CRS</abbr>.
+ * If the given {@code crs} is {@linkplain #equivalent equivalent} to an
element of the {@code subCRS} array,
+ * then this method returns a {@link BitSet} in which all bits are set to
{@code true} in the range from 0 to
+ * <var>n</var>−1, where <var>n</var> is the number of dimensions of
{@code crs}.
+ * Otherwise, if {@code crs} is an instance of {@link CompoundCRS}, then
the {@code crs} components are traversed
+ * recursively and compared in the same way as described above, except
that the range of bits set to {@code true}
+ * does not start at index 0. Instead, the range starts at the index of
the first dimension of the matched
+ * {@code crs} component. If no match is found, then the returned {@link
BitSet} is empty.
+ *
+ * <h4>Example</h4>
+ * The following snippet gets the dimensions of the temporal and vertical
components of a <abbr>CRS</abbr>:
+ *
+ * {@snippet lang="java" :
+ * CoordinateReferenceSystem crs = ...;
+ * SingleCRS temporal = CRS.getTemporalComponent(crs);
+ * SingleCRS vertical = CRS.getVerticalComponent(crs, false);
+ * BitSet mask = CRS.locateDimensions(crs, vertical, temporal);
+ * int[] dimensions = mask.stream().toArray();
+ * }
+ *
+ * <p><b>Tip 1:</b> if only the index of the first dimension is desired,
{@code mask.stream().toArray()}
+ * can be replaced by <code>mask.{@linkplain BitSet#nextSetBit(int)
nextSetBit}(0)</code>.</p>
+ *
+ * <p><b>Tip 2:</b> for locating all dimensions <em>except</em> the
vertical and temporal ones, use
+ * <code>mask.{@linkplain BitSet#flip(int, int) flip}(0,
CRS.getDimensionOrZero(crs))</code>.</p>
+ *
+ * <h4>Null values</h4>
+ * This method is null-safe: if {@code crs} or {@code subCRS} is {@code
null}, this method returns an empty set.
+ * If {@code subCRS} contains {@code null} elements, these elements are
ignored. The latter makes easy to use
+ * directly the return value of methods such as {@link
#getTemporalComponent(CoordinateReferenceSystem)}.
+ *
+ * @param crs the <abbr>CRS</abbr> for which the indexes of some
dimensions are wanted.
+ * @param subCRS the <abbr>CRS</abbr>s to compare with {@code crs} or
{@code crs} components.
+ * @return indexes of the dimensions where a {@code subCRS} element is
found in {@code crs}.
+ *
+ * @see #selectDimensions(CoordinateReferenceSystem, BitSet,
SeparationMode)
+ *
+ * @since 1.6
+ */
+ public static BitSet locateDimensions(final CoordinateReferenceSystem crs,
final SingleCRS... subCRS) {
+ final var mask = new BitSet();
+ if (subCRS != null) {
+ int lower = 0;
+ for (final CoordinateReferenceSystem component :
getSingleComponents(crs)) {
+ final int upper = lower + getDimensionOrZero(component);
+ for (final CoordinateReferenceSystem search : subCRS) {
+ if (equivalent(component, search)) {
+ mask.set(lower, upper);
+ }
+ }
+ lower = upper;
+ }
+ }
+ return mask;
+ }
+
+ /**
+ * Gets or creates a coordinate reference system with a subset of the
dimensions of the given <abbr>CRS</abbr>.
+ * The dimensions to retain are specified by a mask. The bit at index 0
specifies whether to retain dimension 0,
+ * the bit at index 1 specifies whether to retain dimension 1, <i>etc</i>.
After this method call,
+ * the given mask is updated to the dimensions that the {@code crs}
dimensions that were effectively retained.
+ * The return value is always a <abbr>CRS</abbr> with axes in the same
order as the given {@code crs}.
+ *
+ * <h4>Ellipsoidal height</h4>
+ * This method can transform a three-dimensional geographic
<abbr>CRS</abbr> into a two-dimensional geographic
+ * <abbr>CRS</abbr>, i.e. this method can do the converse of {@link
#compound(CoordinateReferenceSystem...)}.
+ * This method can also extract the {@linkplain
CommonCRS.Vertical#ELLIPSOIDAL ellipsoidal height}
+ * from a three-dimensional geographic <abbr>CRS</abbr>, but this is
generally not recommended because
+ * ellipsoidal heights make little sense without the (<var>latitude</var>,
<var>longitude</var>) coordinates.
+ *
+ * <h4>Unseparable <abbr>CRS</abbr></h4>
+ * It is illegal to extract only the latitude or longitude axis from a
geodetic <abbr>CRS</abbr>.
+ * Similar constraints exist also for projected and engineering
<abbr>CRS</abbr>. If {@code crs}
+ * or a component of {@code crs} cannot be separated as requested by the
{@code dimensions} mask,
+ * then the behavior of this method depends on the {@code mode}
enumeration value:
+ *
+ * <ul>
+ * <li>If {@link SeparationMode#EXACT}, then a {@link FactoryException}
is thrown.</li>
+ * <li>If {@link SeparationMode#OMIT_UNSEPARABLE}, then the result may
contain less dimensions than requested.</li>
+ * <li>If {@link SeparationMode#WHOLE_UNSEPARABLE}, then the result may
contain more dimensions than requested.</li>
+ * </ul>
+ *
+ * @param crs the <abbr>CRS</abbr> to reduce the dimensionality, or
{@code null} if none.
+ * @param mask on input, the dimensions to select. On output, the
dimensions effectively selected.
+ * @param mode action to take if the {@code crs} cannot be separated in
components at the requested dimensions.
+ * @return a coordinate reference system for the given dimensions, or
{@code null} if the given {@code crs} was null.
+ * @throws IllegalArgumentException if the given {@code mask} is invalid.
+ * @throws FactoryException if this method needs to create a new
<abbr>CRS</abbr> and that operation failed.
+ *
+ * @see #locateDimensions(CoordinateReferenceSystem, SingleCRS...)
+ * @see #compound(CoordinateReferenceSystem...)
+ *
+ * @since 1.6
+ */
+ public static CoordinateReferenceSystem selectDimensions(final
CoordinateReferenceSystem crs, final BitSet mask, final SeparationMode mode)
+ throws FactoryException
+ {
+ ArgumentChecks.ensureNonNull("mask", mask);
+ ArgumentChecks.ensureNonNull("mode", mode);
+ return (crs == null) ? null : new Separator(mask, mode).reduce(crs);
+ }
+
+ /**
+ * Gets or creates a coordinate reference system with a subset of the
dimensions of the given <abbr>CRS</abbr>.
* This method can be used for dimensionality reduction, but not for
changing axis order.
* The specified dimensions are used as if they were in strictly
increasing order without duplicated values.
*
* <h4>Ellipsoidal height</h4>
- * This method can transform a three-dimensional geographic CRS into a
two-dimensional geographic CRS.
- * In this aspect, this method is the converse of {@link
#compound(CoordinateReferenceSystem...)}.
+ * This method can transform a three-dimensional geographic
<abbr>CRS</abbr> into a two-dimensional geographic
+ * <abbr>CRS</abbr>, i.e. this method can do the converse of {@link
#compound(CoordinateReferenceSystem...)}.
* This method can also extract the {@linkplain
CommonCRS.Vertical#ELLIPSOIDAL ellipsoidal height}
- * from a three-dimensional geographic CRS, but this is generally not
recommended since ellipsoidal
- * heights make little sense without their (<var>latitude</var>,
<var>longitude</var>) locations.
+ * from a three-dimensional geographic <abbr>CRS</abbr>, but this is
generally not recommended because
+ * ellipsoidal heights make little sense without the (<var>latitude</var>,
<var>longitude</var>) coordinates.
*
- * @param crs the CRS to reduce the dimensionality, or {@code
null} if none.
- * @param dimensions the dimensions to retain. The dimensions will be
taken in increasing order, ignoring duplicated values.
- * @return a coordinate reference system for the given dimensions. May be
the given {@code crs}, which may be {@code null}.
- * @throws IllegalArgumentException if the given array is empty or if the
array contains invalid indices.
- * @throws FactoryException if this method needed to create a new CRS and
that operation failed.
+ * @param crs the <abbr>CRS</abbr> to reduce the dimensionality,
or {@code null} if none.
+ * @param dimensions the dimensions to retain. Will be taken in
increasing order, ignoring duplicated values.
+ * @return a coordinate reference system for the given dimensions, or
{@code null} if the given {@code crs} was null.
+ * @throws IllegalArgumentException if the content of the given {@code
dimensions} array is invalid.
+ * @throws FactoryException if this method needs to create a new
<abbr>CRS</abbr> and that operation failed.
*
* @see #getComponentAt(CoordinateReferenceSystem, int, int)
* @see #compound(CoordinateReferenceSystem...)
@@ -1254,25 +1362,46 @@ public final class CRS {
public static CoordinateReferenceSystem selectDimensions(final
CoordinateReferenceSystem crs, final int... dimensions)
throws FactoryException
{
- final var components = selectComponents(crs, dimensions);
- return components.isEmpty() ? null :
compound(components.toArray(CoordinateReferenceSystem[]::new));
+ ArgumentChecks.ensureNonNull("dimensions", dimensions);
+ return (crs == null) ? null : new Separator(dimensions).reduce(crs);
}
/**
- * Gets or creates CRS components for a subset of the dimensions of the
given <abbr>CRS</abbr>.
- * The method performs the same work as {@link
#selectDimensions(CoordinateReferenceSystem, int...)}
- * except that it does not build new {@link CompoundCRS} instances when
the specified dimensions span
- * more than one {@linkplain DefaultCompoundCRS#getComponents() component}.
- * Instead, the components are returned directly.
+ * Gets or creates <abbr>CRS</abbr> components for a subset of the
dimensions of the given <abbr>CRS</abbr>.
+ * This method does the same work as <code>{@linkplain
#selectDimensions(CoordinateReferenceSystem, BitSet,
+ * SeparationMode) selectDimensions}(crs, mask, mode)</code>, but without
the final step creating a
+ * {@link CompoundCRS} from the selected components.
+ *
+ * @param crs the <abbr>CRS</abbr> from which to get a subset of the
components, or {@code null} if none.
+ * @param mask on input, the dimensions to select. On output, the
dimensions effectively selected.
+ * @param mode action to take if the {@code crs} cannot be separated in
components at the requested dimensions.
+ * @return components in the specified dimensions, or an empty list if the
specified {@code crs} is {@code null}.
+ * @throws IllegalArgumentException if the content of the given {@code
dimensions} array is invalid.
+ * @throws FactoryException if this method needs to create a new
<abbr>CRS</abbr> and that operation failed.
*
- * <p>While this method does not create new {@code CompoundCRS} instances,
it may create other kinds
- * of CRS for handling ellipsoidal height as documented in the {@code
selectDimensions(…)} method.</p>
+ * @see #selectDimensions(CoordinateReferenceSystem, BitSet,
SeparationMode)
+ *
+ * @since 1.6
+ */
+ public static List<CoordinateReferenceSystem> selectComponents(final
CoordinateReferenceSystem crs,
+ final BitSet mask, final SeparationMode mode) throws
FactoryException
+ {
+ ArgumentChecks.ensureNonNull("mask", mask);
+ ArgumentChecks.ensureNonNull("mode", mode);
+ return (crs == null) ? List.of() : new Separator(mask,
mode).components(crs);
+ }
+
+ /**
+ * Gets or creates <abbr>CRS</abbr> components for a subset of the
dimensions of the given <abbr>CRS</abbr>.
+ * This method does the same work as <code>{@linkplain
#selectDimensions(CoordinateReferenceSystem, int...)
+ * selectDimensions}(crs, dimensions)</code>, but without the final step
creating a {@link CompoundCRS} from
+ * the selected components.
*
- * @param crs the CRS from which to get a subset of the
components, or {@code null} if none.
- * @param dimensions the dimensions to retain. The dimensions will be
taken in increasing order, ignoring duplicated values.
+ * @param crs the <abbr>CRS</abbr> from which to get a subset of
the components, or {@code null} if none.
+ * @param dimensions the dimensions to retain. Will be taken in
increasing order, ignoring duplicated values.
* @return components in the specified dimensions, or an empty list if the
specified {@code crs} is {@code null}.
- * @throws IllegalArgumentException if the given array is empty or if the
array contains invalid indices.
- * @throws FactoryException if this method needed to create a new CRS and
that operation failed.
+ * @throws IllegalArgumentException if the content of the given {@code
dimensions} array is invalid.
+ * @throws FactoryException if this method needs to create a new
<abbr>CRS</abbr> and that operation failed.
*
* @see #selectDimensions(CoordinateReferenceSystem, int...)
*
@@ -1282,76 +1411,244 @@ public final class CRS {
throws FactoryException
{
ArgumentChecks.ensureNonNull("dimensions", dimensions);
- final int dimension = getDimensionOrZero(crs);
- long selected = 0;
- if (crs != null) {
- for (final int d : dimensions) {
- if (Objects.checkIndex(d, dimension) >= Long.SIZE) {
- throw new
ArithmeticException(Errors.format(Errors.Keys.ExcessiveNumberOfDimensions_1,
d+1));
+ return (crs == null) ? List.of() : new
Separator(dimensions).components(crs);
+ }
+
+ /**
+ * Action to take when a <abbr>CRS</abbr> cannot be separated in
components at the requested dimensions.
+ * For example, a two-dimensional geographic <abbr>CRS</abbr> cannot be
separated in a <abbr>CRS</abbr>
+ * containing only the latitude or only the longitude axis. If only the
first dimension of such geographic
+ * <abbr>CRS</abbr> is requested, the action can be to throw an exception
({@link #EXACT}),
+ * omit the unseparable geographic <abbr>CRS</abbr> from the separation
result ({@link #OMIT_UNSEPARABLE}),
+ * or keep the whole geographic <abbr>CRS</abbr> with all its dimensions
({@link #WHOLE_UNSEPARABLE}).
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.6
+ *
+ * @see #selectDimensions(CoordinateReferenceSystem, BitSet,
SeparationMode)
+ *
+ * @since 1.6
+ */
+ public enum SeparationMode {
+ /**
+ * Separation result must contain exactly the requested dimensions.
+ * If the request cannot be satisfied, then a {@link FactoryException}
will be throw.
+ */
+ EXACT,
+
+ /**
+ * Separation result contains only the components that can satisfy the
requested dimensions.
+ * If a <abbr>CRS</abbr> cannot be separated in components at the
requested dimensions, that
+ * <abbr>CRS</abbr> is excluded from the result. In other words, the
result does not contain
+ * any dimension that was not requested, but some requested dimensions
may be ignored.
+ */
+ OMIT_UNSEPARABLE,
+
+ /**
+ * Separation result contains components for all the requested
dimensions. The result contains
+ * all requested dimensions, but may also contain some dimensions that
were not requested.
+ */
+ WHOLE_UNSEPARABLE
+ }
+
+ /**
+ * Helper class for extracting some components of a <abbr>CRS</abbr>.
+ * The dimensions of the desired components are specified by a mask as a
{@link BitSet}.
+ * It is possible to request the two-dimensional horizontal part of a
three-dimensional
+ * geographic or projected <abbr>CRS</abbr>. It is also possible to
request the ellipsoidal
+ * height of a 3D geographic or projected <abbr>CRS</abbr>, but this is
not recommended.
+ */
+ private static final class Separator {
+ /** Mask of dimensions of the components to extract. */
+ private final BitSet mask;
+
+ /** Action when a <abbr>CRS</abbr> cannot be separated in components
at the requested dimensions. */
+ private final SeparationMode mode;
+
+ /** The components selected from the specified dimensions. */
+ private final List<CoordinateReferenceSystem> components;
+
+ /** Next range of dimensions to select. */
+ private int lower, upper;
+
+ /**
+ * Creates a new separator for the specified dimensions.
+ *
+ * @param dimensions the dimensions to retain. Will be taken in
increasing order, ignoring duplicated values.
+ * @throws IllegalArgumentException if {@code dimensions} array
contains a negative index.
+ */
+ Separator(final int[] dimensions) {
+ this(new BitSet(), SeparationMode.EXACT);
+ for (int i : dimensions) {
+ try {
+ mask.set(i);
+ } catch (IndexOutOfBoundsException e) {
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.IndexOutOfBounds_1, i), e);
}
- selected |= (1L << d);
}
- if (selected == 0) {
- throw new
IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1,
"dimensions"));
+ }
+
+ /**
+ * Creates a new separator for the specified dimensions specified as a
mask.
+ *
+ * @param mask on input, the dimensions to select. On output, the
dimensions effectively selected.
+ * @param mode action to take if the {@code crs} cannot be separated
in components at the requested dimensions.
+ */
+ Separator(final BitSet mask, final SeparationMode mode) {
+ this.mask = mask;
+ this.mode = mode;
+ components = new ArrayList<>(mask.cardinality());
+ }
+
+ /**
+ * Gets or creates a coordinate reference system with a subset of the
dimensions of the given <abbr>CRS</abbr>.
+ *
+ * @param crs the <abbr>CRS</abbr> to reduce the dimensionality.
+ * @return a coordinate reference system for the given dimensions.
+ * @throws FactoryException if this method needs to create a new
<abbr>CRS</abbr> and that operation failed.
+ *
+ * @see #selectDimensions(CoordinateReferenceSystem, BitSet,
SeparationMode)
+ */
+ CoordinateReferenceSystem reduce(final CoordinateReferenceSystem crs)
throws FactoryException {
+ return
compound(components(crs).toArray(CoordinateReferenceSystem[]::new));
+ }
+
+ /**
+ * Gets or creates <abbr>CRS</abbr> components for a subset of the
dimensions of the given <abbr>CRS</abbr>.
+ *
+ * @param crs the <abbr>CRS</abbr> from which to get a subset of the
components, or {@code null}.
+ * @return components in the specified dimensions, or an empty list if
the specified {@code crs} is {@code null}.
+ * @throws FactoryException if this method needs to create a new
<abbr>CRS</abbr> and that operation failed.
+ *
+ * @see #selectComponents(CoordinateReferenceSystem, BitSet,
SeparationMode)
+ */
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ List<CoordinateReferenceSystem> components(final
CoordinateReferenceSystem crs) throws FactoryException {
+ final int dimension = getDimensionOrZero(crs);
+ if (mode != SeparationMode.OMIT_UNSEPARABLE) {
+ final int i = mask.nextSetBit(dimension);
+ if (i >= 0) {
+ throw new
IllegalArgumentException(Errors.format(Errors.Keys.IndexOutOfBounds_1, i));
+ }
}
+ lower = mask.nextSetBit(0);
+ if (lower >= 0 && lower < dimension) {
+ upper = mask.nextClearBit(lower + 1);
+ reduce(crs, 0, dimension);
+ }
+ return components;
}
- final var components = new
ArrayList<CoordinateReferenceSystem>(Long.bitCount(selected));
- reduce(0, crs, dimension, selected, components);
- return components;
- }
- /**
- * Adds the components of reduced CRS into the given list.
- * This method may invoke itself recursively for walking through compound
CRS.
- *
- * @param previous number of dimensions of previous CRS.
- * @param crs the CRS for which to select components.
- * @param dimension number of dimensions of {@code crs}.
- * @param selected bitmask of dimensions to select.
- * @param addTo where to add CRS components.
- * @return new bitmask after removal of dimensions of the components added
to {@code addTo}.
- */
- private static long reduce(int previous, final CoordinateReferenceSystem
crs, int dimension, long selected,
- final List<CoordinateReferenceSystem> addTo)
- throws FactoryException
- {
- final long current = (Numerics.bitmask(dimension) - 1) << previous;
- final long intersect = selected & current;
-choice: if (intersect != 0) {
- if (intersect == current) {
- addTo.add(crs);
- selected &= ~current;
- } else if (crs instanceof CompoundCRS) {
+ /**
+ * Adds selected components of the given {@code crs} into the {@link
#components} list.
+ * This method may invoke itself recursively for walking through
compound <abbr>CRS</abbr>.
+ *
+ * <p><b>Precondition:</b> caller must ensure that {@code lower} is
between {@code offset}
+ * inclusive and {@code limit} exclusive. This is not verified by this
method.</p>
+ *
+ * @param crs the <abbr>CRS</abbr> for which to select components.
+ * @param offset index of the first dimension of {@code crs} in the
{@link #mask}.
+ * @param limit index after the last dimension of {@code crs} in
the {@link #mask}.
+ * @return whether this method has added the given {@code crs} fully.
+ */
+ private boolean reduce(final CoordinateReferenceSystem crs, final int
offset, final int limit)
+ throws FactoryException
+ {
+ assert lower >= offset && lower < limit : lower;
+ /*
+ * Unambiguous case where the next requested component is the
whole `crs`.
+ * It may be a `CompoundCRS`, taken without separation of its
components.
+ */
+ if (lower == offset && upper >= limit) {
+ return components.add(crs);
+ }
+ /*
+ * Decompose the `crs` in its components and repeat the operation
for each of them.
+ * After each iteration, this block needs to ensure that `lower`
and `upper` are up-to-date.
+ */
+ if (crs instanceof CompoundCRS) {
+ int end = offset;
+ boolean addedFully = true;
+ final int index = components.size();
for (final CoordinateReferenceSystem component :
((CompoundCRS) crs).getComponents()) {
- dimension = getDimensionOrZero(component);
- selected = reduce(previous, component, dimension,
selected, addTo);
- if ((selected & current) == 0) break; // Stop if
it would be useless to continue.
- previous += dimension;
+ final int start = end;
+ end += getDimensionOrZero(component);
+ if (lower >= end) {
+ addedFully = false;
+ } else {
+ addedFully &= reduce(component, start, end);
+ lower = mask.nextSetBit(end);
+ if (lower < 0) break; // Stop if it would be
useless to continue.
+ if (lower >= upper) {
+ upper = mask.nextClearBit(lower + 1);
+ }
+ }
}
- } else if (dimension == 3) {
- final GeodeticCRS baseCRS;
- if (crs instanceof GeodeticCRS) {
- baseCRS = (GeodeticCRS) crs;
- } else if (crs instanceof ProjectedCRS) {
- baseCRS = ((ProjectedCRS) crs).getBaseCRS();
- } else {
- break choice;
+ /*
+ * If all components were added, replace the components by the
whole `crs` without
+ * updating the mask, because the mask was already updated by
the recursive calls.
+ * Note that in `OMIT_UNSEPARABLE` mode, it is possible that
no component was added.
+ */
+ if (addedFully && end == limit) {
+ components.subList(index, components.size()).clear();
+ return components.add(crs);
}
- final boolean isVertical = Long.bitCount(intersect) == 1;
// Presumed for now, verified later.
- final int verticalDimension =
Long.numberOfTrailingZeros((isVertical ? intersect : ~intersect) >>> previous);
- final CoordinateSystemAxis verticalAxis =
crs.getCoordinateSystem().getAxis(verticalDimension);
- if (AxisDirections.isVertical(verticalAxis.getDirection()))
try {
- addTo.add(new EllipsoidalHeightSeparator(baseCRS,
isVertical).separate((SingleCRS) crs));
- selected &= ~current;
+ return false;
+ }
+ /*
+ * Special case for the decomposition of a three-dimensional
geographic CRS into a horizontal
+ * or a vertical component. Extracting the vertical component is
illegal according ISO 19111,
+ * but sometime useful as temporary information.
+ */
+ GeodeticCRS baseCRS = null;
+ if (crs instanceof GeodeticCRS) {
+ baseCRS = (GeodeticCRS) crs;
+ } else if (crs instanceof ProjectedCRS) {
+ baseCRS = ((ProjectedCRS) crs).getBaseCRS();
+ }
+ RuntimeException cause = null;
+ if (baseCRS != null) {
+ int i = lower;
+ int dimension = 0; // Number of dimensions.
+ boolean isVertical = false; // Whether the requested
dimension is the vertical one.
+ do {
+ dimension++;
+ isVertical |=
AxisDirections.isVertical(crs.getCoordinateSystem().getAxis(i).getDirection());
+ i = mask.nextSetBit(i + 1);
+ } while (i >= 0 && i < limit);
+ if (dimension == (isVertical ? 1 : BIDIMENSIONAL)) try {
+ components.add(new EllipsoidalHeightSeparator(baseCRS,
isVertical).separate((SingleCRS) crs));
+ return false;
} catch (IllegalArgumentException | ClassCastException e) {
- throw new
FactoryException(Resources.format(Resources.Keys.CanNotSeparateCRS_1,
crs.getName()));
+ cause = e;
}
}
+ /*
+ * Only some dimensions of the CRS are specified, but cannot
separate `crs`.
+ * The failure to separate may be because the CRS is not geodetic,
or because
+ * an exception occurred while trying to separate the geodetic CRS.
+ */
+ final boolean full;
+ switch (mode) {
+ case OMIT_UNSEPARABLE: {
+ mask.clear(offset, limit);
+ full = false;
+ break;
+ }
+ case WHOLE_UNSEPARABLE: {
+ mask.set(offset, limit);
+ full = components.add(crs);
+ break;
+ }
+ default: {
+ throw new
FactoryException(Resources.format(Resources.Keys.CanNotSeparateCRS_1,
crs.getName()), cause);
+ }
+ }
+ if (cause != null) {
+ Logging.recoverableException(LOGGER, CRS.class,
"selectComponents", cause);
+ }
+ return full;
}
- if ((selected & current) != 0) {
- throw new
FactoryException(Resources.format(Resources.Keys.CanNotSeparateCRS_1,
crs.getName()));
- }
- return selected;
}
/**
@@ -1467,7 +1764,7 @@ choice: if (intersect != 0) {
case BIDIMENSIONAL: {
return (SingleCRS) crs;
}
- case 3: {
+ case TRIDIMENSIONAL: {
/*
* The CRS would be horizontal if we can remove the vertical
axis. CoordinateSystems.replaceAxes(…)
* will do this task for us. We can verify if the operation
has been successful by checking that
@@ -1569,7 +1866,7 @@ choice: if (intersect != 0) {
}
} while ((a = !a) == allowCreateEllipsoidal);
}
- if (allowCreateEllipsoidal && horizontalCode(crs) == 3) {
+ if (allowCreateEllipsoidal && horizontalCode(crs) == TRIDIMENSIONAL) {
final CoordinateSystem cs = crs.getCoordinateSystem();
final int i = AxisDirections.indexOfColinear(cs, AxisDirection.UP);
if (i >= 0) {
@@ -1651,25 +1948,23 @@ choice: if (intersect != 0) {
*
* This method guaranteed that the returned list is a flat one as shown on
the right side.
* Note that such flat lists are the only one allowed by ISO/OGC standards
for compound CRS.
- * The hierarchical structure is an Apache SIS flexibility.
+ * The hierarchical structure is an Apache <abbr>SIS</abbr> flexibility.
*
* @param crs the coordinate reference system, or {@code null}.
* @return the single coordinate reference systems, or an empty list if
the given CRS is {@code null}.
- * @throws ClassCastException if a CRS is neither a {@link SingleCRS} or a
{@link CompoundCRS}.
+ * @throws ClassCastException if a <abbr>CRS</abbr> is neither a {@link
SingleCRS} or a {@link CompoundCRS}.
*
* @see DefaultCompoundCRS#getSingleComponents()
*/
public static List<SingleCRS> getSingleComponents(final
CoordinateReferenceSystem crs) {
- final List<SingleCRS> singles;
if (crs == null) {
- singles = List.of();
+ return List.of();
} else if (crs instanceof CompoundCRS) {
- singles = ((CompoundCRS) crs).getSingleComponents();
+ return ((CompoundCRS) crs).getSingleComponents();
} else {
// Intentional CassCastException here if the crs is not a
SingleCRS.
- singles = List.of((SingleCRS) crs);
+ return List.of((SingleCRS) crs);
}
- return singles;
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/DirectPositionView.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/DirectPositionView.java
index 37101a406d..e34fa06be7 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/DirectPositionView.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/DirectPositionView.java
@@ -22,7 +22,8 @@ import org.apache.sis.geometry.AbstractDirectPosition;
/**
* A read-only direct position wrapping an array without performing any copy.
- * This class shall be used for temporary objects only (it is not serializable
for this reason).
+ * This class shall be used for temporary objects only, unless the backing
array is short.
+ * This class is not serializable for avoiding serialization of a potentially
large array.
*
* @author Martin Desruisseaux (Geomatys)
*/
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
index 153c5c23d0..676674b804 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/SubOperationInfo.java
@@ -276,13 +276,11 @@ searchSrc: while (sourceComponentIndex <
sourceComponentIsUsed.length) {
*/
int indexOfConstant = targetLowerDimension; // Value for
the default CRS.
final CoordinateReferenceSystem crs =
coordinates.getCoordinateReferenceSystem();
-locate: if (crs != null) {
- indexOfConstant = 0;
- for (SingleCRS component : CRS.getSingleComponents(crs)) {
- if (CRS.equivalent(targetComponent, component)) break
locate;
- indexOfConstant +=
component.getCoordinateSystem().getDimension();
+ if (crs != null) {
+ indexOfConstant = CRS.locateDimensions(crs,
targetComponent).nextSetBit(0);
+ if (indexOfConstant < 0) {
+ return null;
}
- return null;
}
final int d = coordinates.getDimension();
final var c = new double[targetUpperDimension -
targetLowerDimension];
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
index 7f2bdb6302..b919234ef2 100644
---
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CRSTest.java
@@ -19,6 +19,7 @@ package org.apache.sis.referencing;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.List;
import org.opengis.util.FactoryException;
import org.opengis.util.NoSuchIdentifierException;
@@ -407,6 +408,16 @@ public final class CRSTest extends TestCaseWithLogs {
loggings.assertNoUnexpectedLog();
}
+ /**
+ * Tests getting a mask of some CRS components.
+ */
+ @Test
+ public void testLocateDimensions() {
+ // Following currently locate only the temporal CRS because geographic
CRS is not separated in 2D+1D parts.
+ BitSet mask = CRS.locateDimensions(HardCodedCRS.WGS84_4D,
HardCodedCRS.TIME, HardCodedCRS.ELLIPSOIDAL_HEIGHT);
+ assertEquals(BitSet.valueOf(new long[] {0b1000}), mask);
+ }
+
/**
* Tests {@link CRS#selectDimensions(CoordinateReferenceSystem, int[])} in
the simpler case
* where there is no three-dimensional geographic CRS to separate.
diff --git
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
index dd8495506a..8de693b5a4 100644
---
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
+++
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/MultiResolutionImage.java
@@ -39,6 +39,7 @@ import org.apache.sis.storage.base.StoreResource;
import org.apache.sis.storage.base.GridResourceWrapper;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.internal.shared.DirectPositionView;
+import org.apache.sis.referencing.operation.CoordinateOperationContext;
import org.apache.sis.referencing.operation.matrix.MatrixSIS;
import static
org.apache.sis.storage.geotiff.reader.GridGeometryBuilder.BIDIMENSIONAL;
@@ -216,22 +217,29 @@ final class MultiResolutionImage extends
GridResourceWrapper implements StoreRes
}
double[] resolution = domain.getResolution(true);
if (domain.isDefined(GridGeometry.CRS | GridGeometry.ENVELOPE)) try {
- final CoordinateReferenceSystem crs =
domain.getCoordinateReferenceSystem();
CoordinateOperation op = lastOperation;
- if (op == null || !crs.equals(op.getTargetCRS())) {
- final GridGeometry gg = getGridGeometry();
- op = CRS.findOperation(crs, gg.getCoordinateReferenceSystem(),
gg.getGeographicExtent().orElse(null));
+ if (op == null ||
!domain.getCoordinateReferenceSystem().equals(op.getSourceCRS())) {
+ /*
+ * The resolution in the user-supplied domain is associated to
a CRS different than the CRS
+ * of the last resolution that we computed. We must update the
operation from user-supplied
+ * resolution to the units of this grid coverage.
+ */
+ final GridGeometry targetGrid = getGridGeometry();
+ final var context = new CoordinateOperationContext();
+
targetGrid.getGeographicExtent().ifPresent(context::addAreaOfInterest);
+
targetGrid.getConstantCoordinates().ifPresent(context::setConstantCoordinates);
+ op = CRS.findOperation(domain.getCoordinateMetadata(),
targetGrid.getCoordinateMetadata(), context);
lastOperation = op;
}
- final MathTransform sourceToCoverage = op.getMathTransform();
- if (!sourceToCoverage.isIdentity()) {
+ final MathTransform domainToCoverage = op.getMathTransform();
+ if (!domainToCoverage.isIdentity()) {
/*
* If the `domain` grid geometry has a resolution and an
envelope, then it should have
* an extent and a "grid to CRS" transform (otherwise it may
be a `GridGeometry` bug)
*/
DirectPosition poi = new
DirectPositionView.Double(domain.getExtent().getPointOfInterest(PixelInCell.CELL_CENTER));
poi =
domain.getGridToCRS(PixelInCell.CELL_CENTER).transform(poi, null);
- final MatrixSIS derivative =
MatrixSIS.castOrCopy(sourceToCoverage.derivative(poi));
+ final MatrixSIS derivative =
MatrixSIS.castOrCopy(domainToCoverage.derivative(poi));
resolution = derivative.multiply(resolution);
for (int i=0; i<resolution.length; i++) {
resolution[i] = Math.abs(resolution[i]);
diff --git a/netbeans-project/nbproject/project.xml
b/netbeans-project/nbproject/project.xml
index bf34c520bf..4137422c50 100644
--- a/netbeans-project/nbproject/project.xml
+++ b/netbeans-project/nbproject/project.xml
@@ -39,6 +39,7 @@
<word>programmatically</word>
<word>transformative</word>
<word>unary</word>
+ <word>unseparable</word>
<word>untiled</word>
</spellchecker-wordlist>
</configuration>