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
commit 3dff7c6c4bad6220d5d7c906a3dac34d74c87547
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Sep 23 16:13:30 2020 +0200
Add a `GridOrientation` argument to `GridGeometry(GridExtent, Envelope)` constructor.
The intent is to make this constructor more useful for expressing "desired" geometry,
as opposed to geometry of preexisting data.
Remove `GridCoverage2D(Envelope, …, RenderedImage)` constructor for consistency with
the idea
that (Envelope, Extent) tuples should be used for desired instead than actual grid geometries.
If someone really wants to create a `GridCoverage2D` from such tuple, `GridCoverageBuilder`
provides better controls.
---
.../apache/sis/coverage/grid/GridCoverage2D.java | 57 +----------
.../sis/coverage/grid/GridCoverageBuilder.java | 2 +-
.../org/apache/sis/coverage/grid/GridExtent.java | 20 ++--
.../org/apache/sis/coverage/grid/GridGeometry.java | 41 +++++++-
.../apache/sis/coverage/grid/GridOrientation.java | 87 +++++++++++++++++
.../sis/coverage/grid/GridCoverageBuilderTest.java | 4 +-
.../sis/coverage/grid/GridDerivationTest.java | 6 +-
.../apache/sis/coverage/grid/GridExtentTest.java | 20 +++-
.../apache/sis/coverage/grid/GridGeometryTest.java | 104 ++++++++++++++++++++-
.../coverage/grid/ResampledGridCoverageTest.java | 4 +-
.../sis/storage/geotiff/GridGeometryBuilder.java | 3 +-
.../sis/storage/geotiff/ImageFileDirectory.java | 2 +-
12 files changed, 267 insertions(+), 83 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index 7f14bd1..fe5df02 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -29,7 +29,6 @@ import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
-import org.opengis.geometry.Envelope;
import org.opengis.metadata.spatial.DimensionNameType;
import org.opengis.util.NameFactory;
import org.opengis.util.InternationalString;
@@ -51,7 +50,6 @@ import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ArraysExt;
-import org.apache.sis.util.Workaround;
import org.apache.sis.util.Debug;
import static java.lang.Math.min;
@@ -204,6 +202,8 @@ public class GridCoverage2D extends GridCoverage {
* @throws IllegalGridGeometryException if the {@code domain} does not met the above-documented
conditions.
* @throws IllegalArgumentException if the image number of bands is not the same than
the number of sample dimensions.
* @throws ArithmeticException if the distance between grid location and image location
exceeds the {@code long} capacity.
+ *
+ * @see GridCoverageBuilder
*/
public GridCoverage2D(GridGeometry domain, final List<? extends SampleDimension>
range, RenderedImage data) {
/*
@@ -304,7 +304,7 @@ public class GridCoverage2D extends GridCoverage {
} catch (TransformException e) {
throw new IllegalGridGeometryException(e); // Should
never happen.
} else {
- domain = new GridGeometry(extent, domain.envelope);
+ domain = new GridGeometry(extent, domain.envelope, GridOrientation.HOMOTHETIC);
}
}
}
@@ -312,57 +312,6 @@ public class GridCoverage2D extends GridCoverage {
}
/**
- * Constructs a grid coverage using the specified envelope, range and data.
- * This convenience constructor computes a {@link GridGeometry} from the given envelope
and image size.
- * This constructor assumes that all grid axes are in the same order than CRS axes and
no axis is flipped.
- * This straightforward approach often results in the <var>y</var> axis to
be oriented toward up,
- * not down as commonly expected with rendered images.
- *
- * <p>This constructor is generally not recommended because of the assumptions
on axis order and directions.
- * For better control, use the constructor expecting a {@link GridGeometry} argument
instead.
- * This constructor is provided mostly as a convenience for testing purposes.</p>
- *
- * @param domain the envelope encompassing all images, from upper-left corner to lower-right
corner.
- * If {@code null} a default grid geometry will be created with no CRS
and identity conversion.
- * @param range sample dimensions for each image band. The size of this list must
be equal to the number of bands.
- * If {@code null}, default sample dimensions will be created with no
transfer function.
- * @param data the sample values as a {@link RenderedImage}, with one band for each
sample dimension.
- * @throws IllegalArgumentException if the image number of bands is not the same than
the number of sample dimensions.
- *
- * @see GridGeometry#GridGeometry(GridExtent, Envelope)
- */
- public GridCoverage2D(final Envelope domain, final List<? extends SampleDimension>
range, final RenderedImage data) {
- super(createGridGeometry(data, domain), defaultIfAbsent(range, data, ImageUtilities.getNumBands(data)));
- this.data = data; // Non-null verified by createGridGeometry(…, data).
- xDimension = 0;
- yDimension = 1;
- gridToImageX = 0;
- gridToImageY = 0;
- verifyBandCount(range, data);
- gridGeometry2D = new AtomicReference<>();
- }
-
- /**
- * Creates a grid geometry from an envelope. The grid extent is computed from the image
size.
- * This static method is a workaround for RFE #4093999
- * ("Relax constraint on placement of this()/super() call in constructors").
- */
- @Workaround(library="JDK", version="1.8")
- private static GridGeometry createGridGeometry(final RenderedImage data, final Envelope
envelope) {
- ArgumentChecks.ensureNonNull("data", data);
- CoordinateReferenceSystem crs = null;
- int dimension = BIDIMENSIONAL;
- if (envelope != null) {
- dimension = envelope.getDimension();
- if (dimension < BIDIMENSIONAL) {
- throw new IllegalGridGeometryException(Resources.format(Resources.Keys.GridEnvelopeMustBeNDimensional_1,
BIDIMENSIONAL));
- }
- crs = envelope.getCoordinateReferenceSystem();
- }
- return new GridGeometry(createExtent(dimension, ImageUtilities.getBounds(data), crs),
envelope);
- }
-
- /**
* Creates a grid extent with the low and high coordinates of the given image bounds.
* The coordinate reference system is used for extracting grid axis names, in particular
* the {@link DimensionNameType#VERTICAL} and {@link DimensionNameType#TIME} dimensions.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
index b446dfe..c598725 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
@@ -234,7 +234,7 @@ public class GridCoverageBuilder {
* @see GridGeometry#GridGeometry(GridExtent, Envelope)
*/
public GridCoverageBuilder setDomain(final Envelope domain) {
- return setDomain(domain == null ? null : new GridGeometry(null, domain));
+ return setDomain(domain == null ? null : new GridGeometry(null, domain, null));
}
/**
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 513244d..503fff0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -1346,15 +1346,13 @@ public class GridExtent implements GridEnvelope, LenientComparable,
Serializable
/**
* Creates an affine transform from the coordinates of this grid to the coordinates of
the given envelope.
* This method assumes that all axes are in the same order (no axis swapping) and there
is no flipping of
- * axis direction. The transform maps cell corners.
+ * axis direction except for those specified in the {@code flips} bitmask. The transform
maps cell corners.
*
- * <p>This method is not yet public because we have not decided what could be an
API for controlling axis
- * swapping and flipping, if desired.</p>
- *
- * @param env the target envelope. Despite this method name, the envelope CRS is ignored.
+ * @param env the target envelope. Despite this method name, the envelope CRS is
ignored.
+ * @param flips bitmask of axes to flip.
* @return an affine transform from this grid extent to the given envelope, expressed
as a matrix.
*/
- final MatrixSIS cornerToCRS(final Envelope env) {
+ final MatrixSIS cornerToCRS(final Envelope env, final long flips) {
final int srcDim = getDimension();
final int tgtDim = env.getDimension();
final MatrixSIS affine = Matrices.create(tgtDim + 1, srcDim + 1, ExtendedPrecisionMatrix.ZERO);
@@ -1362,16 +1360,18 @@ public class GridExtent implements GridEnvelope, LenientComparable,
Serializable
final DoubleDouble offset = new DoubleDouble();
for (int j=0; j<tgtDim; j++) {
if (j < srcDim) {
+ final boolean flip = (flips & Numerics.bitmask(j)) != 0;
offset.set(coordinates[j]);
scale.set(coordinates[j + srcDim]);
scale.subtract(offset);
- scale.add(1);
- scale.inverseDivideGuessError(env.getSpan(j));
- if (!offset.isZero()) { // Use `if` for keeping the value
if scale is NaN.
+ scale.add(1); // == getSize(j) but without
overflow.
+ scale.inverseDivideGuessError(env.getSpan(j)); // == (envelope span) / (grid
size).
+ if (flip) scale.negate();
+ if (!offset.isZero()) { // Use `if` for keeping the
value if scale is NaN.
offset.multiply(scale);
offset.negate();
}
- offset.addGuessError(env.getMinimum(j));
+ offset.addGuessError(flip ? env.getMaximum(j) : env.getMinimum(j));
affine.setNumber(j, srcDim, offset);
} else {
scale.value = Double.NaN;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index 3def0f8..fb91cf6 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -61,6 +61,7 @@ import org.apache.sis.util.collection.TreeTable;
import org.apache.sis.util.collection.TableColumn;
import org.apache.sis.util.collection.DefaultTreeTable;
import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.util.NullArgumentException;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.Logging;
@@ -565,8 +566,36 @@ public class GridGeometry implements LenientComparable, Serializable
{
* @param extent the valid extent of grid coordinates, or {@code null} if unknown.
* @param envelope the envelope together with CRS of the "real world" coordinates,
or {@code null} if unknown.
* @throws NullPointerException if {@code extent} and {@code envelope} arguments are
both null.
+ *
+ * @deprecated Replaced by {@link #GridGeometry(GridExtent, Envelope, GridOrientation)}.
*/
+ @Deprecated
public GridGeometry(final GridExtent extent, final Envelope envelope) {
+ this(extent, envelope, GridOrientation.HOMOTHETIC);
+ }
+
+ /**
+ * Creates a grid geometry with an extent and an envelope.
+ * This constructor can be used when the <cite>grid to CRS</cite> transform
is unknown.
+ * If only the coordinate reference system is known, then the envelope coordinates can
be
+ * {@linkplain GeneralEnvelope#isAllNaN() all NaN}.
+ *
+ * <p>The main purpose of this constructor is to create "desired" grid geometries,
for example for use in
+ * {@linkplain org.apache.sis.storage.GridCoverageResource#read(GridGeometry, int...)
read} or
+ * {@linkplain org.apache.sis.coverage.grid.GridCoverageProcessor#resample resample}
operations.
+ * For grid geometries describing preexisting data, it is safer and more flexible to
use one of
+ * the constructors expecting a {@link MathTransform} argument.</p>
+ *
+ * @param extent the valid extent of grid coordinates, or {@code null} if unknown.
+ * @param envelope the envelope together with CRS of the "real world" coordinates,
or {@code null} if unknown.
+ * @param orientation high-level description of desired characteristics of the {@code
gridToCRS} transform.
+ * Ignored (can be null) if {@code extent} or {@code envelope} is
null.
+ * @throws NullPointerException if {@code extent} and {@code envelope} arguments are
both null,
+ * or if only the {@code orientation} argument is null.
+ *
+ * @since 1.1
+ */
+ public GridGeometry(final GridExtent extent, final Envelope envelope, final GridOrientation
orientation) {
this.extent = extent;
nonLinears = 0;
/*
@@ -576,6 +605,9 @@ public class GridGeometry implements LenientComparable, Serializable {
boolean nilEnvelope = true;
final ImmutableEnvelope env = ImmutableEnvelope.castOrCopy(envelope);
if (env == null || ((nilEnvelope = env.isAllNaN()) && env.getCoordinateReferenceSystem()
== null)) {
+ if (env != null && nilEnvelope) {
+ throw new NullArgumentException(Errors.format(Errors.Keys.UnspecifiedCRS));
+ }
ArgumentChecks.ensureNonNull("extent", extent);
this.envelope = null;
} else {
@@ -584,10 +616,11 @@ public class GridGeometry implements LenientComparable, Serializable
{
/*
* If we have both the extent and an envelope with at least one non-NaN coordinates,
* create the `cornerToCRS` transform. The `gridToCRS` calculation uses the
knowledge
- * that all scale factors are on diagonal with no sign reversal, which allows
simpler
- * calculation than full matrix multiplication. Use double-double arithmetic
everywhere.
+ * that all scale factors are on diagonal, which allows simpler calculation
than full
+ * matrix multiplication. Use double-double arithmetic everywhere.
*/
- final MatrixSIS affine = extent.cornerToCRS(env);
+ ArgumentChecks.ensureNonNull("orientation", orientation);
+ final MatrixSIS affine = extent.cornerToCRS(env, orientation.flip);
cornerToCRS = MathTransforms.linear(affine);
final int srcDim = cornerToCRS.getSourceDimensions(); // Translation
column in matrix.
final int tgtDim = cornerToCRS.getTargetDimensions(); // Number of
matrix rows before last row.
@@ -595,7 +628,7 @@ public class GridGeometry implements LenientComparable, Serializable {
for (int j=0; j<tgtDim; j++) {
final DoubleDouble scale = (DoubleDouble) affine.getNumber(j, j);
final DoubleDouble offset = (DoubleDouble) affine.getNumber(j, srcDim);
- resolution[j] = scale.doubleValue();
+ resolution[j] = Math.abs(scale.doubleValue());
scale.multiply(0.5);
offset.add(scale);
affine.setNumber(j, srcDim, offset);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridOrientation.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridOrientation.java
new file mode 100644
index 0000000..02b2bd1
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridOrientation.java
@@ -0,0 +1,87 @@
+/*
+ * 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.coverage.grid;
+
+import org.opengis.geometry.Envelope;
+import org.opengis.referencing.datum.PixelInCell;
+
+
+/**
+ * High-level description about how a grid is orientated relative to the CRS axes.
+ * This is determined by the {@linkplain GridGeometry#getGridToCRS(PixelInCell) grid to CRS}
transform.
+ * For example conversion from grid coordinates to CRS coordinates may flip the <var>y</var>
axis
+ * (grid coordinates increasing toward down on screen), or may swap <var>x</var>
and <var>y</var> axes, <i>etc.</i>
+ * The possibilities are infinite; this enumeration covers only a few common types.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+public enum GridOrientation {
+ /**
+ * The {@code gridToCRS} transform applies only scales and translations (no axis flip
or swap).
+ * Moving along the grid axis in dimension <var>i</var> causes a displacement
along the CRS axis
+ * in the same dimension <var>i</var>. In matrix terms all coefficients on
the diagonal are positives
+ * (restricted to 1 on the last row), the translation terms can be anything, and all
other terms are zero.
+ * For example in the three-dimensional case:
+ *
+ * {@preformat math
+ * ┌ ┐
+ * │ Sx 0 0 Tx │
+ * │ 0 Sy 0 Ty │
+ * │ 0 0 Sz Tz │
+ * │ 0 0 0 1 │
+ * └ ┘
+ * }
+ *
+ * with
+ * <var>S<sub>x</sub></var> > 0,
+ * <var>S<sub>y</sub></var> > 0 and
+ * <var>S<sub>z</sub></var> > 0.
+ */
+ HOMOTHETIC(0),
+
+ /**
+ * The {@code gridToCRS} transform applies scales and translations with a flip of the
second axis (<var>y</var>).
+ * This is the same kind of conversion than {@link #HOMOTHETIC} except that the <var>S<sub>y</sub></var>
term in
+ * the matrix is replaced by −<var>S<sub>y</sub></var>.
+ *
+ * <p>{@code REFLECTION_Y} is commonly used when the grid is a {@link java.awt.image.RenderedImage}.
+ * By contrast, an {@link #HOMOTHETIC} transform often results in <var>y</var>
axis oriented toward up,
+ * instead of down as commonly expected with rendered images.
+ * This {@code REFLECTION_Y} value matches the common usage for grids backed by images.</p>
+ */
+ REFLECTION_Y(2);
+
+ /*
+ * TODO: add DISPLAY. The difference compared to REFLECTION_Y is that it may change CRS
axes.
+ */
+
+ /**
+ * Bitmask of axes to flip.
+ * This is the argument to give in calls to {@link GridExtent#cornerToCRS(Envelope, long)}.
+ */
+ final int flip;
+
+ /**
+ * Creates a new enumeration.
+ */
+ private GridOrientation(final int flip) {
+ this.flip = flip;
+ }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
index 6a2a7f7..cd5e839 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverageBuilderTest.java
@@ -139,7 +139,7 @@ public final strictfp class GridCoverageBuilderTest extends TestCase {
final GeneralEnvelope env = new GeneralEnvelope(HardCodedCRS.WGS84);
env.setRange(0, 0, 10); // Scale factor of 2 for grid size of 10.
env.setRange(1, 0, 4); // Scale factor of ½ for grid size of 8.
- GridGeometry grid = new GridGeometry(new GridExtent(8, 5), env);
+ GridGeometry grid = new GridGeometry(new GridExtent(8, 5), env, GridOrientation.HOMOTHETIC);
assertSame(builder, builder.setDomain(grid));
try {
builder.build();
@@ -147,7 +147,7 @@ public final strictfp class GridCoverageBuilderTest extends TestCase {
} catch (IllegalStateException ex) {
assertNotNull(ex.getMessage());
}
- grid = new GridGeometry(new GridExtent(width, height), env);
+ grid = new GridGeometry(new GridExtent(width, height), env, GridOrientation.HOMOTHETIC);
assertSame(builder, builder.setDomain(grid));
return builder.build();
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
index bc58662..7dd4a08 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
@@ -564,8 +564,8 @@ public final strictfp class GridDerivationTest extends TestCase {
Envelope2D domain = new Envelope2D(HardCodedCRS.WGS84, 10, 20, 110, 70);
Envelope2D request = new Envelope2D(HardCodedCRS.WGS84, -5, 25, 100, 90);
Envelope2D expected = new Envelope2D(HardCodedCRS.WGS84, 10, 25, 85, 65);
- GridGeometry grid1 = new GridGeometry(null, domain);
- GridGeometry grid2 = new GridGeometry(null, request);
+ GridGeometry grid1 = new GridGeometry(null, domain, null);
+ GridGeometry grid2 = new GridGeometry(null, request, null);
GridGeometry subgrid = grid1.derive().subgrid(grid2).build();
assertTrue(subgrid.isEnvelopeOnly());
assertEnvelopeEquals(expected, subgrid.getEnvelope(), STRICT);
@@ -575,7 +575,7 @@ public final strictfp class GridDerivationTest extends TestCase {
*/
request.setCoordinateReferenceSystem(HardCodedCRS.WGS84_φλ);
request.setRect(25, -5, 90, 100);
- grid2 = new GridGeometry(null, request);
+ grid2 = new GridGeometry(null, request, null);
subgrid = grid1.derive().subgrid(grid2).build();
assertSame(HardCodedCRS.WGS84, subgrid.getCoordinateReferenceSystem());
assertTrue(subgrid.isEnvelopeOnly());
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index 21ecebd..a87ffe1 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -269,18 +269,32 @@ public final strictfp class GridExtentTest extends TestCase {
}
/**
- * Tests {@link GridExtent#cornerToCRS(Envelope)}.
+ * Tests {@link GridExtent#cornerToCRS(Envelope, long)}.
*/
@Test
public void testCornerToCRS() {
final GeneralEnvelope aoi = new GeneralEnvelope(HardCodedCRS.WGS84);
aoi.setRange(0, 40, 55);
aoi.setRange(1, -10, 70);
- final GridExtent extent = new GridExtent(null, new long[] {-20, -25}, new long[]
{10, 15}, false);
+ final GridExtent extent = new GridExtent(null,
+ new long[] {-20, -25},
+ new long[] { 10, 15}, false);
+ /*
+ * No axis flip.
+ * Verification: y = 2 × −25 + 40 = −10 (the minimum value declared in
envelope).
+ */
assertMatrixEquals("cornerToCRS", new Matrix3(
0.5, 0, 50,
0, 2, 40,
- 0, 0, 1), extent.cornerToCRS(aoi), STRICT);
+ 0, 0, 1), extent.cornerToCRS(aoi, 0), STRICT);
+ /*
+ * Y axis flip.
+ * Verification: y = −2 × −25 + 20 = 70 (the maximum value declared in
envelope).
+ */
+ assertMatrixEquals("cornerToCRS", new Matrix3(
+ 0.5, 0, 50,
+ 0, -2, 20,
+ 0, 0, 1), extent.cornerToCRS(aoi, 2), STRICT);
}
/**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
index b44427e..8ea4d2d 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -21,10 +21,12 @@ import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.apache.sis.referencing.operation.matrix.Matrix2;
import org.apache.sis.referencing.operation.matrix.Matrix3;
import org.apache.sis.referencing.operation.matrix.Matrix4;
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.crs.HardCodedCRS;
import org.apache.sis.geometry.GeneralEnvelope;
@@ -54,6 +56,47 @@ public final strictfp class GridGeometryTest extends TestCase {
}
/**
+ * Verifies the shift between the two {@code gridToCRS} transforms.
+ * This method should be invoked when the transforms are linear.
+ *
+ * @param grid the grid geoemtry to validate.
+ */
+ private static void verifyGridToCRS(final GridGeometry grid) {
+ final Matrix tr1 = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CENTER));
+ final Matrix tr2 = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER));
+ final Matrix m;
+ try {
+ /*
+ * Multiply the matrices instead than concatenating the transforms
+ * for making sure that we do not get cached transforms.
+ */
+ m = MatrixSIS.castOrCopy(tr2).inverse().multiply(tr1);
+ } catch (NoninvertibleTransformException e) {
+ throw new AssertionError(e);
+ }
+ /*
+ * Example of expected matrix (size may vary):
+ * ┌ ┐
+ * │ 1 0 0 0.5 │
+ * │ 0 1 0 0.5 │
+ * │ 0 0 1 0.5 │
+ * │ 0 0 0 1 │
+ * └ ┘
+ */
+ for (int j=m.getNumRow(); --j >=0;) {
+ double expected = 0.5; // Expected translation term
in last column.
+ for (int i=m.getNumCol(); --i >= 0;) {
+ if (i == j) expected = 1; // Expected value on the diagonal.
+ final double actual = m.getElement(j,i);
+ if (actual != expected) {
+ fail("Expected " + expected + " but got " + actual + " in following matrix:\n"
+ m);
+ }
+ expected = 0; // For all values other than
diagonal and translation.
+ }
+ }
+ }
+
+ /**
* Tests construction with an identity transform mapping pixel corner.
*/
@Test
@@ -94,6 +137,7 @@ public final strictfp class GridGeometryTest extends TestCase {
*/
assertArrayEquals("resolution", new double[] {1, 1, 1, 1}, grid.getResolution(false),
STRICT);
assertTrue("isConversionLinear", grid.isConversionLinear(0, 1, 2, 3));
+ verifyGridToCRS(grid);
}
/**
@@ -137,6 +181,7 @@ public final strictfp class GridGeometryTest extends TestCase {
*/
assertArrayEquals("resolution", new double[] {1, 1, 1}, grid.getResolution(false),
STRICT);
assertTrue("isConversionLinear", grid.isConversionLinear(0, 1, 2));
+ verifyGridToCRS(grid);
}
/**
@@ -163,6 +208,12 @@ public final strictfp class GridGeometryTest extends TestCase {
0, 1, 0, 7, // Combination of above scales (diagonal) and translation
(last column).
0, 0, 3, 8,
0, 0, 0, 1), MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER)),
STRICT);
+ /*
+ * Verify other computed properties.
+ */
+ assertArrayEquals("resolution", new double[] {2, 1, 3}, grid.getResolution(false),
STRICT);
+ assertTrue("isConversionLinear", grid.isConversionLinear(0, 1, 2));
+ verifyGridToCRS(grid);
}
/**
@@ -176,12 +227,14 @@ public final strictfp class GridGeometryTest extends TestCase {
GridGeometry grid = new GridGeometry(extent, PixelInCell.CELL_CENTER, MathTransforms.identity(2),
HardCodedCRS.WGS84);
GeneralEnvelope expected = new GeneralEnvelope(new double[] {-0.5, -0.5}, new double[]
{125.5, 196.5});
assertEnvelopeEquals(expected, grid.getEnvelope(), STRICT);
+ verifyGridToCRS(grid);
/*
* Derive a new grid geometry with 10×10 times more cells. The geographic area should
be unchanged.
*/
extent = extent.resize(1260, 1970);
grid = grid.derive().resize(extent, 0.1, 0.1).build();
assertEnvelopeEquals(expected, grid.getEnvelope(), STRICT);
+ verifyGridToCRS(grid);
/*
* If we create a grid geometry with identical properties, the envelope computed
by that grid geometry would
* be different than the envelope computed above if the "grid to CRS" transform is
not correctly adjusted.
@@ -189,6 +242,7 @@ public final strictfp class GridGeometryTest extends TestCase {
final GridGeometry alternative = new GridGeometry(grid.getExtent(), PixelInCell.CELL_CENTER,
grid.getGridToCRS(PixelInCell.CELL_CENTER), grid.getCoordinateReferenceSystem());
assertEnvelopeEquals(expected, alternative.getEnvelope(), STRICT);
+ verifyGridToCRS(grid);
}
/**
@@ -207,6 +261,7 @@ public final strictfp class GridGeometryTest extends TestCase {
0, 0, 1));
final GridGeometry grid = new GridGeometry(extent, PixelInCell.CELL_CENTER, identity,
null);
assertTrue("gridToCRS.isIdentity", grid.getGridToCRS(PixelInCell.CELL_CORNER).isIdentity());
+ verifyGridToCRS(grid);
}
/**
@@ -262,6 +317,12 @@ public final strictfp class GridGeometryTest extends TestCase {
0, 0.5, -89.75,
0.5, 0, -179.75,
0, 0, 1), MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CENTER)),
STRICT);
+ /*
+ * Verify other computed properties.
+ */
+ assertArrayEquals("resolution", new double[] {0.5, 0.5}, grid.getResolution(false),
STRICT);
+ assertTrue("isConversionLinear", grid.isConversionLinear(0, 1));
+ verifyGridToCRS(grid);
}
/**
@@ -275,12 +336,39 @@ public final strictfp class GridGeometryTest extends TestCase {
final GeneralEnvelope aoi = new GeneralEnvelope(HardCodedCRS.WGS84);
aoi.setRange(0, 40, 55);
aoi.setRange(1, -10, 70);
- final GridGeometry grid = new GridGeometry(new GridExtent(null, new long[] {-20,
-25}, new long[] {10, 15}, false), aoi);
- final Matrix matrix = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER));
+ final GridExtent extent = new GridExtent(null,
+ new long[] {-20, -25},
+ new long[] { 10, 15}, false);
+ /*
+ * Simplest case: no axis flip.
+ * Verification: y = 2 × −25 + 40 = −10 (the minimum value declared in
envelope).
+ */
+ GridGeometry grid = new GridGeometry(extent, aoi, GridOrientation.HOMOTHETIC);
+ Matrix matrix = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER));
assertMatrixEquals("cornerToCRS", new Matrix3(
0.5, 0, 50,
0, 2, 40,
0, 0, 1), matrix, STRICT);
+
+ // Verify other computed properties.
+ assertArrayEquals("resolution", new double[] {0.5, 2}, grid.getResolution(false),
STRICT);
+ assertTrue("isConversionLinear", grid.isConversionLinear(0, 1));
+ verifyGridToCRS(grid);
+ /*
+ * Same envelope and extent, but flip Y axis.
+ * Verification: y = −2 × −25 + 20 = 70 (the maximum value declared in
envelope).
+ */
+ grid = new GridGeometry(extent, aoi, GridOrientation.REFLECTION_Y);
+ matrix = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER));
+ assertMatrixEquals("cornerToCRS", new Matrix3(
+ 0.5, 0, 50,
+ 0, -2, 20,
+ 0, 0, 1), matrix, STRICT);
+
+ // Verify other computed properties.
+ assertArrayEquals("resolution", new double[] {0.5, 2}, grid.getResolution(false),
STRICT);
+ assertTrue("isConversionLinear", grid.isConversionLinear(0, 1));
+ verifyGridToCRS(grid);
}
/**
@@ -318,6 +406,12 @@ public final strictfp class GridGeometryTest extends TestCase {
assertMatrixEquals("gridToCRS", new Matrix2(
2, 3,
0, 1), MathTransforms.getMatrix(reduced.getGridToCRS(PixelInCell.CELL_CORNER)),
STRICT);
+ /*
+ * Verify other computed properties.
+ */
+ assertArrayEquals("resolution", new double[] {0.5, 0.5, 2}, grid.getResolution(false),
STRICT);
+ assertTrue("isConversionLinear", grid.isConversionLinear(0, 1, 2));
+ verifyGridToCRS(grid);
}
/**
@@ -347,6 +441,12 @@ public final strictfp class GridGeometryTest extends TestCase {
0.5, 0, -180,
// 0, 0, 3, // All scale coefficients set to 0.
0, 0, 1), MathTransforms.getMatrix(tr), STRICT);
+ /*
+ * Verify other computed properties.
+ */
+ assertArrayEquals("resolution", new double[] {0.5, 0.5}, reduced.getResolution(false),
STRICT);
+ assertTrue("isConversionLinear", reduced.isConversionLinear(0, 1));
+ verifyGridToCRS(reduced);
}
/**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
index 60e5328..7fe355e 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
@@ -105,7 +105,7 @@ public final strictfp class ResampledGridCoverageTest extends TestCase
{
final int y = random.nextInt(32) - 10;
final GridGeometry gg = new GridGeometry(
new GridExtent(null, new long[] {x, y}, new long[] {x+width, y+height}, false),
- new Envelope2D(HardCodedCRS.WGS84, 20, 15, 60, 62));
+ new Envelope2D(HardCodedCRS.WGS84, 20, 15, 60, 62), GridOrientation.HOMOTHETIC);
return new GridCoverage2D(gg, null, image);
}
@@ -440,7 +440,7 @@ public final strictfp class ResampledGridCoverageTest extends TestCase
{
@Test
public void testSubGeographicArea() throws TransformException {
final GridCoverage2D source = createCoverage2D(); // Envelope2D(20, 15,
60, 62)
- GridGeometry gg = new GridGeometry(null, new Envelope2D(HardCodedCRS.WGS84, 18, 20,
17, 31));
+ GridGeometry gg = new GridGeometry(null, new Envelope2D(HardCodedCRS.WGS84, 18, 20,
17, 31), null);
final GridCoverage target = resample(source, gg);
final GridExtent sourceExtent = source.getGridGeometry().getExtent();
final GridExtent targetExtent = target.getGridGeometry().getExtent();
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
index b780694..a45aec1 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GridGeometryBuilder.java
@@ -37,6 +37,7 @@ import org.apache.sis.internal.geotiff.Resources;
import org.apache.sis.internal.util.DoubleDouble;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridOrientation;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.math.Vector;
@@ -334,7 +335,7 @@ final class GridGeometryBuilder {
envelope = new GeneralEnvelope(crs);
envelope.setToNaN();
}
- gridGeometry = new GridGeometry(extent, envelope);
+ gridGeometry = new GridGeometry(extent, envelope, GridOrientation.HOMOTHETIC);
canNotCreate(e);
/*
* Note: we catch TransformExceptions because they may be caused by erroneous
data in the GeoTIFF file,
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index aab1e7f..5456d32 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -1254,7 +1254,7 @@ final class ImageFileDirectory extends AbstractGridResource {
}
return gridGeometry;
} else {
- return new GridGeometry(new GridExtent(imageWidth, imageHeight), null);
+ return new GridGeometry(new GridExtent(imageWidth, imageHeight), null, null);
}
}
|