/*
* 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.geom.AffineTransform;
import java.awt.image.RenderedImage;
import java.io.Serializable;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.metadata.iso.spatial.PixelTranslation;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.builder.GridToEnvelopeMapper;
import org.geotools.referencing.operation.matrix.XAffineTransform;
import org.geotools.referencing.operation.transform.ProjectiveTransform;
import org.geotools.resources.CRSUtilities;
import org.geotools.resources.Classes;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.Utilities;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.coverage.grid.GridGeometry;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.Cloneable;
/**
* Describes the valid range of grid coordinates and the math transform to transform grid
* coordinates to real world coordinates. Grid geometries contains:
* <p>
* <ul>
* <li>An optional {@linkplain GridEnvelope grid envelope} (a.k.a. "<cite>grid range</cite>"),
* usually inferred from the {@linkplain RenderedImage rendered image} size.</li>
* <li>An optional "grid to CRS" {@linkplain MathTransform transform}, which may be inferred
* from the grid range and the envelope.</li>
* <li>An optional {@linkplain Envelope envelope}, which may be inferred from the grid range
* and the "grid to CRS" transform.</li>
* <li>An optional {@linkplain CoordinateReferenceSystem coordinate reference system} to be
* given to the envelope.</li>
* </ul>
* <p>
* All grid geometry attributes are optional because some of them may be inferred from a wider
* context. For example a grid geometry know nothing about {@linkplain RenderedImage rendered
* images}, but {@link GridCoverage2D} do. Consequently, the later may infer the {@linkplain
* GridEnvelope grid range} by itself.
* <p>
* By default, any request for an undefined attribute will thrown an
* {@link InvalidGridGeometryException}. In order to check if an attribute is defined,
* use {@link #isDefined}.
*
* @since 2.1
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
* @author Simone Giannecchini, GeoSolutions SAS
*
* @see GridGeometry2D
* @see ImageGeometry
*/
public class GeneralGridGeometry implements GridGeometry, Serializable {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 124700383873732132L;
private static boolean assertsEnabled=false;
static{
assert assertsEnabled = true; // Intentional side-effect !!!
}
/**
* A bitmask to specify the validity of the {@linkplain #getCoordinateReferenceSystem
* coordinate reference system}. This is given as an argument to the {@link #isDefined}
* method.
*
* @since 2.2
*/
public static final int CRS_BITMASK = 1;
/**
* A bitmask to specify the validity of the {@linkplain #getEnvelope envelope}.
* This is given as an argument to the {@link #isDefined} method.
*
* @since 2.2
*/
public static final int ENVELOPE_BITMASK = 2;
/**
* A bitmask to specify the validity of the {@linkplain #getGridRange grid range}.
* This is given as an argument to the {@link #isDefined} method.
*
* @since 2.2
*/
public static final int GRID_RANGE_BITMASK = 4;
/**
* A bitmask to specify the validity of the {@linkplain #getGridToCoordinateSystem grid to CRS}
* transform. This is given as an argument to the {@link #isDefined} method.
*
* @since 2.2
*/
public static final int GRID_TO_CRS_BITMASK = 8;
/**
* The valid coordinate range of a grid coverage, or {@code null} if none. The lowest valid
* grid coordinate is zero for {@link java.awt.image.BufferedImage}, but may be non-zero for
* arbitrary {@link RenderedImage}. A grid with 512 cells can have a minimum coordinate of 0
* and maximum of 512, with 511 as the highest valid index.
*
* @see RenderedImage#getMinX
* @see RenderedImage#getMinY
* @see RenderedImage#getWidth
* @see RenderedImage#getHeight
*/
protected final GridEnvelope gridRange;
/**
* The envelope, which is usually the {@linkplain #gridRange grid range}
* {@linkplain #gridToCRS transformed} to real world coordinates. This
* envelope contains the {@linkplain CoordinateReferenceSystem coordinate
* reference system} of "real world" coordinates.
* <p>
* This field should be considered as private because envelopes are mutable, and we want to make
* sure that envelopes are cloned before to be returned to the user. Only {@link GridGeometry2D}
* and {@link GridCoverage2D} access directly to this field (read only) for performance reason.
*
* @since 2.2
*/
GeneralEnvelope envelope;
/**
* The math transform (usually an affine transform), or {@code null} if none.
* This math transform maps {@linkplain PixelInCell#CELL_CENTER pixel center}
* to "real world" coordinate using the following line:
*
* <pre>gridToCRS.transform(pixels, point);</pre>
*/
protected MathTransform gridToCRS;
/**
* Same as {@link #gridToCRS} but from {@linkplain PixelInCell#CELL_CORNER pixel corner}
* instead of center. Will be computed only when first needed. Serialized because it may
* be a value specified explicitly at construction time, in which case it can be more
* accurate than a computed value.
*/
private MathTransform cornerToCRS;
/**
* Constructs a new grid geometry identical to the specified one except for the CRS.
* Note that this constructor just defines the CRS; it does <strong>not</strong> reproject
* the envelope. For this reason, this constructor should not be public. It is for internal
* use by {@link GridCoverageFactory} only.
*/
GeneralGridGeometry(final GeneralGridGeometry gm, final CoordinateReferenceSystem crs) {
gridRange = gm.gridRange; // Do not clone; we assume it is safe to share.
gridToCRS = gm.gridToCRS;
cornerToCRS = gm.cornerToCRS;
envelope = new GeneralEnvelope(gm.envelope);
envelope.setCoordinateReferenceSystem(crs);
}
/**
* Creates a new grid geometry with the same values than the given grid geometry. This
* is a copy constructor useful when the instance must be a {@code GeneralGridGeometry}.
*
* @param other The other grid geometry to copy.
*
* @since 2.5
*/
public GeneralGridGeometry(final GridGeometry other) {
if (other instanceof GeneralGridGeometry) {
// Uses this path when possible in order to accept null values.
final GeneralGridGeometry general = (GeneralGridGeometry) other;
gridRange = general.gridRange; // Do not clone; we assume it is safe to share.
gridToCRS = general.gridToCRS;
cornerToCRS = general.cornerToCRS;
envelope = general.envelope;
} else {
gridRange = other.getGridRange();
gridToCRS = other.getGridToCRS();
if (gridRange!=null && gridToCRS!=null) {
envelope = new GeneralEnvelope(gridRange, PixelInCell.CELL_CENTER, gridToCRS, null);
} else {
envelope = null;
}
}
}
/**
* Constructs a new grid geometry from a grid range and a {@linkplain MathTransform math transform}
* mapping {@linkplain PixelInCell#CELL_CENTER pixel center}.
*
* @param gridRange The valid coordinate range of a grid coverage, or {@code null} if none.
* @param gridToCRS The math transform which allows for the transformations from grid
* coordinates (pixel's <em>center</em>) to real world earth coordinates.
* May be {@code null}, but this is not recommanded.
* @param crs The coordinate reference system for the "real world" coordinates, or
* {@code null} if unknown. This CRS is given to the
* {@linkplain #getEnvelope envelope}.
*
* @throws MismatchedDimensionException if the math transform and the CRS don't have
* consistent dimensions.
* @throws IllegalArgumentException if the math transform can't transform coordinates
* in the domain of the specified grid range.
*
* @since 2.2
*/
public GeneralGridGeometry(final GridEnvelope gridRange,
final MathTransform gridToCRS,
final CoordinateReferenceSystem crs)
throws MismatchedDimensionException, IllegalArgumentException
{
this(gridRange, PixelInCell.CELL_CENTER, gridToCRS, crs);
}
/**
* Constructs a new grid geometry from a grid range and a {@linkplain MathTransform math transform}
* mapping pixel {@linkplain PixelInCell#CELL_CENTER center} or {@linkplain PixelInCell#CELL_CORNER
* corner}. This is the most general constructor, the one that gives the maximal control over
* the grid geometry to be created.
*
* @param gridRange The valid coordinate range of a grid coverage, or {@code null} if none.
* @param anchor {@link PixelInCell#CELL_CENTER CELL_CENTER} for OGC conventions or
* {@link PixelInCell#CELL_CORNER CELL_CORNER} for Java2D/JAI conventions.
* @param gridToCRS The math transform which allows for the transformations from grid
* coordinates to real world earth coordinates. May be {@code null},
* but this is not recommended.
* @param crs The coordinate reference system for the "real world" coordinates, or
* {@code null} if unknown. This CRS is given to the
* {@linkplain #getEnvelope envelope}.
*
* @throws MismatchedDimensionException if the math transform and the CRS don't have
* consistent dimensions.
* @throws IllegalArgumentException if the math transform can't transform coordinates
* in the domain of the specified grid range.
*
* @since 2.5
*/
public GeneralGridGeometry(final GridEnvelope gridRange,
final PixelInCell anchor,
final MathTransform gridToCRS,
final CoordinateReferenceSystem crs)
throws MismatchedDimensionException, IllegalArgumentException
{
this.gridRange = clone(gridRange);
this.gridToCRS = PixelTranslation.translate(gridToCRS, anchor, PixelInCell.CELL_CENTER);
if (PixelInCell.CELL_CORNER.equals(anchor)) {
cornerToCRS = gridToCRS;
}
if (gridRange!=null && gridToCRS!=null) {
envelope = new GeneralEnvelope(gridRange, anchor, gridToCRS, crs);
} else if (crs != null) {
envelope = new GeneralEnvelope(crs);
envelope.setToNull();
} else {
envelope = null;
}
}
/**
* Casts the specified grid range into an envelope. This is used before to transform
* the envelope using {@link CRSUtilities#transform(MathTransform, Envelope)}.
*/
private static Envelope toEnvelope(final GridEnvelope gridRange) {
final int dimension = gridRange.getDimension();
final double[] lower = new double[dimension];
final double[] upper = new double[dimension];
for (int i=0; i<dimension; i++) {
lower[i] = gridRange.getLow(i);
upper[i] = gridRange.getHigh(i) + 1;
}
return new GeneralEnvelope(lower, upper);
}
/**
* Constructs a new grid geometry from an envelope and a {@linkplain MathTransform math
* transform}. According OGC specification, the math transform should map {@linkplain
* PixelInCell#CELL_CENTER pixel center}. But in Java2D/JAI conventions, the transform
* is rather expected to maps {@linkplain PixelInCell#CELL_CORNER pixel corner}. The
* convention to follow can be specified by the {@code anchor} argument.
*
* <p>
* It is worth to point a few guidelines for the usage of the gridMaxInclusive parameter.
* In case we are using this contructor for a reprojection it might be worth using a <code>false</code>
* value for this parameter since we would not force to create a new raster that might be slightly bigger than
* the original one, causing problems with black or nodata collars appearing after a resample.
* In case we are trying to build a source raster area to be used at reading time, in that case we may want to provide
* <code>true</code> for gridMaxInclusive since we may want to read an area that is slightly bigger than what we need
* to be sure we actually reading something.
*
* @param anchor {@link PixelInCell#CELL_CENTER CELL_CENTER} for OGC conventions or
* {@link PixelInCell#CELL_CORNER CELL_CORNER} for Java2D/JAI conventions.
* @param gridToCRS The math transform which allows for the transformations from grid
* coordinates to real world earth coordinates. May be {@code null},
* but this is not recommended.
* @param envelope The envelope (including CRS) of a grid coverage, or {@code null} if none.
* @param gridMaxInclusive Tells us whether or not the resulting gridRange max values should be
* inclusive or not. See notes above.
* @param preserveGridToCRS Tells us whether we should try to preserve the the gridToCRS or the envelope
* most part of the time we won't be able to preserve both for our purposes.
*
* @throws MismatchedDimensionException if the math transform and the envelope doesn't have
* consistent dimensions.
* @throws IllegalArgumentException if the math transform can't transform coordinates
* in the domain of the grid range.
*
* @since 2.7
*/
public GeneralGridGeometry(final PixelInCell anchor,
final MathTransform gridToCRS,
final Envelope envelope,
final boolean gridMaxInclusive,
final boolean preserveGridToCRS)
throws MismatchedDimensionException, IllegalArgumentException
{
this.gridToCRS = PixelTranslation.translate(gridToCRS, anchor, PixelInCell.CELL_CENTER);
if (PixelInCell.CELL_CORNER.equals(anchor)) {
cornerToCRS = gridToCRS;
}
else
cornerToCRS =PixelTranslation.translate(gridToCRS, anchor, PixelInCell.CELL_CORNER);
if (envelope == null) {
this.envelope = null;
this.gridRange = null;
return;
}
this.envelope = new GeneralEnvelope(envelope);
if (gridToCRS == null) {
this.gridRange = null;
return;
}
final GeneralEnvelope transformed;
try {
transformed = org.geotools.referencing.CRS.transform(cornerToCRS.inverse(), envelope);
} catch (TransformException exception) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.BAD_TRANSFORM_$1,
Classes.getClass(gridToCRS)), exception);
}
// making this inclusive may cause problems when we use for warping of something since we might try to include more data
// than we actually havein the source imagery
gridRange = new GeneralGridEnvelope(transformed, PixelInCell.CELL_CORNER,gridMaxInclusive);
//
// CORRECTIONS
//
// do we need to ajust the gridToCRS or the envelope?
if(preserveGridToCRS)
{
//preserve firdToCrs
GeneralEnvelope tempEnvelope;
try {
tempEnvelope = CRS.transform(getGridToCRS(),toEnvelope(gridRange));
} catch (Throwable e) {
throw new RuntimeException(e);
}
tempEnvelope.setCoordinateReferenceSystem(envelope.getCoordinateReferenceSystem());
this.envelope=tempEnvelope;
}else
{
// preserve envelope
if(gridToCRS!=null){
// all we need is a simple scale and translate to take care of the fact that we might have approximated a bit the
// raster area
final double xScale=transformed.getSpan(0)/gridRange.getSpan(0);
final double yScale=transformed.getSpan(1)/gridRange.getSpan(1);
final double xTrans=transformed.getMinimum(0)-xScale*gridRange.getLow(0);
final double yTrans=transformed.getMinimum(1)-yScale*gridRange.getLow(1);
final AffineTransform newTr=new AffineTransform(xScale,0,0,yScale,xTrans,yTrans);
newTr.preConcatenate((AffineTransform)this.cornerToCRS);
this.cornerToCRS=
ProjectiveTransform.create(
newTr
);
this.gridToCRS=PixelTranslation.translate(cornerToCRS, PixelInCell.CELL_CORNER, PixelInCell.CELL_CENTER);
// Now 'assertsEnabled' is set to the correct value
if(assertsEnabled){
try {
final MathTransform tr=cornerToCRS;
if(!(tr instanceof AffineTransform))
return;
final AffineTransform transform=(AffineTransform)tr;
final double scale= Math.min(XAffineTransform.getScaleX0(transform), XAffineTransform.getScaleY0(transform));
final GeneralEnvelope tempEnvelope = CRS.transform(tr,toEnvelope(gridRange));
tempEnvelope.setCoordinateReferenceSystem(envelope.getCoordinateReferenceSystem());
assert tempEnvelope.equals(envelope,scale*1E-3,true):"Unable to preserve the envelope for this GridGeometry";
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
}
}
/**
* Constructs a new grid geometry from an envelope and a {@linkplain MathTransform math
* transform}. According OGC specification, the math transform should map {@linkplain
* PixelInCell#CELL_CENTER pixel center}. But in Java2D/JAI conventions, the transform
* is rather expected to maps {@linkplain PixelInCell#CELL_CORNER pixel corner}. The
* convention to follow can be specified by the {@code anchor} argument.
*
* @param anchor {@link PixelInCell#CELL_CENTER CELL_CENTER} for OGC conventions or
* {@link PixelInCell#CELL_CORNER CELL_CORNER} for Java2D/JAI conventions.
* @param gridToCRS The math transform which allows for the transformations from grid
* coordinates to real world earth coordinates. May be {@code null},
* but this is not recommended.
* @param envelope The envelope (including CRS) of a grid coverage, or {@code null} if none.
*
* @throws MismatchedDimensionException if the math transform and the envelope doesn't have
* consistent dimensions.
* @throws IllegalArgumentException if the math transform can't transform coordinates
* in the domain of the grid range.
*
* @since 2.5
*/
public GeneralGridGeometry(final PixelInCell anchor,
final MathTransform gridToCRS,
final Envelope envelope)
throws MismatchedDimensionException, IllegalArgumentException
{
this(anchor, gridToCRS, envelope, false,false);
}
/**
* Constructs a new grid geometry from an {@linkplain Envelope envelope}. An {@linkplain
* java.awt.geom.AffineTransform affine transform} will be computed automatically from the
* specified envelope using heuristic rules described in {@link GridToEnvelopeMapper} javadoc.
* More specifically, heuristic rules are applied for:
* <p>
* <ul>
* <li>{@linkplain GridToEnvelopeMapper#getSwapXY axis swapping}</li>
* <li>{@linkplain GridToEnvelopeMapper#getReverseAxis axis reversal}</li>
* </ul>
*
* @param gridRange The valid coordinate range of a grid coverage.
* @param userRange The corresponding coordinate range in user coordinate. This rectangle must
* contains entirely all pixels, i.e. the rectangle's upper left corner must
* coincide with the upper left corner of the first pixel and the rectangle's
* lower right corner must coincide with the lower right corner of the last
* pixel.
*
* @throws MismatchedDimensionException if the grid range and the envelope doesn't have
* consistent dimensions.
*
* @since 2.2
*/
public GeneralGridGeometry(final GridEnvelope gridRange, final Envelope userRange)
throws MismatchedDimensionException
{
this(gridRange, userRange, null, false, true);
}
/**
* Implementation of heuristic constructors.
*/
GeneralGridGeometry(final GridEnvelope gridRange,
final Envelope userRange,
final boolean[] reverse,
final boolean swapXY,
final boolean automatic)
throws MismatchedDimensionException
{
this.gridRange = clone(gridRange);
this.envelope = new GeneralEnvelope(userRange);
final GridToEnvelopeMapper mapper = new GridToEnvelopeMapper(gridRange, userRange);
if (!automatic) {
mapper.setReverseAxis(reverse);
mapper.setSwapXY(swapXY);
}
gridToCRS = mapper.createTransform();
}
/**
* Clones the given grid range if necessary. This is mostly a protection for {@link GridRange2D}
* which is mutable, at the opposite of {@link GeneralGridRange} which is immutable. We test for
* the {@link GridRange2D} super-class which defines a {@code clone()} method, instead of
* {@link GridRange2D} itself, for gaining some generality.
*/
private static GridEnvelope clone(GridEnvelope gridRange) {
if (gridRange instanceof Cloneable) {
gridRange = (GridEnvelope) ((Cloneable) gridRange).clone();
}
return gridRange;
}
/**
* Returns the number of dimensions.
*
* @return The number of dimensions.
*/
public int getDimension() {
if (gridToCRS != null) {
return gridToCRS.getSourceDimensions();
}
return gridRange.getDimension();
}
/**
* Returns the "real world" coordinate reference system.
*
* @return The coordinate reference system (never {@code null}).
* @throws InvalidGridGeometryException if this grid geometry has no CRS (i.e.
* <code>{@linkplain #isDefined isDefined}({@linkplain #CRS_BITMASK})</code>
* returned {@code false}).
*
* @see GridGeometry2D#getCoordinateReferenceSystem2D
*
* @since 2.2
*/
public CoordinateReferenceSystem getCoordinateReferenceSystem()
throws InvalidGridGeometryException
{
if (envelope != null) {
final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
if (crs != null) {
assert isDefined(CRS_BITMASK);
return crs;
}
}
assert !isDefined(CRS_BITMASK);
throw new InvalidGridGeometryException(ErrorKeys.UNSPECIFIED_CRS);
}
/**
* Returns the bounding box of "real world" coordinates for this grid geometry. This envelope is
* the {@linkplain #getGridRange grid range} {@linkplain #getGridToCoordinateSystem transformed}
* to the "real world" coordinate system.
*
* @return The bounding box in "real world" coordinates (never {@code null}).
* @throws InvalidGridGeometryException if this grid geometry has no envelope (i.e.
* <code>{@linkplain #isDefined isDefined}({@linkplain #ENVELOPE_BITMASK})</code>
* returned {@code false}).
*
* @see GridGeometry2D#getEnvelope2D
*/
public Envelope getEnvelope() throws InvalidGridGeometryException {
if (envelope!=null && !envelope.isNull()) {
assert isDefined(ENVELOPE_BITMASK);
return envelope.clone();
}
assert !isDefined(ENVELOPE_BITMASK);
throw new InvalidGridGeometryException(gridToCRS == null ?
ErrorKeys.UNSPECIFIED_TRANSFORM : ErrorKeys.UNSPECIFIED_IMAGE_SIZE);
}
/**
* Returns the valid coordinate range of a grid coverage. The lowest valid grid coordinate
* is zero for {@link java.awt.image.BufferedImage}, but may be non-zero for arbitrary
* {@link RenderedImage}. A grid with 512 cells can have a minimum coordinate of 0 and
* maximum of 512, with 511 as the highest valid index.
*
* @return The grid range (never {@code null}).
* @throws InvalidGridGeometryException if this grid geometry has no grid range (i.e.
* <code>{@linkplain #isDefined isDefined}({@linkplain #GRID_RANGE_BITMASK})</code>
* returned {@code false}).
*
* @see GridGeometry2D#getGridRange2D
*/
public GridEnvelope getGridRange() throws InvalidGridGeometryException {
if (gridRange != null) {
assert isDefined(GRID_RANGE_BITMASK);
return clone(gridRange);
}
assert !isDefined(GRID_RANGE_BITMASK);
throw new InvalidGridGeometryException(ErrorKeys.UNSPECIFIED_IMAGE_SIZE);
}
/**
* Returns the transform from grid coordinates to real world earth coordinates.
* The transform is often an affine transform. The coordinate reference system of the
* real world coordinates is given by
* {@link org.opengis.coverage.Coverage#getCoordinateReferenceSystem}.
* <p>
* <strong>Note:</strong> OpenGIS requires that the transform maps <em>pixel centers</em>
* to real world coordinates. This is different from some other systems that map pixel's
* upper left corner.
*
* @return The transform (never {@code null}).
* @throws InvalidGridGeometryException if this grid geometry has no transform (i.e.
* <code>{@linkplain #isDefined isDefined}({@linkplain #GRID_TO_CRS_BITMASK})</code>
* returned {@code false}).
*
* @see GridGeometry2D#getGridToCRS2D()
*
* @since 2.3
*/
public MathTransform getGridToCRS() throws InvalidGridGeometryException {
if (gridToCRS != null) {
assert isDefined(GRID_TO_CRS_BITMASK);
return gridToCRS;
}
assert !isDefined(GRID_TO_CRS_BITMASK);
throw new InvalidGridGeometryException(ErrorKeys.UNSPECIFIED_TRANSFORM);
}
/**
* Returns the transform from grid coordinates to real world earth coordinates.
* This is similar to {@link #getGridToCRS()} except that the transform may maps
* other parts than {@linkplain PixelInCell#CELL_CENTER pixel center}.
*
* @param anchor The pixel part to map.
* @return The transform (never {@code null}).
* @throws InvalidGridGeometryException if this grid geometry has no transform (i.e.
* <code>{@linkplain #isDefined isDefined}({@linkplain #GRID_TO_CRS_BITMASK})</code>
* returned {@code false}).
*
* @see GridGeometry2D#getGridToCRS(org.opengis.referencing.datum.PixelInCell)
*
* @since 2.3
*/
public MathTransform getGridToCRS(final PixelInCell anchor) throws InvalidGridGeometryException {
if (gridToCRS == null) {
throw new InvalidGridGeometryException(ErrorKeys.UNSPECIFIED_TRANSFORM);
}
if (PixelInCell.CELL_CENTER.equals(anchor)) {
return gridToCRS;
}
if (PixelInCell.CELL_CORNER.equals(anchor)) {
synchronized (this) {
if (cornerToCRS == null) {
cornerToCRS = PixelTranslation.translate(gridToCRS, PixelInCell.CELL_CENTER, anchor);
}
}
assert !cornerToCRS.equals(gridToCRS) : cornerToCRS;
return cornerToCRS;
}
return PixelTranslation.translate(gridToCRS, PixelInCell.CELL_CENTER, anchor);
}
/**
* Returns {@code true} if all the parameters specified by the argument are set.
*
* @param bitmask Any combinaison of {@link #CRS_BITMASK}, {@link #ENVELOPE_BITMASK}, {@link #GRID_RANGE_BITMASK}
* and {@link #GRID_TO_CRS_BITMASK}.
* @return {@code true} if all specified attributes are defined (i.e. invoking the
* corresponding method will not thrown an {@link InvalidGridGeometryException}).
* @throws IllegalArgumentException if the specified bitmask is not a combinaison of known
* masks.
*
* @since 2.2
*
* @see javax.media.jai.ImageLayout#isValid
*/
public boolean isDefined(final int bitmask) throws IllegalArgumentException {
if ((bitmask & ~(CRS_BITMASK | ENVELOPE_BITMASK | GRID_RANGE_BITMASK | GRID_TO_CRS_BITMASK)) != 0) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, "bitmask", bitmask));
}
return ((bitmask & CRS_BITMASK) == 0 || (envelope != null && envelope.getCoordinateReferenceSystem() != null))
&& ((bitmask & ENVELOPE_BITMASK) == 0 || (envelope != null && !envelope.isNull()))
&& ((bitmask & GRID_RANGE_BITMASK) == 0 || (gridRange != null))
&& ((bitmask & GRID_TO_CRS_BITMASK) == 0 || (gridToCRS != null));
}
/**
* Returns a hash value for this grid geometry. This value need not remain
* consistent between different implementations of the same class.
*/
@Override
public int hashCode() {
int code = (int) serialVersionUID;
if (gridToCRS != null) {
code += gridToCRS.hashCode();
}
if (gridRange != null) {
code += gridRange.hashCode();
}
// We do not check the envelope, since it usually has
// a determinist relationship with other attributes.
return code;
}
/**
* Compares the specified object with this grid geometry for equality.
*
* @param object The object to compare with.
* @return {@code true} if the given object is equals to this grid geometry.
*/
@Override
public boolean equals(final Object object) {
if (object!=null && object.getClass().equals(getClass())) {
final GeneralGridGeometry that = (GeneralGridGeometry) object;
return Utilities.equals(this.gridRange, that.gridRange) &&
Utilities.equals(this.gridToCRS, that.gridToCRS) &&
Utilities.equals(this.envelope , that.envelope );
// Do not compare cornerToCRS since it may not be computed yet,
// and should be strictly derived from gridToCRS anyway.
}
return false;
}
/**
* Returns a string representation of this grid geometry. The returned string
* is implementation dependent. It is usually provided for debugging purposes.
*/
@Override
public String toString() {
return Classes.getShortClassName(this) + '[' + gridRange + ", " + gridToCRS + ']';
}
/**
* Makes sure that an argument is non-null.
*
* @param name Argument name.
* @param object User argument.
* @throws IllegalArgumentException if {@code object} is null.
*/
static void ensureNonNull(final String name, final Object object)
throws IllegalArgumentException
{
if (object == null) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1, name));
}
}
}