/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.coverage.grid; import java.awt.Rectangle; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.io.Serializable; import java.util.Arrays; import org.opengis.geometry.Envelope; import org.opengis.coverage.grid.GridEnvelope; import org.opengis.coverage.grid.GridCoordinates; import org.opengis.referencing.datum.PixelInCell; import org.geotools.resources.Classes; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.metadata.iso.spatial.PixelTranslation; /** * Defines a range of grid coverage coordinates. *
* CAUTION:
* ISO 19123 defines {@linkplain #getHigh high} coordinates as inclusive.
* We follow this specification for all getters methods, but keep in mind that this is the
* opposite of Java2D usage where {@link Rectangle} maximal values are exclusive. When the
* context is ambiguous, an explicit {@code isHighIncluded} argument is required.
*
* @since 2.5
*
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @see GridEnvelope2D
*/
public class GeneralGridEnvelope implements GridEnvelope, Serializable {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -1695224412095031712L;
/**
* The lower left corner, inclusive.
* Will be created only when first needed.
*/
private transient GridCoordinates low;
/**
* The upper right corner, inclusive.
* Will be created only when first needed.
*/
private transient GridCoordinates high;
/**
* Minimum and maximum grid ordinates. The first half contains minimum ordinates (inclusive),
* while the last half contains maximum ordinates (exclusive). Note that the
* later is the opposite of ISO specification. We store upper coordinates as exclusive values
* for implementation convenience.
*/
private final int[] index;
/**
* Checks if ordinate values in the minimum index are less than or
* equal to the corresponding ordinate value in the maximum index.
*
* @throws IllegalArgumentException if an ordinate value in the minimum index is not
* less than or equal to the corresponding ordinate value in the maximum index.
*/
private void checkCoherence() throws IllegalArgumentException {
final int dimension = index.length/2;
for (int i=0; i
* Notice that this method ensure interoperability between {@link Raster} dimensions in Java2D style and
* {@link GridEnvelope} dimensions in ISO 19123 style providing that the user remember to add 1 to the
* {@link GridEnvelope#getHigh(int)} values.
*
* @param rect The grid coordinates as a rectangle.
*/
public GeneralGridEnvelope(final Rectangle rect) {
this(rect.x, rect.y, rect.width, rect.height, 2);
}
/**
* Constructs multi-dimensional grid envelope defined by a {@link Raster}.
* The two first dimensions are set to the
* [{@linkplain Raster#getMinX x} .. x+{@linkplain Raster#getWidth width}-1] and
* [{@linkplain Raster#getMinY y} .. y+{@linkplain Raster#getHeight height}-1]
* inclusive ranges respectively.
* Extra dimensions (if any) are set to the [0..0] inclusive range.
*
*
* Notice that this method ensure interoperability between {@link Raster} dimensions in Java2D style and
* {@link GridEnvelope} dimensions in ISO 19123 style providing that the user remember to add 1 to the
* {@link GridEnvelope#getHigh(int)} values.
*
* @param raster The raster for which to construct a grid envelope.
* @param dimension Number of dimensions for this grid envelope.
* Must be equals or greater than 2.
*/
public GeneralGridEnvelope(final Raster raster, final int dimension) {
this(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), dimension);
}
/**
* Constructs multi-dimensional grid envelope defined by a {@link RenderedImage}.
* The two first dimensions are set to the
* [{@linkplain RenderedImage#getMinX x} .. x+{@linkplain RenderedImage#getWidth width}-1] and
* [{@linkplain RenderedImage#getMinY y} .. y+{@linkplain RenderedImage#getHeight height}-1]
* inclusive ranges respectively.
* Extra dimensions (if any) are set to the [0..0] inclusive range.
*
*
* Notice that this method ensure interoperability between {@link Raster} dimensions in Java2D style and
* {@link GridEnvelope} dimensions in ISO 19123 style providing that the user remember to add 1 to the
* {@link GridEnvelope#getHigh(int)} values.
*
* @param image The image for which to construct a grid envelope.
* @param dimension Number of dimensions for this grid envelope.
* Must be equals or greater than 2.
*/
public GeneralGridEnvelope(final RenderedImage image, final int dimension) {
this(image.getMinX(), image.getMinY(), image.getWidth(), image.getHeight(), dimension);
}
/**
* Constructs a multi-dimensional grid envelope. We keep this constructor private because
* the arguments order can be confusing. Forcing usage of {@link Rectangle} in public API
* is probably safer.
*/
private GeneralGridEnvelope(int x, int y, int width, int height, int dimension) {
if (dimension < 2) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, "dimension", dimension));
}
index = new int[dimension * 2];
index[0] = x;
index[1] = y;
index[dimension + 0] = x + width; // Reminder: upper values in index[] are exclusive.
index[dimension + 1] = y + height; // So there is no +1 offset to add here.
Arrays.fill(index, dimension+2, index.length, 1);
checkCoherence();
}
/**
* Casts the specified envelope into a grid envelope. This is sometime useful after an
* envelope has been transformed from "real world" coordinates to grid coordinates using the
* {@linkplain org.opengis.coverage.grid.GridGeometry#getGridToCRS grid to CRS} transform.
* The floating point values are rounded toward the nearest integers.
*
* Notice that highest values are interpreted as non-inclusive
*
*
* Anchor
* The convention is specified as a {@link PixelInCell} code instead than the more detailed
* {@link org.opengis.metadata.spatial.PixelOrientation} because the latter is restricted to
* the two-dimensional case while the former can be used for any number of dimensions.
*
* @param envelope
* The envelope to use for initializing this grid envelope.
* @param anchor
* Whatever envelope coordinates map to pixel center or pixel corner. Should be
* {@link PixelInCell#CELL_CENTER} for OGC convention (an offset of 0.5 will be
* added to every envelope coordinate values), or {@link PixelInCell#CELL_CORNER}
* for Java2D/JAI convention (no offset will be added).
* @throws IllegalArgumentException
* If {@code anchor} is not valid.
*
* @see org.geotools.referencing.GeneralEnvelope#GeneralEnvelope(GridEnvelope, PixelInCell,
* org.opengis.referencing.operation.MathTransform,
* org.opengis.referencing.crs.CoordinateReferenceSystem)
*/
public GeneralGridEnvelope(final Envelope envelope, final PixelInCell anchor)
throws IllegalArgumentException
{
this(envelope,anchor,false);
}
/**
* Casts the specified envelope into a grid envelope. This is sometime useful after an
* envelope has been transformed from "real world" coordinates to grid coordinates using the
* {@linkplain org.opengis.coverage.grid.GridGeometry#getGridToCRS grid to CRS} transform.
* The floating point values are rounded toward the nearest integers.
*
* Note about rounding mode
* Anchor
* The convention is specified as a {@link PixelInCell} code instead than the more detailed
* {@link org.opengis.metadata.spatial.PixelOrientation} because the latter is restricted to
* the two-dimensional case while the former can be used for any number of dimensions.
*
* @param envelope
* The envelope to use for initializing this grid envelope.
* @param anchor
* Whatever envelope coordinates map to pixel center or pixel corner. Should be
* {@link PixelInCell#CELL_CENTER} for OGC convention (an offset of 0.5 will be
* added to every envelope coordinate values), or {@link PixelInCell#CELL_CORNER}
* for Java2D/JAI convention (no offset will be added).
* @param isHighIncluded
* {@code true} if the envelope maximal values are inclusive, or {@code false} if
* they are exclusive. This argument does not apply to minimal values, which are
* always inclusive.
* @throws IllegalArgumentException
* If {@code anchor} is not valid.
*
* @see org.geotools.referencing.GeneralEnvelope#GeneralEnvelope(GridEnvelope, PixelInCell,
* org.opengis.referencing.operation.MathTransform,
* org.opengis.referencing.crs.CoordinateReferenceSystem)
*/
public GeneralGridEnvelope(final Envelope envelope, final PixelInCell anchor,
final boolean isHighIncluded)
throws IllegalArgumentException
{
final double offset = PixelTranslation.getPixelTranslation(anchor) + 0.5;
final int dimension = envelope.getDimension();
index = new int[dimension * 2];
for (int i=0; i
* According OpenGIS specification, {@linkplain org.opengis.coverage.grid.GridGeometry grid
* geometry} maps pixel's center. But envelopes typically encompass all pixels. This means
* that grid coordinates (0,0) has an envelope starting at (-0.5, -0.5). In order to revert
* back such envelope to a grid envelope, it is necessary to add 0.5 to every coordinates
* (including the maximum value since it is exclusive in a grid envelope). This offset is
* applied only if {@code anchor} is {@link PixelInCell#CELL_CENTER}. Users who don't want
* such offset should specify {@link PixelInCell#CELL_CORNER}.
*
* It would have been possible to round the {@linkplain Envelope#getMinimum minimal value}
* toward {@linkplain Math#floor floor} and the {@linkplain Envelope#getMaximum maximal value}
* toward {@linkplain Math#ceil ceil} in order to make sure that the grid envelope encompass
* fully the envelope - like what Java2D does when converting {@link java.awt.geom.Rectangle2D}
* to {@link Rectangle}). But this approach may increase by 1 or 2 units the image
* {@linkplain RenderedImage#getWidth width} or {@linkplain RenderedImage#getHeight height}. For
* example the range {@code [-0.25 ... 99.75]} (which is exactly 101 units wide) would be casted
* to {@code [-1 ... 100]}, which is 102 units wide. This leads to unexpected results when using
* grid envelope with image operations like "{@link javax.media.jai.operator.AffineDescriptor
* Affine}". For avoiding such changes in size, it is necessary to use the same rounding mode
* for both minimal and maximal values. The selected rounding mode is {@linkplain Math#round
* nearest integer} in this implementation.
*
* According OpenGIS specification, {@linkplain org.opengis.coverage.grid.GridGeometry grid
* geometry} maps pixel's center. But envelopes typically encompass all pixels. This means
* that grid coordinates (0,0) has an envelope starting at (-0.5, -0.5). In order to revert
* back such envelope to a grid envelope, it is necessary to add 0.5 to every coordinates
* (including the maximum value since it is exclusive in a grid envelope). This offset is
* applied only if {@code anchor} is {@link PixelInCell#CELL_CENTER}. Users who don't want
* such offset should specify {@link PixelInCell#CELL_CORNER}.
*