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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new e3288548dd Be more conservative when creating an EngineeringCRS for a
GridExtent. Such engineering CRS are not expected to be always present.
e3288548dd is described below
commit e3288548dd17f7d5d6c84db76b6deba133093f1b
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Jul 22 19:47:30 2026 +0200
Be more conservative when creating an EngineeringCRS for a GridExtent.
Such engineering CRS are not expected to be always present.
---
.../apache/sis/coverage/grid/GridCRSBuilder.java | 156 +++++++++++++--------
.../org/apache/sis/coverage/grid/GridExtent.java | 83 ++++++-----
.../org/apache/sis/coverage/grid/GridGeometry.java | 37 +++--
.../apache/sis/coverage/grid/GridExtentTest.java | 21 ++-
.../main/org/apache/sis/geometry/Envelopes.java | 12 +-
.../apache/sis/gui/coverage/CoverageCanvas.java | 30 ++--
.../apache/sis/gui/coverage/CoverageControls.java | 2 +-
.../apache/sis/gui/coverage/CoverageExplorer.java | 2 +-
.../org/apache/sis/gui/coverage/GridControls.java | 2 +-
.../main/org/apache/sis/gui/coverage/GridView.java | 2 +-
.../sis/gui/coverage/StyledRenderingData.java | 5 +
.../main/org/apache/sis/gui/map/MapCanvas.java | 26 +++-
12 files changed, 244 insertions(+), 134 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCRSBuilder.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCRSBuilder.java
index 38682be486..7ebf7e57a3 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCRSBuilder.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCRSBuilder.java
@@ -22,7 +22,6 @@ import java.util.Map;
import java.util.HashMap;
import java.util.EnumSet;
import java.util.Locale;
-import java.util.Optional;
import org.opengis.util.FactoryException;
import org.opengis.util.InternationalString;
import org.opengis.metadata.Identifier;
@@ -67,11 +66,14 @@ import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.collection.Containers;
import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.util.internal.shared.Numerics;
import org.apache.sis.util.iso.Types;
import org.apache.sis.measure.Units;
+import org.apache.sis.util.resources.Errors;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.referencing.ObjectDomain;
+import org.opengis.coordinate.MismatchedDimensionException;
/**
@@ -80,8 +82,8 @@ import org.opengis.referencing.ObjectDomain;
* coordinates associated to the grid extent. This class provides two factory
methods:
*
* <ul>
- * <li>{@link #forCoverage()}</li>
- * <li>{@link #forExtentAlone(Matrix, DimensionNameType[])}</li>
+ * <li>{@link #forCoverage(GridGeometry, PixelInCell, Identifier)}</li>
+ * <li>{@link #forExtent(DimensionNameType[], int, Matrix, boolean,
Identifier)}</li>
* </ul>
*
* @author Martin Desruisseaux (IRD, Geomatys)
@@ -211,6 +213,14 @@ final class GridCRSBuilder extends
ReferencingFactoryContainer {
*/
private static final Locale LOCALE = null;
+ /**
+ * Whether to force {@link DerivedCRS} instances.
+ *
+ * @deprecated to be removed after {@link GridGeometry#createImageCRS} has
been removed.
+ */
+ @Deprecated(forRemoval = true)
+ boolean derived;
+
/**
* Creates a new helper class for building a grid coordinate reference
system.
*/
@@ -233,15 +243,14 @@ final class GridCRSBuilder extends
ReferencingFactoryContainer {
* Derived <abbr>CRS</abbr> are preferred as they allow conversions to
geospatial <abbr>CRS</abbr>.
* May return a compound <abbr>CRS</abbr> if the grid geometry has, for
example, a temporal component.
*
- * @param grid grid geometry of the coverage.
- * @param anchor the cell part to map (center or corner).
- * @param derived whether to force {@link DerivedCRS} instances.
- * @param name name of the derived or engineering <abbr>CRS</abbr> to
create.
+ * @param grid grid geometry of the coverage.
+ * @param anchor the cell part to map (center or corner).
+ * @param name name of the derived or engineering <abbr>CRS</abbr> to
create.
* @return a derived, engineering or compound <abbr>CRS</abbr> for cell
indices associated to the grid extent.
* @throws InvalidGeodeticParameterException if characteristics of the
grid geometry disallow this operation.
* @throws FactoryException if another error occurred during the use of a
referencing factory.
*/
- final CoordinateReferenceSystem forCoverage(final GridGeometry grid, final
PixelInCell anchor, final boolean derived, final Identifier name)
+ final CoordinateReferenceSystem forCoverage(final GridGeometry grid, final
PixelInCell anchor, final Identifier name)
throws FactoryException
{
this.anchor = anchor;
@@ -262,7 +271,8 @@ final class GridCRSBuilder extends
ReferencingFactoryContainer {
* We cannot create a derived CRS. Fallback on an engineering CRS with
no
* relationship to any other CRS.
*/
- return forExtent(name, grid.getDimension(),
grid.isDefined(GridGeometry.EXTENT) ? grid.getExtent() : null);
+ DimensionNameType[] types = grid.isDefined(GridGeometry.EXTENT) ?
grid.getExtent().getAxisTypes() : null;
+ return forExtent(types, grid.getDimension(), null, true, name);
}
/**
@@ -271,26 +281,70 @@ final class GridCRSBuilder extends
ReferencingFactoryContainer {
* is possible between two engineering <abbr>CRS</abbr>s. It is
recommended to use an identifier which is unique
* for the grid. It may be, for example, derived from the resource
identifier.
*
- * @param name name of the engineering datum.
- * @param dimension number of dimensions.
- * @param extent extent, or {@code null} if none.
+ * <p>The <abbr>CRS</abbr> type is always engineering. In particular, the
<abbr>CRS</abbr> cannot be temporal
+ * because we do not know the temporal datum origin and because index unit
is not a temporal unit.</p>
+ *
+ * @param types {@link GridExtent#getAxisTypes()} or {@code null}
if there is no extent.
+ * @param dimension number of dimensions of the <abbr>CRS</abbr> to
create.
+ * @param derivative derivative of the transform from cell indices to
envelope coordinates, or {@code null}.
+ * @param isLinear whether the derivative come from a linear transform.
+ * @param name name of the engineering datum.
* @return an engineering <abbr>CRS</abbr> for cell indices associated to
the grid extent.
* @throws FactoryException if an error occurred during the use of a
referencing factory.
+ *
+ * @see GridExtent#toEnvelope(MathTransform, Identifier)
+ * @see GridExtent#typeFromAxes(CoordinateReferenceSystem, int)
*/
- final EngineeringCRS forExtent(final Identifier name, final int dimension,
final GridExtent extent)
+ final EngineeringCRS forExtent(final DimensionNameType[] types,
+ final int dimension,
+ final Matrix derivative,
+ final boolean isLinear,
+ final Identifier name)
throws FactoryException
{
final DimensionNameType[] dimensionNames;
- if (extent != null) {
- dimensionNames = Arrays.copyOf(extent.getAxisTypes(), dimension);
+ final AxisDirection[] directions;
+ final boolean cartesian;
+ if (derivative != null) {
+ dimensionNames = new DimensionNameType[dimension];
+ directions = reorder(directions(types), derivative, types,
dimensionNames);
+ cartesian = isLinear && isOrthogonal(derivative, dimension);
} else {
dimensionNames = new DimensionNameType[dimension];
+ if (types != null) {
+ System.arraycopy(types, 0, dimensionNames, 0, dimension);
+ }
+ directions = directions(dimensionNames);
+ cartesian = true;
}
- final CoordinateSystem cs = createCS(dimension, dimensionNames,
directions(dimensionNames), 1, true);
+ final CoordinateSystem cs = createCS(dimension, dimensionNames,
directions, 1, cartesian);
final EngineeringDatum datum =
getDatumFactory().createEngineeringDatum(properties(name));
return
getCRSFactory().createEngineeringCRS(properties(datum.getName()), datum, cs);
}
+ /**
+ * Returns whether applying a transform with the given derivative would
still have orthogonal axes.
+ * This is {@code true} if each row and each column contains exactly one
non-zero coefficient.
+ */
+ private static boolean isOrthogonal(final Matrix derivative, final int
dimension) {
+ long columns = 0;
+ for (int j = Math.min(derivative.getNumRow(), dimension); --j >= 0;) {
+ boolean found = false;
+ for (int i = Math.min(derivative.getNumCol(), dimension); --i >=
0;) {
+ if (derivative.getElement(j, i) != 0) {
+ if (found || (columns == (columns |=
Numerics.bitmask(i)))) {
+ return false;
+ }
+ found = true;
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
/**
* Creates a derived <abbr>CRS</abbr> with a conversion from the real
world <abbr>CRS</abbr> to grid indices.
* This method may invoke itself recursively for separating a compound
<abbr>CRS</abbr> into its components.
@@ -369,6 +423,7 @@ toGrid: try {
} else try {
derivative = crsToGrid.derivative(null);
} catch (NullPointerException e) {
+ // `GridGeometry.createGridCRS(…)` is the public API that
invoked this method.
Logging.ignorableException(GridExtent.LOGGER,
GridGeometry.class, "createGridCRS", e);
directions = directions(dimensionNames);
break toGrid;
@@ -427,15 +482,6 @@ toGrid: try {
return "Grid based on " + name;
}
- /**
- * Returns the coordinate reference system that we use as a template for
object names.
- * This template uses generic terms such as "Cell indices" for the
<abbr>CRS</abbr> name
- * and "Unknown grid" for the datum.
- */
- private static EngineeringCRS template() {
- return CommonCRS.Engineering.GRID.crs();
- }
-
/**
* Creates the coordinate system for the derived or engineering
<abbr>CRS</abbr> of a grid.
*
@@ -443,7 +489,8 @@ toGrid: try {
* @param dimensionNames names of grid dimension. Shall not be null but
may contain null elements.
* @param directions directions of the axes of the coordinate system
to create. May contain null elements.
* @param labelOffset offset to add to the dimension for producing a
default axis name or abbreviation.
- * @return coordinate system for the grid extent, or {@code null} if it
cannot be inferred.
+ * @param cartesian {@code true} for a Cartesian <abbr>CS</abbr>,
or {@code false} for an affine <abbr>CS</abbr>.
+ * @return coordinate system for the grid extent.
* @throws FactoryException if an error occurred during the use of {@link
CSFactory}.
*/
private CoordinateSystem createCS(final int dimension, final
DimensionNameType[] dimensionNames,
@@ -508,7 +555,7 @@ toGrid: try {
* coordinate system type in last resort.
*/
@SuppressWarnings("LocalVariableHidesMemberVariable")
- final Map<String,?> properties =
properties(template().getCoordinateSystem().getName());
+ final Map<String,?> properties =
properties(CommonCRS.Engineering.GRID.crs().getCoordinateSystem().getName());
final CoordinateSystemAxis axis = axes[0];
switch (dimension) {
case 1: {
@@ -540,6 +587,8 @@ toGrid: try {
/**
* Returns the default axis directions for grid dimensions of the given
name.
+ * The returned array has the same length as the given array, but may
contain
+ * null elements.
*
* @param types grid dimension names.
* @return default axis directions. May contain null elements.
@@ -564,19 +613,30 @@ toGrid: try {
* and if {@code derivative} is the derivative of the <abbr>CRS</abbr> to
grid transform, then this
* method returns the axis directions of the grid. Values that cannot be
mapped are set to null.
*
+ * <p>The {@code source} and {@code target} arrays shall be either both
null, or both non-null.
+ * If non-null, the following should be true:</p>
+ *
+ * <ul>
+ * <li>{@code source.length} = {@code directions.length}</li>
+ * <li>{@code target.length} = length of the returned array</li>
+ * </ul>
+ *
* @param directions the directions to reorder. Shall not be null but
may contain null elements.
* @param derivative derivative of the transform from source to target
<abbr>CRS</abbr>.
- * @param source an optional array to reorder together with {@code
directions}.
+ * @param source array of the same length as {@code directions} to
be reordered as well.
* @param target where to store the result of {@code source}
reordering, or {@code null}.
* @return the reordered axis directions. May contain {@code null}
elements.
*/
private static AxisDirection[] reorder(final AxisDirection[] directions,
final Matrix derivative,
final DimensionNameType[] source,
final DimensionNameType[] target)
{
- final var ordered = new AxisDirection[derivative.getNumRow()];
- for (int j=0; j<ordered.length; j++) {
+ final int numRow, numCol;
+ ensureDimensionMatches("source", numCol = derivative.getNumCol(),
source);
+ ensureDimensionMatches("target", numRow = derivative.getNumRow(),
target);
+ final var ordered = new AxisDirection[numRow];
+ for (int j=0; j<numRow; j++) {
boolean found = false;
- for (int i=0; i<directions.length; i++) {
+ for (int i=0; i<numCol; i++) {
final double m = derivative.getElement(j, i);
if (m != 0) {
if (found) {
@@ -592,7 +652,7 @@ toGrid: try {
selected = AxisDirections.opposite(selected);
}
ordered[j] = selected;
- if (target != null && i < source.length) {
+ if (target != null) {
target[j] = source[i];
}
}
@@ -602,34 +662,16 @@ toGrid: try {
}
/**
- * Builds the coordinate reference system of the result of transforming a
{@link GridExtent}.
- * This is used only in the rare cases where we need to represent an
extent as an envelope.
- * This class converts {@link DimensionNameType} codes into axis names,
abbreviations and directions.
- * It is the converse of {@link
GridExtent#typeFromAxes(CoordinateReferenceSystem, int)}.
- *
- * <p>The <abbr>CRS</abbr> type is always engineering. In particular, the
<abbr>CRS</abbr> cannot be temporal
- * because we do not know the temporal datum origin and because index unit
is not a temporal unit.</p>
+ * Verifies the number of dimensions of an array of axis directions.
*
- * @param derivative derivative of the transform converting grid cell
indices to envelope coordinates.
- * @param types the value of {@link GridExtent#types} or a default
value (shall not be {@code null}).
- * @return <abbr>CRS</abbr> for the grid, or empty if it cannot be built.
- * @throws FactoryException if an error occurred during the use of a
referencing factory.
- *
- * @see GridExtent#toEnvelope(MathTransform)
- * @see GridExtent#typeFromAxes(CoordinateReferenceSystem, int)
+ * @throws MismatchedDimensionException if the dimensions do not match.
*/
- final Optional<EngineeringCRS> forExtentAlone(final Matrix derivative,
final DimensionNameType[] types)
- throws FactoryException
+ private static void ensureDimensionMatches(final String name, final int
expected, final DimensionNameType[] axes)
+ throws MismatchedDimensionException
{
- final int dimension = derivative.getNumRow();
- final var dimensionNames = new DimensionNameType[dimension];
- AxisDirection[] directions = directions(ArraysExt.resize(types,
dimension));
- directions = reorder(directions, derivative, types, dimensionNames);
- final CoordinateSystem cs = createCS(dimension, dimensionNames,
directions, 1, false);
- if (cs == null) {
- return Optional.empty();
+ if (axes != null && axes.length != expected) {
+ throw new MismatchedDimensionException(Errors.format(
+ Errors.Keys.MismatchedDimension_3, name, expected,
axes.length));
}
- final EngineeringCRS template = template();
- return
Optional.of(getCRSFactory().createEngineeringCRS(properties(template.getName()),
template.getDatum(), cs));
}
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
index 6f181ebc24..c259f8a1af 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
@@ -55,7 +55,6 @@ import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.collection.WeakValueHashMap;
-import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.util.internal.shared.Numerics;
import org.apache.sis.util.internal.shared.Strings;
import org.apache.sis.util.internal.shared.DoubleDouble;
@@ -69,6 +68,7 @@ import
org.apache.sis.referencing.internal.shared.ExtendedPrecisionMatrix;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.matrix.MatrixSIS;
import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.referencing.operation.transform.TransformSeparator;
import org.apache.sis.parameter.Parameters;
import org.apache.sis.math.MathFunctions;
@@ -1308,6 +1308,8 @@ public class GridExtent implements GridEnvelope,
LenientComparable, Serializable
/**
* Transforms this grid extent to a "real world" envelope using the given
transform.
* The given transform shall map <em>cell corners</em> to real world
coordinates.
+ * The returned envelope is not associated to any <abbr>CRS</abbr> since
the target
+ * of the given transform is unknown.
*
* @param cornerToCRS a transform from <em>cell corners</em> to real
world coordinates.
* @return this grid extent in real world coordinates. Upper coordinate
values are exclusive.
@@ -1320,13 +1322,48 @@ public class GridExtent implements GridEnvelope,
LenientComparable, Serializable
*/
public GeneralEnvelope toEnvelope(final MathTransform cornerToCRS) throws
TransformException {
ArgumentChecks.ensureNonNull("cornerToCRS", cornerToCRS);
- final GeneralEnvelope envelope = toEnvelope(cornerToCRS, false,
cornerToCRS, null);
- try {
- final Matrix derivative = derivativeAtPOI(cornerToCRS,
PixelInCell.CELL_CORNER);
- final var builder = new GridCRSBuilder();
- builder.forExtentAlone(derivative,
getAxisTypes()).ifPresent(envelope::setCoordinateReferenceSystem);
- } catch (FactoryException | TransformException e) {
- Logging.ignorableException(LOGGER, GridExtent.class, "toEnvelope",
e);
+ return toEnvelope(cornerToCRS, false, cornerToCRS, null);
+ }
+
+ /**
+ * Transforms this grid extent to an envelope using the given transform.
+ * The transform shall map grid <em>cell corners</em> to envelope
coordinates.
+ *
+ * <h4>Envelope coordinate reference system</h4>
+ * If and only if the {@code datum} argument is non-null, the returned
envelope is
+ * assigned an {@link EngineeringCRS} with an engineering datum of the
given name.
+ * Coordinate operations between two <abbr>CRS</abbr>s created by this
method will
+ * be possible only if they were created with the same {@code name}
argument.
+ * It is recommended to use an identifier which is, on a best effort
basis, unique for the grid.
+ * It may be, for example, derived from the
+ * {@linkplain org.apache.sis.storage.GridCoverageResource#getIdentifier()
resource identifier}.
+ *
+ * <p>This method can be used when no "grid to <abbr>CRS</abbr>"
information is available.
+ * Otherwise, {@link GridGeometry} methods should be preferred.</p>
+ *
+ * @param cornerToCRS a transform from <em>cell corners</em> to real
world coordinates.
+ * @param datum name of the engineering datum, or {@code null} for
not creating a <abbr>CRS</abbr>.
+ * @return this grid extent in real world coordinates. Upper coordinate
values are exclusive.
+ * @throws TransformException if the envelope cannot be computed with the
given transform.
+ *
+ * @see GridGeometry#createGridCRS(Identifier, PixelInCell)
+ *
+ * @since 1.7
+ */
+ public GeneralEnvelope toEnvelope(final MathTransform cornerToCRS, final
Identifier datum)
+ throws TransformException
+ {
+ final GeneralEnvelope envelope = toEnvelope(cornerToCRS);
+ if (datum != null) try {
+ envelope.setCoordinateReferenceSystem(new
GridCRSBuilder().forExtent(
+ getAxisTypes(),
+ cornerToCRS.getTargetDimensions(),
+ derivativeAtPOI(cornerToCRS, PixelInCell.CELL_CORNER),
+ cornerToCRS instanceof LinearTransform,
+ datum));
+ } catch (FactoryException e) {
+ // Should never happen because `GridCRSBuilder` uses known
implementations.
+ Logging.recoverableException(LOGGER, GridExtent.class,
"toEnvelope", e);
}
return envelope;
}
@@ -1382,7 +1419,7 @@ public class GridExtent implements GridEnvelope,
LenientComparable, Serializable
/**
* Returns the coordinates of this grid extent in an envelope.
- * The returned envelope has no CRS.
+ * The returned envelope has no <abbr>CRS</abbr>.
*
* @param isHighIncluded whether the upper coordinate values should be
inclusive instead of exclusive.
* @return an envelope with the coordinates of this grid extent,
optionally with exclusive upper values.
@@ -2354,34 +2391,6 @@ public class GridExtent implements GridEnvelope,
LenientComparable, Serializable
}
}
- /**
- * Creates a coordinate reference system for cell indices in this extent.
- * The {@code name} argument will be the name of the engineering datum.
- * Coordinate operations between two <abbr>CRS</abbr>s created by this
method
- * will be possible only if they were created with the same {@code name}
argument.
- * It is recommended to use an identifier which is unique for the grid. It
may be, for example, derived
- * from the {@linkplain
org.apache.sis.storage.GridCoverageResource#getIdentifier() resource
identifier}.
- *
- * <p>This engineering <abbr>CRS</abbr> may be used when no "grid to
<abbr>CRS</abbr>" information is available.
- * Otherwise, {@link GridGeometry#createGridCRS(Identifier, PixelInCell)}
should be preferred.</p>
- *
- * @param name name of the engineering datum.
- * @return an engineering <abbr>CRS</abbr> for cell indices associated to
this grid extent.
- *
- * @see GridGeometry#createGridCRS(Identifier, PixelInCell)
- *
- * @since 1.7
- */
- public EngineeringCRS createGridCRS(final Identifier name) {
- ArgumentChecks.ensureNonNull("name", name);
- try {
- return new GridCRSBuilder().forExtent(name, getDimension(), this);
- } catch (FactoryException e) {
- // Should never happen because `GridCRSBuilder` uses known
implementations.
- throw new BackingStoreException(e);
- }
- }
-
/**
* Compares the specified object with this grid extent for equality.
* This method delegates to {@code equals(object, ComparisonMode.STRICT)}.
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 92660fc262..cc19710e4a 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
@@ -604,8 +604,11 @@ public class GridGeometry implements LenientComparable,
Serializable {
*
* @param extent the extent to validate, or {@code null} if none.
* @param expected the expected number of dimension.
+ * @throws MismatchedDimensionException if the dimensions do not match.
*/
- private static void ensureDimensionMatches(final int expected, final
GridExtent extent) throws MismatchedDimensionException {
+ private static void ensureDimensionMatches(final int expected, final
GridExtent extent)
+ throws MismatchedDimensionException
+ {
if (extent != null) {
final int dimension = extent.getDimension();
if (dimension != expected) {
@@ -1847,10 +1850,16 @@ public class GridGeometry implements LenientComparable,
Serializable {
* transform and no <abbr>CRS</abbr>, then this method returns a new grid
geometry with the resolution set to 1,
* <i>i.e.</i> the resolution is defined as one unit of grid cell.
* With such default, the <abbr>CRS</abbr> become implicitly the
<abbr>CRS</abbr> of the grid.
- * If {@code datum} is non-null, then this implicit assumption is made
explicit
- * by setting the <abbr>CRS</abbr> to the value returned by {@link
GridExtent#createGridCRS(Identified)} and
- * by setting "grid to <abbr>CRS</abbr>" to the identity transform.
+ *
+ * <p>If this {@code GridGeometry} is incomplete and this method returns a
new grid geometry, there is a choice:
* If {@code datum} is null, then the <abbr>CRS</abbr> and "grid to
<abbr>CRS</abbr>" properties stay undefined.
+ * Otherwise, the implicit grid <abbr>CRS</abbr> is made explicit by
adding the following properties:</p>
+ *
+ * <ul>
+ * <li>an explicit identity "grid to <abbr>CRS</abbr>" transform,
and</li>
+ * <li>an {@link EngineeringCRS} associated to an engineering datum
+ * of the name given by the {@code datum} argument.</li>
+ * </ul>
*
* <h4>Usage in context of image pyramids</h4>
* The "grid to <abbr>CRS</abbr>" information is sometime missing, for
example because a file
@@ -1876,9 +1885,15 @@ public class GridGeometry implements LenientComparable,
Serializable {
MathTransform tr = null;
ImmutableEnvelope env = envelope;
if (env == null && datum != null && extent != null) {
- final GeneralEnvelope t = extent.toEnvelope(false);
- t.setCoordinateReferenceSystem(extent.createGridCRS(datum));
- env = new ImmutableEnvelope(t);
+ final GeneralEnvelope bounds = extent.toEnvelope(false);
+ try {
+ bounds.setCoordinateReferenceSystem(new
GridCRSBuilder().forExtent(
+ extent.getAxisTypes(), extent.getDimension(), null,
true, datum));
+ } catch (FactoryException e) {
+ // Should never happen because `GridCRSBuilder` uses known
implementations.
+ recoverableException("defaultToGridCRS", e);
+ }
+ env = new ImmutableEnvelope(bounds);
tr = MathTransforms.identity(env.getDimension());
}
return new GridGeometry(extent, tr, tr, env, newResolution, 0);
@@ -1918,7 +1933,9 @@ public class GridGeometry implements LenientComparable,
Serializable {
final var id = new
org.apache.sis.referencing.ImmutableIdentifier(null, null, name);
try {
// Note: the `true` boolean argument can be removed after the
removal of this method.
- final CoordinateReferenceSystem crs = new
GridCRSBuilder().forCoverage(this, anchor, true, id);
+ var builder = new GridCRSBuilder();
+ builder.derived = true;
+ final CoordinateReferenceSystem crs = builder.forCoverage(this,
anchor, id);
return (DerivedCRS)
org.apache.sis.referencing.CRS.getSingleComponents(crs).get(0);
} catch (FactoryException e) {
throw new BackingStoreException(e);
@@ -1956,14 +1973,14 @@ public class GridGeometry implements LenientComparable,
Serializable {
* @throws FactoryException if another error occurred during the use of a
referencing factory.
*
* @see #createTransformTo(GridGeometry, PixelInCell)
- * @see GridExtent#createGridCRS(Identifier)
+ * @see GridExtent#toEnvelope(MathTransform, Identifier)
*
* @since 1.7
*/
public CoordinateReferenceSystem createGridCRS(final Identifier name,
final PixelInCell anchor) throws FactoryException {
ArgumentChecks.ensureNonNull("name", name);
ArgumentChecks.ensureNonNull("anchor", anchor);
- return new GridCRSBuilder().forCoverage(this, anchor, false, name);
+ return new GridCRSBuilder().forCoverage(this, anchor, name);
}
/**
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java
index 9db31faf35..1eea817b3c 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -21,13 +21,16 @@ import java.util.Locale;
import java.io.IOException;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
+import org.opengis.metadata.Identifier;
import org.opengis.metadata.spatial.DimensionNameType;
import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.crs.EngineeringCRS;
import org.opengis.referencing.cs.AxisDirection;
import org.apache.sis.geometry.AbstractEnvelope;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.GeneralDirectPosition;
import org.apache.sis.coverage.SubspaceNotSpecifiedException;
+import org.apache.sis.referencing.ImmutableIdentifier;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.matrix.Matrix3;
@@ -515,7 +518,7 @@ public final class GridExtentTest extends TestCase {
}
/**
- * Tests {@link GridExtent#toEnvelope(MathTransform)} with an identity
transform.
+ * Tests {@link GridExtent#toEnvelope(MathTransform, Identifier)} with an
identity transform.
*
* @throws TransformException if an error occurred while transforming to
an envelope.
*/
@@ -526,20 +529,24 @@ public final class GridExtentTest extends TestCase {
DimensionNameType.ROW,
DimensionNameType.TIME
}, new long[] {0, 0, 741}, new long[] {13, 9, 741}, true);
- final GeneralEnvelope envelope =
extent.toEnvelope(MathTransforms.identity(3));
+ final Identifier id = new ImmutableIdentifier(null, null, "Grid cell");
+ final GeneralEnvelope envelope =
extent.toEnvelope(MathTransforms.identity(3), id);
assertEnvelopeEquals(new GeneralEnvelope(
new double[] { 0, 0, 741},
new double[] {14, 10, 742}), envelope);
-
assertAxisDirectionsEqual(envelope.getCoordinateReferenceSystem().getCoordinateSystem(),
+ EngineeringCRS crs = assertInstanceOf(EngineeringCRS.class,
envelope.getCoordinateReferenceSystem());
+ assertEquals(id, crs.getDatum().getName());
+
+ assertAxisDirectionsEqual(crs.getCoordinateSystem(),
AxisDirection.COLUMN_POSITIVE,
AxisDirection.ROW_POSITIVE,
AxisDirection.FUTURE);
}
/**
- * Tests {@link GridExtent#toEnvelope(MathTransform)} with a non-identity
transform.
+ * Tests {@link GridExtent#toEnvelope(MathTransform, Identifier)} with a
non-identity transform.
*
* @throws TransformException if an error occurred while transforming to
an envelope.
*/
@@ -551,17 +558,21 @@ public final class GridExtentTest extends TestCase {
DimensionNameType.COLUMN,
DimensionNameType.VERTICAL
}, new long[] {100, 5, 200, 40}, new long[] {500, 7, 800, 50}, false);
+ final Identifier id = new ImmutableIdentifier(null, null, "Grid cell");
final GeneralEnvelope envelope =
extent.toEnvelope(MathTransforms.linear(Matrices.create(5, 5, new double[] {
0, 0, 1, 0, 0,
-1, 0, 0, 0, 0,
0, 0, 0, -1, 0,
0, 1, 0, 0, 0,
- 0, 0, 0, 0, 1})));
+ 0, 0, 0, 0, 1})), id);
assertEnvelopeEquals(new GeneralEnvelope(
new double[] {200, -500, -50, 5},
new double[] {800, -100, -40, 7}), envelope);
+ EngineeringCRS crs = assertInstanceOf(EngineeringCRS.class,
envelope.getCoordinateReferenceSystem());
+ assertEquals(id, crs.getDatum().getName());
+
assertAxisDirectionsEqual(envelope.getCoordinateReferenceSystem().getCoordinateSystem(),
AxisDirection.COLUMN_POSITIVE,
AxisDirection.ROW_NEGATIVE,
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
index dff81ab9eb..93bd403ad0 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/geometry/Envelopes.java
@@ -361,6 +361,9 @@ public final class Envelopes {
* @param results where to store the individual results when the
transform contains wraparound steps,
* or {@code null} for computing the union of all
results instead.
* @return the transformed envelope. May be {@code null} if {@code
results} was non-null.
+ * @throws MismatchedDimensionException if the {@code transform} source
number of dimensions
+ * is not equal to the {@code envelope} dimension.
+ * @throws TransformException if an error occurred during the execution of
the transform.
*/
private static GeneralEnvelope transform(final MathTransform transform,
final Envelope envelope,
double[] targetPt, final Map<Parameters, GeneralEnvelope> results)
throws TransformException
@@ -388,8 +391,9 @@ public final class Envelopes {
final int sourceDim = transform.getSourceDimensions();
final int targetDim = transform.getTargetDimensions();
if (envelope.getDimension() != sourceDim) {
- throw new
MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_2,
- sourceDim, envelope.getDimension()));
+ throw new MismatchedDimensionException(Errors.format(
+ Errors.Keys.MismatchedDimension_2,
+ sourceDim, envelope.getDimension()));
}
/*
* Allocates all needed objects. The power of 3 below is because the
following `while` loop
@@ -947,7 +951,9 @@ poles: for (int i=0; i<dimension; i++) {
* @param transform the transform to use.
* @param envelope envelope to transform, or {@code null}. This
envelope will not be modified.
* @return the transformed envelope, or {@code null} if {@code envelope}
was null.
- * @throws TransformException if a transform failed.
+ * @throws MismatchedDimensionException if the {@code transform} source
number of dimensions
+ * is not equal to the {@code envelope} dimension.
+ * @throws TransformException if an error occurred during the execution of
the transform.
*
* @see #transform(CoordinateOperation, Envelope)
*
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
index 93a2e21a9a..b99963769e 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -21,6 +21,7 @@ import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
import java.util.Queue;
+import java.util.Optional;
import java.util.concurrent.Future;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.LogRecord;
@@ -352,6 +353,19 @@ public class CoverageCanvas extends MapCanvasAWT {
return fixedPane;
}
+ /**
+ * Return an identifier derived from the resource identifier.
+ * This method returns an empty value if no worker has read the resource
identifier yet.
+ * It may happen randomly depending on thread execution order.
+ *
+ * @return an identifier of the rendered coverage.
+ * @since 1.7
+ */
+ @Override
+ public Optional<Identifier> getContentIdentifier() {
+ return Optional.ofNullable(data.gridCrsName);
+ }
+
/**
* Returns the resource to preserve when the coverage is adjusting. If the
coverage is set for another reason
* than adjustment, then the resource should be set to null as specified
in {@link #resourceProperty} contract.
@@ -881,22 +895,6 @@ public class CoverageCanvas extends MapCanvasAWT {
setObjectiveBounds(bounds);
}
- /**
- * Return a name of the grid <abbr>CRS</abbr>, derived from the resource
identifier.
- * This method returns {@code null} if no worker has read the resource
identifier yet.
- * It may happen randomly depending on thread execution order.
- *
- * <p>Note: do not fallback on an artificial name if the name is {@code
null}.
- * This method is used for building a grid <abbr>CRS</abbr> for cell
indices.
- * If this method returns an artificial name, it would cause an unusable
menu
- * item to appear in the menu that offers different <abbr>CRS</abbr>.</p>
- *
- * @see StoreUtilities#gridCrsName(GridCoverageResource, GridGeometry)
- */
- final Identifier gridCrsName() {
- return data.gridCrsName;
- }
-
/**
* Clears all information that are derived from the raw image projected to
objective CRS.
* In current version this is only isolines.
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageControls.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageControls.java
index bdb95744bb..270bd2379e 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageControls.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageControls.java
@@ -183,7 +183,7 @@ final class CoverageControls extends ViewAndControls {
final int visibleBand = 0; // TODO: provide a selector
for the band to show.
items.setAll(coverage.getSampleDimensions().get(visibleBand).getCategories());
}
- owner.notifyDataChanged(view.gridCrsName(), resource, coverage);
+ owner.notifyDataChanged(view.getContentIdentifier().orElse(null),
resource, coverage);
}
/**
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
index f473d80867..c4a8bd1bf7 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
@@ -612,7 +612,7 @@ public class CoverageExplorer extends Widget {
* {@link #coverageProperty}. In the latter case, the {@code resource} and
{@code coverage} arguments
* given to this method may be the value that the properties already
have.</p>
*
- * @param gridName value of {@link CoverageCanvas#gridCrsName()} or
equivalent.
+ * @param gridName value of {@link
CoverageCanvas#getContentIdentifier()} or equivalent.
* @param resource the new source of coverage, or {@code null} if none.
* @param coverage the new coverage, or {@code null} if none.
*/
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridControls.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridControls.java
index c1e7513b24..e8cf7112cd 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridControls.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridControls.java
@@ -104,7 +104,7 @@ final class GridControls extends ViewAndControls {
* Invoked after {@link GridView#setImage(ImageRequest)} for updating the
table of sample
* dimensions when information become available. This method is invoked in
JavaFX thread.
*
- * @param gridName value of {@link CoverageCanvas#gridCrsName()}.
+ * @param gridName value of {@link
CoverageCanvas#getContentIdentifier()}.
* @param resource the new source of coverage, or {@code null} if none.
* @param coverage the new coverage, or {@code null} if none.
*/
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridView.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridView.java
index 319e5c6b6f..22d4ca4d9b 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridView.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridView.java
@@ -322,7 +322,7 @@ public class GridView extends Control {
/**
* Invoked after the image has been loaded or after failure.
*
- * @param gridName value equivalent to {@link
CoverageCanvas#gridCrsName()}.
+ * @param gridName value equivalent to {@link
CoverageCanvas#getContentIdentifier()}.
* @param resource the new source of coverage, or {@code null} if none.
* @param coverage the new coverage, or {@code null} if none.
* @param image the loaded image, or {@code null} on failure.
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyledRenderingData.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyledRenderingData.java
index 70b6743946..26cab1572b 100644
---
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyledRenderingData.java
+++
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/StyledRenderingData.java
@@ -46,6 +46,11 @@ final class StyledRenderingData extends RenderingData {
/**
* Name of the grid <abbr>CRS</abbr>, derived from the resource identifier.
*
+ * <p>Note: do not fallback on an artificial name if the name is missing.
+ * This field is used for building a grid <abbr>CRS</abbr> for cell
indices.
+ * If this method returns an artificial name, it would cause an unusable
menu
+ * item to appear in the menu that offers different <abbr>CRS</abbr>.</p>
+ *
* @see StoreUtilities#gridCrsName(GridCoverageResource, GridGeometry)
*/
Identifier gridCrsName;
diff --git
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
index 5dbe9dfed7..7f1074100d 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/MapCanvas.java
@@ -19,6 +19,7 @@ package org.apache.sis.gui.map;
import java.util.Locale;
import java.util.Arrays;
import java.util.Objects;
+import java.util.Optional;
import java.util.Formatter;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
@@ -55,6 +56,7 @@ import javafx.scene.transform.Transform;
import javafx.scene.transform.NonInvertibleTransformException;
import javax.measure.Quantity;
import javax.measure.quantity.Length;
+import org.opengis.metadata.Identifier;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.ReferenceSystem;
@@ -439,6 +441,19 @@ public abstract class MapCanvas extends PlanarCanvas {
invalidObjectiveToDisplay = true;
}
+ /**
+ * Returns an identifier of the rendered data.
+ * The identifier should be unique on a best effort basis, but this is not
a strict requirement.
+ * It can be, for example, derived from the
+ * {@linkplain org.apache.sis.storage.GridCoverageResource#getIdentifier()
resource identifier}.
+ *
+ * @return an identifier of the rendered data.
+ * @since 1.7
+ */
+ public Optional<Identifier> getContentIdentifier() {
+ return Optional.empty();
+ }
+
/**
* Returns a pane where graphics can be added as if the <i>objective to
display</i> transform was static.
* The returned object takes a snapshot of the transform at the time when
this method has been invoked.
@@ -1418,7 +1433,11 @@ public abstract class MapCanvas extends PlanarCanvas {
if (init != null && init.isDefined(GridGeometry.CRS)) {
objectiveCRS = init.getCoordinateReferenceSystem();
} else {
- objectiveCRS =
extent.toEnvelope(crsToDisplay.inverse()).getCoordinateReferenceSystem();
+ Optional<Identifier> id = getContentIdentifier();
+ if (id.isPresent()) {
+ Envelope e =
extent.toEnvelope(crsToDisplay.inverse(), id.get());
+ objectiveCRS = e.getCoordinateReferenceSystem();
+ }
}
/*
* Above code tried to provide a non-null CRS on a "best
effort" basis. The objective CRS
@@ -1745,7 +1764,9 @@ public abstract class MapCanvas extends PlanarCanvas {
}
/**
- * Clears the error message in status bar.
+ * Clears the error message in the status bar.
+ *
+ * @see #clear()
*/
protected final void clearError() {
hasError = false;
@@ -1854,6 +1875,7 @@ public abstract class MapCanvas extends PlanarCanvas {
* }
*
* @see #reset()
+ * @see #clearError()
* @see #runAfterRendering(Runnable)
*/
@Override