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 151ed6c Initial commit of a GridGeometry(..., MathTransform,
Envelope) constructor.
151ed6c is described below
commit 151ed6cb115ec1c98166dc678ccbd10939814bf8
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sun Sep 23 22:53:53 2018 -0400
Initial commit of a GridGeometry(..., MathTransform, Envelope) constructor.
---
.../sis/internal/metadata/AxisDirections.java | 41 ++-----
.../org/apache/sis/coverage/grid/GridExtent.java | 102 ++++++++++++++++
.../org/apache/sis/coverage/grid/GridGeometry.java | 135 ++++++++++++++++++---
.../apache/sis/coverage/grid/GridExtentTest.java | 80 ++++++++++++
.../apache/sis/coverage/grid/GridGeometryTest.java | 21 ++++
.../org/apache/sis/test/suite/RasterTestSuite.java | 1 +
.../java/org/apache/sis/geometry/Envelopes.java | 1 +
.../sis/internal/metadata/AxisDirectionsTest.java | 10 +-
8 files changed, 344 insertions(+), 47 deletions(-)
diff --git
a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java
index 3690b19..d1e2be1 100644
---
a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java
+++
b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/AxisDirections.java
@@ -157,34 +157,19 @@ public final class AxisDirections extends Static {
* ({@code NORTH}, {@code EAST}, {@code UP}, {@code FUTURE}).
* More specifically, the following conversion table is applied:
*
- * <table class="compact">
+ * <table class="sis">
* <caption>Mapping to "absolute" directions</caption><tr>
- * <td><table class="sis" summary="Geospatial directions">
- * <tr>
- * <th style="width: 50%">Direction</th>
- * <th style="width: 50%">Absolute value</th>
- * </tr>
- * <tr><td>{@code NORTH}</td> <td>{@code NORTH}</td></tr>
- * <tr><td>{@code SOUTH}</td> <td>{@code NORTH}</td></tr>
- * <tr><td>{@code EAST}</td> <td>{@code EAST}</td></tr>
- * <tr><td>{@code WEST}</td> <td>{@code EAST}</td></tr>
- * <tr><td>{@code UP}</td> <td>{@code UP}</td></tr>
- * <tr><td>{@code DOWN}</td> <td>{@code UP}</td></tr>
- * </table></td>
- * <td><table class="sis" summary="Other directions">
- * <tr>
- * <th style="width: 50%">Direction</th>
- * <th style="width: 50%">Absolute value</th>
- * </tr>
- * <tr><td>{@code DISPLAY_RIGHT}</td> <td>{@code DISPLAY_RIGHT}</td></tr>
- * <tr><td>{@code DISPLAY_LEFT}</td> <td>{@code DISPLAY_RIGHT}</td></tr>
- * <tr><td>{@code DISPLAY_UP}</td> <td>{@code DISPLAY_UP}</td></tr>
- * <tr><td>{@code DISPLAY_DOWN}</td> <td>{@code DISPLAY_UP}</td></tr>
- * <tr><td>{@code FUTURE}</td> <td>{@code FUTURE}</td></tr>
- * <tr><td>{@code PAST}</td> <td>{@code FUTURE}</td></tr>
- * <tr><td>{@code CLOCKWISE}</td> <td>{@code
COUNTERCLOCKWISE}</td></tr>
- * </table></td></tr>
- * <tr align="center"><td>{@code OTHER}</td><td>{@code OTHER}</td></tr>
+ * <tr><th>Directions</th>
<th>Absolute value</th></tr>
+ * <tr><td>{@code NORTH}, {@code SOUTH}</td>
<td>{@code NORTH}</td></tr>
+ * <tr><td>{@code EAST}, {@code WEST}</td>
<td>{@code EAST}</td></tr>
+ * <tr><td>{@code UP}, {@code DOWN}</td>
<td>{@code UP}</td></tr>
+ * <tr><td>{@code FUTURE}, {@code PAST}</td>
<td>{@code FUTURE}</td></tr>
+ * <tr><td>{@code COLUMN_POSITIVE}, {@code COLUMN_NEGATIVE}</td>
<td>{@code COLUMN_POSITIVE}</td></tr>
+ * <tr><td>{@code ROW_POSITIVE}, {@code ROW_NEGATIVE}</td>
<td>{@code ROW_POSITIVE}</td></tr>
+ * <tr><td>{@code DISPLAY_RIGHT}, {@code DISPLAY_LEFT}</td>
<td>{@code DISPLAY_RIGHT}</td></tr>
+ * <tr><td>{@code DISPLAY_UP}, {@code DISPLAY_DOWN}</td>
<td>{@code DISPLAY_UP}</td></tr>
+ * <tr><td>{@code CLOCKWISE}, {@code COUNTERCLOCKWISE}</td>
<td>{@code COUNTERCLOCKWISE}</td></tr>
+ * <tr><td>{@code OTHER}</td>
<td>{@code OTHER}</td></tr>
* </table>
*
* @param dir the direction for which to return the absolute direction,
or {@code null}.
@@ -330,7 +315,7 @@ public final class AxisDirections extends Static {
}
/**
- * Returns {@code true} if the given direction is {@code COLUMN_POSITIVE},
{@code COLUMN_NEGATICE},
+ * Returns {@code true} if the given direction is {@code COLUMN_POSITIVE},
{@code COLUMN_NEGATIVE},
* {@code ROW_POSITIVE} or {@code ROW_NEGATIVE}.
*
* @param dir the direction to test, or {@code null}.
diff --git
a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 9d972de..aeefa6e 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -19,14 +19,21 @@ package org.apache.sis.coverage.grid;
import java.util.Arrays;
import java.util.Optional;
import java.io.Serializable;
+import java.util.Map;
+import java.util.HashMap;
import org.opengis.geometry.DirectPosition;
import org.opengis.metadata.spatial.DimensionNameType;
+import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.collection.WeakValueHashMap;
+import org.apache.sis.internal.metadata.AxisDirections;
import org.apache.sis.internal.raster.Resources;
+import org.apache.sis.geometry.AbstractEnvelope;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralDirectPosition;
@@ -66,6 +73,22 @@ public class GridExtent implements Serializable {
private static final long serialVersionUID = -4717353677844056017L;
/**
+ * The dimension name types for given coordinate system axis directions.
+ * This map contains only the "positive" axis directions.
+ *
+ * @todo Verify if there is more directions to add as of ISO 19111:2018.
+ */
+ private static final Map<AxisDirection,DimensionNameType> AXIS_DIRECTIONS;
+ static {
+ final Map<AxisDirection,DimensionNameType> dir = new HashMap<>(6);
+ dir.put(AxisDirection.COLUMN_POSITIVE, DimensionNameType.COLUMN);
+ dir.put(AxisDirection.ROW_POSITIVE, DimensionNameType.ROW);
+ dir.put(AxisDirection.UP, DimensionNameType.VERTICAL);
+ dir.put(AxisDirection.FUTURE, DimensionNameType.TIME);
+ AXIS_DIRECTIONS = dir;
+ }
+
+ /**
* Default axis types for the two-dimensional cases.
*/
private static final DimensionNameType[] DEFAULT_TYPES = new
DimensionNameType[] {
@@ -250,11 +273,88 @@ public class GridExtent implements Serializable {
}
/**
+ * Creates a new grid extent by rounding the given envelope to (usually)
nearest integers.
+ * The envelope coordinates shall be cell indices with lower values
inclusive and upper values exclusive.
+ * Envelopes crossing the anti-meridian shall be {@linkplain
GeneralEnvelope#simplify() simplified}.
+ * The envelope CRS is ignored, except for identifying dimension names for
information purpose.
+ * The way floating point values are rounded to integers may be adjusted
in any future version.
+ *
+ * <p><b>NOTE:</b> this constructor is not public because its contract is
a bit approximative.</p>
+ *
+ * @param envelope the envelope containing cell indices to store in a
{@code GridExtent}.
+ *
+ * @see #toCRS(MathTransform)
+ */
+ GridExtent(final AbstractEnvelope envelope) {
+ final int dimension = envelope.getDimension();
+ ordinates = allocate(dimension);
+ for (int i=0; i<dimension; i++) {
+ final double min = envelope.getLower(i);
+ final double max = envelope.getUpper(i);
+ if (min >= Long.MIN_VALUE && max <= Long.MAX_VALUE && min <= max) {
+ long lower = Math.round(min);
+ long upper = Math.round(max);
+ if (lower != upper) upper--; //
For making the coordinate inclusive.
+ /*
+ * The [lower … upper] range may be slightly larger than
desired in some rounding error situations.
+ * For example if 'min' was 1.49999 and 'max' was 2.50001, the
roundings will create a [1…3] range
+ * while there is actually only 2 pixels. We detect those
rounding problems by comparing the spans
+ * before and after rounding. We attempt an adjustment only if
the span mistmatch is ±1, otherwise
+ * the difference is assumed to be caused by overflow. On the
three values that can be affected by
+ * the adjustment (min, max and span), we change only the
number which is farthest from an integer
+ * value.
+ */
+ long error = (upper - lower) + 1; //
Negative number if overflow.
+ if (error >= 0) {
+ final double span = envelope.getSpan(i);
+ final long extent = Math.round(span);
+ if (extent != 0 && Math.abs(error -= extent) == 1) {
+ final double dmin = Math.abs(min - Math.rint(min));
+ final double dmax = Math.abs(max - Math.rint(max));
+ final boolean adjustMax = (dmax >= dmin);
+ if (Math.abs(span - extent) < (adjustMax ? dmax :
dmin)) {
+ if (adjustMax) upper = Math.subtractExact(upper,
error);
+ else lower = Math.addExact(lower, error);
+ }
+ }
+ }
+ ordinates[i] = lower;
+ ordinates[i + dimension] = upper;
+ } else {
+ throw new IllegalArgumentException(Resources.format(
+ Resources.Keys.IllegalGridEnvelope_3, i, min, max));
+ }
+ }
+ /*
+ * At this point we finished to compute ordinate values.
+ * Now try to infer dimension types from the CRS axes.
+ * This is only for information purpose.
+ */
+ DimensionNameType[] axisTypes = null;
+ final CoordinateReferenceSystem crs =
envelope.getCoordinateReferenceSystem();
+ if (crs != null) {
+ final CoordinateSystem cs = crs.getCoordinateSystem();
+ for (int i=0; i<dimension; i++) {
+ final DimensionNameType type =
AXIS_DIRECTIONS.get(AxisDirections.absolute(cs.getAxis(i).getDirection()));
+ if (type != null) {
+ if (axisTypes == null) {
+ axisTypes = new DimensionNameType[dimension];
+ }
+ axisTypes[i] = type;
+ }
+ }
+ }
+ types = validateAxisTypes(axisTypes);
+ }
+
+ /**
* Creates a new grid envelope as a copy of the given one.
*
* @param extent the grid envelope to copy.
* @throws IllegalArgumentException if a coordinate value in the low part
is
* greater than the corresponding coordinate value in the high
part.
+ *
+ * @see #castOrCopy(GridEnvelope)
*/
protected GridExtent(final GridEnvelope extent) {
ArgumentChecks.ensureNonNull("extent", extent);
@@ -421,6 +521,8 @@ public class GridExtent implements Serializable {
*
* @param gridToCRS a transform from <em>pixel corner</em> to real world
coordinates
* @return this grid extent in real world coordinates.
+ *
+ * @see #GridExtent(AbstractEnvelope)
*/
final GeneralEnvelope toCRS(final MathTransform gridToCRS) throws
TransformException {
final int dimension = getDimension();
diff --git
a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index 878f644..47d800f 100644
---
a/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++
b/core/sis-raster/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -29,6 +29,7 @@ import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.CoordinateSystem;
import org.apache.sis.math.MathFunctions;
+import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.ImmutableEnvelope;
import org.apache.sis.referencing.operation.transform.MathTransforms;
@@ -203,7 +204,7 @@ public class GridGeometry implements Serializable {
/**
* Creates a new grid geometry from a grid envelope and a mapping from
pixel coordinates to "real world" coordinates.
* At least one of {@code extent}, {@code gridToCRS} or {@code crs}
arguments shall be non-null.
- * If {@code gridToCRS} is non-null, than {@code anchor} shall be non-null
too with one of the following values:
+ * If {@code gridToCRS} is non-null, then {@code anchor} shall be non-null
too with one of the following values:
*
* <ul>
* <li>{@link PixelInCell#CELL_CENTER} if conversions of cell indices by
{@code gridToCRS} give "real world"
@@ -218,9 +219,12 @@ public class GridGeometry implements Serializable {
* (with pixels that may be tens of kilometres large) is a recurrent
problem. We want to encourage developers
* to always think about wether their <cite>grid to CRS</cite> transform
is mapping pixel corner or center.</div>
*
- * <div class="note"><b>Upcoming API generalization:</b>
+ * <div class="warning"><b>Upcoming API generalization:</b>
* the {@code extent} type of this method may be changed to {@code
GridEnvelope} interface in a future Apache SIS version.
- * This is pending <a
href="https://github.com/opengeospatial/geoapi/issues/36">GeoAPI
update</a>.</div>
+ * This is pending <a
href="https://github.com/opengeospatial/geoapi/issues/36">GeoAPI update</a>.
+ * In addition, the {@code PixelInCell} code list currently defined in the
{@code org.opengis.referencing.datum} package
+ * may move in another package in a future GeoAPI version because this
type is no longer defined by the ISO 19111 standard
+ * after the 2018 revision.</div>
*
* @param extent the valid extent of grid coordinates, or {@code
null} if unknown.
* @param anchor {@linkplain PixelInCell#CELL_CENTER Cell center} for
OGC conventions or
@@ -249,7 +253,7 @@ public class GridGeometry implements Serializable {
this.gridToCRS = PixelTranslation.translate(gridToCRS, anchor,
PixelInCell.CELL_CENTER);
this.cornerToCRS = PixelTranslation.translate(gridToCRS, anchor,
PixelInCell.CELL_CORNER);
GeneralEnvelope env = null;
- if (extent != null && gridToCRS != null) {
+ if (extent != null && cornerToCRS != null) {
env = extent.toCRS(cornerToCRS);
env.setCoordinateReferenceSystem(crs);
} else if (crs != null) {
@@ -263,9 +267,9 @@ public class GridGeometry implements Serializable {
* The easiest way to estimate a resolution is then to ask for the
derivative at some
* arbitrary point. For this constructor, we take the grid center.
*/
- final Matrix mat = MathTransforms.getMatrix(gridToCRS);
- if (mat != null) {
- resolution = resolution(mat, 1);
+ final Matrix matrix = MathTransforms.getMatrix(gridToCRS);
+ if (matrix != null) {
+ resolution = resolution(matrix, 1);
} else if (extent != null && gridToCRS != null) {
resolution =
resolution(gridToCRS.derivative(extent.getCentroid()), 0);
} else {
@@ -275,6 +279,71 @@ public class GridGeometry implements Serializable {
}
/**
+ * Creates a new grid geometry from a geospatial envelope and a mapping
from pixel coordinates to "real world" coordinates.
+ * At least one of {@code gridToCRS} or {@code envelope} arguments shall
be non-null.
+ * If {@code gridToCRS} is non-null, then {@code anchor} shall be non-null
too with one of the values documented in the
+ * {@link #GridGeometry(GridExtent, PixelInCell, MathTransform,
CoordinateReferenceSystem) constructor expecting a grid
+ * extent}.
+ *
+ * <p>The given envelope shall encompass all cell surfaces, from the left
border of leftmost pixel to the right border
+ * of the rightmost pixel and similarly along other axes. This constructor
tries to store a geospatial envelope close
+ * to the specified envelope, but there is no guarantees that the envelope
returned by {@link #getEnvelope()} will be
+ * equal to the given envelope. The envelope stored in the new {@code
GridGeometry} may be slightly smaller, larger or
+ * shifted because the floating point values used in geospatial envelope
can not always be mapped to the integer
+ * coordinates used in {@link GridExtent}.
+ * The rules for deciding whether coordinates should be rounded toward
nearest integers,
+ * to {@linkplain Math#floor(double) floor} or to {@linkplain
Math#ceil(double) ceil} values
+ * are implementation details and may be adjusted in any Apache SIS
versions.</p>
+ *
+ * <p>Because of the uncertainties explained in above paragraph, this
constructor should be used only in last resort,
+ * when the grid envelope is unknown. For determinist results, developers
should prefer the
+ * {@linkplain #GridGeometry(GridExtent, PixelInCell, MathTransform,
CoordinateReferenceSystem) constructor using grid extent}
+ * as much as possible. In particular, this constructor is not suitable
for computing grid geometry of tiles in a tiled image,
+ * because the above-cited uncertainties may result in apparently random
black lines between tiles.</p>
+ *
+ * <div class="warning"><b>Upcoming API change:</b>
+ * The {@code PixelInCell} code list currently defined in the {@code
org.opengis.referencing.datum} package
+ * may move in another package in a future GeoAPI version because this
type is no longer defined by the
+ * ISO 19111 standard after the 2018 revision. This code list may be taken
by ISO 19123 in a future revision.</div>
+ *
+ * @param anchor {@linkplain PixelInCell#CELL_CENTER Cell center} for
OGC conventions or
+ * {@linkplain PixelInCell#CELL_CORNER cell corner} for
Java2D/JAI conventions.
+ * @param gridToCRS the mapping from grid coordinates to "real world"
coordinates, or {@code null} if unknown.
+ * @param envelope the geospatial envelope, including its coordinate
reference system if available.
+ * There is no guarantees that the envelope actually
stored in the {@code GridGeometry}
+ * will be equal to this specified envelope.
+ * @throws TransformException if the math transform can not compute the
grid envelope or the resolution.
+ */
+ @SuppressWarnings("null")
+ public GridGeometry(final PixelInCell anchor, final MathTransform
gridToCRS, final Envelope envelope) throws TransformException {
+ if (gridToCRS == null) {
+ ArgumentChecks.ensureNonNull("envelope", envelope);
+ } else if (envelope != null) {
+ ensureDimensionMatches("envelope",
gridToCRS.getTargetDimensions(), envelope.getDimension());
+ }
+ this.gridToCRS = PixelTranslation.translate(gridToCRS, anchor,
PixelInCell.CELL_CENTER);
+ this.cornerToCRS = PixelTranslation.translate(gridToCRS, anchor,
PixelInCell.CELL_CORNER);
+ Matrix matrix = MathTransforms.getMatrix(gridToCRS);
+ int numToIgnore = 1;
+ if (envelope != null && cornerToCRS != null) {
+ GeneralEnvelope env = Envelopes.transform(cornerToCRS.inverse(),
envelope);
+ extent = new GridExtent(env);
+ env = extent.toCRS(cornerToCRS);
+
env.setCoordinateReferenceSystem(envelope.getCoordinateReferenceSystem());
+ this.envelope = new ImmutableEnvelope(env);
+ if (matrix == null) {
+ matrix = gridToCRS.derivative(extent.getCentroid()); //
'gridToCRS' can not be null if 'cornerToCRS' is non-null.
+ numToIgnore = 0;
+ }
+ } else {
+ this.extent = null;
+ this.envelope = ImmutableEnvelope.castOrCopy(envelope);
+ }
+ resolution = (matrix != null) ? resolution(matrix, numToIgnore) : null;
+ nonLinears = findNonLinearTargets(gridToCRS);
+ }
+
+ /**
* Ensures that the given dimension is equals to the expected value. If
not, throws an exception.
*
* @param argument the name of the argument being tested.
@@ -374,6 +443,8 @@ public class GridGeometry implements Serializable {
* Returns the bounding box of "real world" coordinates for this grid
geometry.
* This envelope is computed from the {@linkplain #getExtent() grid
extent}, which is
* {@linkplain #getGridToCRS(PixelInCell) transformed} to the "real world"
coordinate system.
+ * The returned envelope encompasses all cell surfaces, from the left
border of leftmost pixel
+ * to the right border of the rightmost pixel and similarly along other
axes.
*
* @return the bounding box in "real world" coordinates (never {@code
null}).
* @throws IncompleteGridGeometryException if this grid geometry has no
envelope —
@@ -747,16 +818,40 @@ public class GridGeometry implements Serializable {
appendLabel(buffer, "Grid size", visible);
if (dimension == 0) {
buffer.append("unspecified");
- } else for (int i=0; i<dimension; i++) {
- if (i != 0) buffer.append(" × ");
- buffer.append(extent.getSize(i));
- }
- appendLabel(buffer, "Grid low", visible);
- if (dimension == 0) {
- buffer.append("unspecified");
- } else for (int i=0; i<dimension; i++) {
- if (i != 0) buffer.append(", ");
- buffer.append(extent.getLow(i));
+ } else {
+ /*
+ * Get the string representations of all GridExtent numbers
before to write them.
+ * We do that for computing their length, in order to apply
right alignment.
+ */
+ final int NUM_PROPERTIES = 3;
+ final int[] columnSizes = new int[dimension];
+ final String[] values = new String[dimension *
NUM_PROPERTIES]; // Will contain (span, low, high) tuples.
+ for (int i=0; i<values.length; i++) {
+ final long value;
+ int margin = 0;
+ final int dim = i / NUM_PROPERTIES;
+ switch (i % NUM_PROPERTIES) {
+ case 0: value = extent.getSize(dim); if (i != 0)
margin = 1; break;
+ case 1: value = extent.getLow (dim); break;
+ case 2: value = extent.getHigh(dim); break;
+ default: throw new AssertionError(i);
+ }
+ final int length = (values[i] =
String.valueOf(value)).length() + margin;
+ if (length > columnSizes[dim]) columnSizes[dim] = length;
+ }
+ for (int t=0; t<NUM_PROPERTIES; t++) {
+ String separator = ", ";
+ switch (t) {
+ case 0: separator = " ×"; break;
+ case 1: appendLabel(buffer, "Grid low", visible);
break;
+ case 2: appendLabel(buffer, "Grid high", visible);
break;
+ }
+ for (int i=0; i<dimension; i++) {
+ if (i != 0) buffer.append(separator);
+ final String value = values[i*NUM_PROPERTIES + t];
+ buffer.append(CharSequences.spaces(columnSizes[i] -
value.length())).append(value);
+ }
+ }
}
}
CoordinateSystem cs = null;
@@ -795,7 +890,11 @@ public class GridGeometry implements Serializable {
if (i != 0) buffer.append(" × ");
buffer.append((float) resolution[i]);
if (cs != null) {
- buffer.append(' ').append(cs.getAxis(i).getUnit());
+ final String unit =
String.valueOf(cs.getAxis(i).getUnit());
+ if (unit.isEmpty() ||
Character.isLetterOrDigit(unit.codePointAt(0))) {
+ buffer.append(' ');
+ }
+ buffer.append(unit);
}
}
}
diff --git
a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
new file mode 100644
index 0000000..38f83c7
--- /dev/null
+++
b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.metadata.spatial.DimensionNameType;
+import org.apache.sis.geometry.AbstractEnvelope;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.referencing.crs.HardCodedCRS;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link GridExtent}.
+ *
+ * @author Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+public final strictfp class GridExtentTest extends TestCase {
+ /**
+ * Tests the {@link GridExtent#GridExtent(AbstractEnvelope)} constructor.
+ */
+ @Test
+ public void testCreateFromEnvelope() {
+ final GeneralEnvelope env = new GeneralEnvelope(HardCodedCRS.IMAGE);
+ env.setRange(0, -23.01, 30.107);
+ env.setRange(1, 12.97, 18.071);
+ GridExtent extent = new GridExtent(env);
+ assertExtentEquals(extent, 0, -23, 29);
+ assertExtentEquals(extent, 1, 13, 17);
+ assertEquals(DimensionNameType.COLUMN, extent.getAxisType(0).get());
+ assertEquals(DimensionNameType.ROW, extent.getAxisType(1).get());
+ }
+
+ /**
+ * Tests the rounding performed by the {@link
GridExtent#GridExtent(AbstractEnvelope)} constructor.
+ */
+ @Test
+ public void testRoundings() {
+ final GeneralEnvelope env = new GeneralEnvelope(6);
+ env.setRange(0, 1.49999, 3.49998); // Round to [1…3), stored as
[1…2].
+ env.setRange(1, 1.50001, 3.49998); // Round to [2…3), stored as
[1…2] (not [2…2]) because the span is close to 2.
+ env.setRange(2, 1.49998, 3.50001); // Round to [1…4), stored as
[1…2] (not [1…3]) because the span is close to 2.
+ env.setRange(3, 1.49999, 3.50002); // Round to [1…4), stored as
[2…3] because the upper part is closer to integer.
+ env.setRange(4, 1.2, 3.8); // Round to [1…4), stores as
[1…3] because the span is not close enough to integer.
+ GridExtent extent = new GridExtent(env);
+ assertExtentEquals(extent, 0, 1, 2);
+ assertExtentEquals(extent, 1, 1, 2);
+ assertExtentEquals(extent, 2, 1, 2);
+ assertExtentEquals(extent, 3, 2, 3);
+ assertExtentEquals(extent, 4, 1, 3);
+ assertExtentEquals(extent, 5, 0, 0); // Unitialized envelope values
were [0…0].
+ }
+
+ /**
+ * Verifies the low and high values in the specified dimension of the
given extent
+ */
+ private static void assertExtentEquals(final GridExtent extent, final int
dimension, final int low, final int high) {
+ assertEquals("low", low, extent.getLow (dimension));
+ assertEquals("high", high, extent.getHigh(dimension));
+ }
+}
diff --git
a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
index f660f76..1da415f 100644
---
a/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
+++
b/core/sis-raster/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -24,7 +24,9 @@ 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.transform.MathTransforms;
+import org.apache.sis.referencing.crs.HardCodedCRS;
import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.test.DependsOn;
import org.apache.sis.test.TestCase;
import org.junit.Test;
@@ -39,6 +41,7 @@ import static org.apache.sis.test.ReferencingAssert.*;
* @since 1.0
* @module
*/
+@DependsOn(GridExtentTest.class)
public final strictfp class GridGeometryTest extends TestCase {
/**
* Verifies grid extent coordinates.
@@ -187,4 +190,22 @@ public final strictfp class GridGeometryTest extends
TestCase {
assertFalse("isConversionLinear", grid.isConversionLinear(0, 1, 2, 3));
assertTrue ("isConversionLinear", grid.isConversionLinear(0, 1, 3));
}
+
+ /**
+ * Tests the construction from a geospatial envelope.
+ *
+ * @throws TransformException if an error occurred while using the "grid
to CRS" transform.
+ */
+ @Test
+ public void testFromGeospatialEnvelope() throws TransformException {
+ final GeneralEnvelope envelope = new
GeneralEnvelope(HardCodedCRS.WGS84_φλ);
+ envelope.setRange(0, -70.001, +80.002);
+ envelope.setRange(1, 4.997, 15.003);
+ final MathTransform gridToCRS =
MathTransforms.linear(Matrices.create(3, 3, new double[] {
+ 0, 0.5, -90,
+ 0.5, 0, -180,
+ 0, 0, 1}));
+ final GridGeometry grid = new GridGeometry(PixelInCell.CELL_CENTER,
gridToCRS, envelope);
+ // TODO: verify values.
+ }
}
diff --git
a/core/sis-raster/src/test/java/org/apache/sis/test/suite/RasterTestSuite.java
b/core/sis-raster/src/test/java/org/apache/sis/test/suite/RasterTestSuite.java
index 1817827..f580fb0 100644
---
a/core/sis-raster/src/test/java/org/apache/sis/test/suite/RasterTestSuite.java
+++
b/core/sis-raster/src/test/java/org/apache/sis/test/suite/RasterTestSuite.java
@@ -32,6 +32,7 @@ import org.junit.BeforeClass;
@Suite.SuiteClasses({
org.apache.sis.image.DefaultIteratorTest.class,
org.apache.sis.coverage.grid.PixelTranslationTest.class,
+ org.apache.sis.coverage.grid.GridExtentTest.class,
org.apache.sis.coverage.grid.GridGeometryTest.class
})
public final strictfp class RasterTestSuite extends TestSuite {
diff --git
a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
index 2ec3c31..d6688c1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
@@ -184,6 +184,7 @@ public final class Envelopes extends Static {
* Transforms an envelope using the given math transform.
* The transformation is only approximative: the returned envelope may be
bigger than necessary,
* or smaller than required if the bounding box contains a pole.
+ * The coordinate reference system of the returned envelope will be null.
*
* <div class="section">Limitation</div>
* This method can not handle the case where the envelope contains the
North or South pole,
diff --git
a/core/sis-referencing/src/test/java/org/apache/sis/internal/metadata/AxisDirectionsTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/internal/metadata/AxisDirectionsTest.java
index 0ba6061..4902fb4 100644
---
a/core/sis-referencing/src/test/java/org/apache/sis/internal/metadata/AxisDirectionsTest.java
+++
b/core/sis-referencing/src/test/java/org/apache/sis/internal/metadata/AxisDirectionsTest.java
@@ -43,7 +43,7 @@ import static
org.apache.sis.internal.metadata.AxisDirections.COUNTER_CLOCKWISE;
* {@code sis-referencing} module because those tests use {@link
HardCodedAxes} constants.</p>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
* @since 0.4
* @module
*/
@@ -65,6 +65,14 @@ public final strictfp class AxisDirectionsTest extends
TestCase {
assertEquals(UP, AxisDirections.absolute(DOWN));
assertEquals(FUTURE, AxisDirections.absolute(FUTURE));
assertEquals(FUTURE, AxisDirections.absolute(PAST));
+ assertEquals(COLUMN_POSITIVE,
AxisDirections.absolute(COLUMN_POSITIVE));
+ assertEquals(COLUMN_POSITIVE,
AxisDirections.absolute(COLUMN_NEGATIVE));
+ assertEquals(ROW_POSITIVE, AxisDirections.absolute(ROW_POSITIVE));
+ assertEquals(ROW_POSITIVE, AxisDirections.absolute(ROW_NEGATIVE));
+ assertEquals(DISPLAY_RIGHT,
AxisDirections.absolute(DISPLAY_RIGHT));
+ assertEquals(DISPLAY_RIGHT, AxisDirections.absolute(DISPLAY_LEFT));
+ assertEquals(DISPLAY_UP, AxisDirections.absolute(DISPLAY_UP));
+ assertEquals(DISPLAY_UP, AxisDirections.absolute(DISPLAY_DOWN));
assertEquals(AWAY_FROM, AxisDirections.absolute(AWAY_FROM));
assertEquals(COUNTER_CLOCKWISE, AxisDirections.absolute(CLOCKWISE));
assertEquals(COUNTER_CLOCKWISE,
AxisDirections.absolute(COUNTER_CLOCKWISE));