/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2001-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* 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.geotoolkit.coverage.grid;
import java.util.Objects;
import java.io.Serializable;
import java.awt.image.RenderedImage;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.coverage.grid.GridGeometry;
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.crs.CoordinateReferenceSystem;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.metadata.spatial.PixelOrientation; // For javadoc
import org.apache.sis.math.MathFunctions;
import org.geotoolkit.util.Cloneable;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.ImmutableEnvelope;
import org.geotoolkit.metadata.iso.spatial.PixelTranslation;
import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.geotoolkit.referencing.operation.builder.GridToEnvelopeMapper;
import org.geotoolkit.resources.Errors;
import static org.apache.sis.util.ArgumentChecks.*;
/**
* Describes the valid range of grid coordinates and the transform from those 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 "<cite>grid to CRS</cite>" {@linkplain MathTransform transform}, which can
* be inferred from the grid envelope and the georeferenced envelope.</li>
* <li>An optional georeferenced {@linkplain Envelope envelope}, which can be inferred from
* the grid envelope and the "<cite>grid to CRS</cite>" transform.</li>
* <li>An optional {@linkplain CoordinateReferenceSystem coordinate reference system} (CRS)
* to be given to the envelope. This CRS is the target of the <cite>grid to CRS</cite>
* transform.</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 envelope} 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}.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Alessio Fabiani (Geosolutions)
* @version 3.20
*
* @see GridGeometry2D
* @see ImageGeometry
*
* @since 1.2
* @module
*/
public class GeneralGridGeometry implements GridGeometry, Serializable {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = 670887173069270234L;
/**
* Empirical tolerance factor while fixing rounding errors in envelope.
*
* @see org.geotoolkit.coverage.sql.GridGeometryTableTest#testEnvelope()
*/
private static final int ULP_TOLERANCE = 16;
/**
* A bitmask to specify the validity of the {@linkplain #getCoordinateReferenceSystem()
* coordinate reference system}. This is given in argument to the {@link #isDefined(int)}
* method.
*
* @since 2.2
*/
public static final int CRS = 1;
/**
* A bitmask to specify the validity of the geodetic {@linkplain #getEnvelope() envelope}.
* This is given in argument to the {@link #isDefined(int)} method.
*
* @since 2.2
*/
public static final int ENVELOPE = 2;
/**
* A bitmask to specify the validity of the {@linkplain #getExtent() grid envelope}.
* This is given in argument to the {@link #isDefined(int)} method.
*
* @since 3.20 (derived from 2.2)
*/
public static final int EXTENT = 4;
/**
* A bitmask to specify the validity of the {@linkplain #getGridToCRS() grid to CRS}
* transform. This is given in argument to the {@link #isDefined(int)} method.
*
* @since 2.2
*/
public static final int GRID_TO_CRS = 8;
/**
* The valid domain 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.
*
* {@note This field name was <code>gridRange</code> in all Geotk versions prior 3.20. The
* <cite>grid range</cite> name was defined in the legacy OGC 01-004 specification,
* while <cite>extent</cite> is defined in the ISO 19123 specification. This field
* has been renamed in order to avoid confusion with <cite>coverage range</cite>,
* which has a totally different meaning in ISO 19123.}
*
* @see RenderedImage#getMinX()
* @see RenderedImage#getMinY()
* @see RenderedImage#getWidth()
* @see RenderedImage#getHeight()
*/
protected final GridEnvelope extent;
/**
* The geodetic envelope, or {@code null} if none. If non-null, this envelope is usually the
* {@linkplain #extent grid envelope} {@linkplain #gridToCRS transformed} to real world
* coordinates. The {@linkplain CoordinateReferenceSystem coordinate reference system} (CRS)
* of this envelope defines the "real world" CRS of this grid geometry.
*
* @since 3.20
*/
protected final ImmutableEnvelope envelope;
/**
* The math transform from grid indices to "real world" coordinates, or {@code null} if none.
* This math transform is usually affine. It maps {@linkplain PixelInCell#CELL_CENTER pixel center}
* to "real world" coordinate using the following line:
*
* {@preformat java
* DirectPosition aCellIndices = ...:
* DirectPosition aPixelCenter = gridToCRS.transform(pixels, aCellIndices);
* }
*/
protected final 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;
/**
* The resolution in units of the CRS axes.
* Computed only when first needed.
*/
private transient double[] resolution;
/**
* 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) {
extent = gm.extent; // Do not clone; we assume it is safe to share.
gridToCRS = gm.gridToCRS;
cornerToCRS = gm.cornerToCRS;
envelope = new ImmutableEnvelope(crs, gm.envelope);
}
/**
* 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;
extent = general.extent; // Do not clone; we assume it is safe to share.
gridToCRS = general.gridToCRS;
cornerToCRS = general.cornerToCRS;
envelope = general.envelope;
} else {
GeneralEnvelope env = null;
extent = other.getExtent();
gridToCRS = other.getGridToCRS();
if (extent != null && gridToCRS != null) {
env = new org.geotoolkit.geometry.GeneralEnvelope(extent, PixelInCell.CELL_CENTER, gridToCRS, null);
((org.geotoolkit.geometry.GeneralEnvelope) env).roundIfAlmostInteger(360, ULP_TOLERANCE);
}
envelope = (env != null) ? new ImmutableEnvelope(env) : null;
}
}
/**
* Constructs a new grid geometry from a grid envelope and a {@linkplain MathTransform math
* transform} mapping {@linkplain PixelInCell#CELL_CENTER pixel center}.
*
* @param extent
* The valid extent of grid coordinates, or {@code null} if none.
* @param gridToCRS
* The math transform which allows for the transformations from grid coordinates
* (pixel <em>center</em>) 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 envelope.
*
* @since 2.2
*/
public GeneralGridGeometry(final GridEnvelope extent,
final MathTransform gridToCRS,
final CoordinateReferenceSystem crs)
throws MismatchedDimensionException, IllegalArgumentException
{
this(extent, PixelInCell.CELL_CENTER, gridToCRS, crs);
}
/**
* Constructs a new grid geometry from a grid envelope 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 extent
* The valid extent of grid coordinates, 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 envelope.
*
* @since 2.5
*/
public GeneralGridGeometry(final GridEnvelope extent,
final PixelInCell anchor,
final MathTransform gridToCRS,
final CoordinateReferenceSystem crs)
throws MismatchedDimensionException, IllegalArgumentException
{
if (gridToCRS != null) {
if (extent != null) {
ensureDimensionMatch("extent", extent.getDimension(), gridToCRS.getSourceDimensions());
}
if (crs != null) {
ensureDimensionMatch("crs", crs.getCoordinateSystem().getDimension(), gridToCRS.getTargetDimensions());
}
}
this.extent = clone(extent);
this.gridToCRS = PixelTranslation.translate(gridToCRS, anchor, PixelInCell.CELL_CENTER);
if (PixelInCell.CELL_CORNER.equals(anchor)) {
cornerToCRS = gridToCRS;
}
GeneralEnvelope env = null;
if (extent != null && gridToCRS != null) {
env = new org.geotoolkit.geometry.GeneralEnvelope(extent, anchor, gridToCRS, crs);
((org.geotoolkit.geometry.GeneralEnvelope) env).roundIfAlmostInteger(360, ULP_TOLERANCE);
} else if (crs != null) {
env = new GeneralEnvelope(crs);
env.setToNaN();
}
envelope = (env != null) ? new ImmutableEnvelope(env) : null;
}
/**
* 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 envelope.
*
* @since 2.5
*/
public GeneralGridGeometry(final PixelInCell anchor,
final MathTransform gridToCRS,
final Envelope envelope)
throws MismatchedDimensionException, IllegalArgumentException
{
if (gridToCRS != null && envelope != null) {
ensureDimensionMatch("envelope", envelope.getDimension(), gridToCRS.getTargetDimensions());
}
this.gridToCRS = PixelTranslation.translate(gridToCRS, anchor, PixelInCell.CELL_CENTER);
if (PixelInCell.CELL_CORNER.equals(anchor)) {
cornerToCRS = gridToCRS;
}
this.envelope = ImmutableEnvelope.castOrCopy(envelope);
if (envelope == null) {
this.extent = null;
return;
}
if (gridToCRS == null) {
this.extent = null;
return;
}
final GeneralEnvelope transformed;
try {
transformed = Envelopes.transform(gridToCRS.inverse(), envelope);
} catch (TransformException exception) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalTransformForType_1,
gridToCRS.getClass()), exception);
}
extent = new GeneralGridEnvelope(transformed, anchor, 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 extent
* The valid extent of grid coordinates, or {@code null} if none.
* @param envelope
* The corresponding domain in "real world" coordinates. 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 envelope and the georeferenced envelope doesn't have consistent dimensions.
*
* @since 2.2
*/
public GeneralGridGeometry(final GridEnvelope extent, final Envelope envelope)
throws MismatchedDimensionException
{
this(extent, envelope, null, false, true);
}
/**
* Implementation of heuristic constructors.
*/
GeneralGridGeometry(final GridEnvelope extent,
final Envelope envelope,
final boolean[] reverse,
final boolean swapXY,
final boolean automatic)
throws MismatchedDimensionException
{
ensureNonNull("extent", extent);
ensureNonNull("envelope", envelope);
this.extent = clone(extent);
this.envelope = ImmutableEnvelope.castOrCopy(envelope);
final GridToEnvelopeMapper mapper = new GridToEnvelopeMapper(extent, envelope);
if (!automatic) {
mapper.setReverseAxis(reverse);
mapper.setSwapXY(swapXY);
}
gridToCRS = mapper.createTransform();
}
/**
* Clones the given grid envelope if necessary. This is mostly a protection for {@link GridEnvelope2D}
* which is mutable, at the opposite of {@link GeneralGridEnvelope} which is immutable. We test
* for the {@link GridEnvelope2D} super-class which defines a {@code clone()} method, instead of
* {@link GridEnvelope2D} itself, for gaining some generality.
*/
private static GridEnvelope clone(GridEnvelope extent) {
if (extent instanceof Cloneable) {
extent = (GridEnvelope) ((Cloneable) extent).clone();
}
return extent;
}
/**
* Ensures that the given dimension is equals to the expected value. If not, throw an
* exception.
*
* @param argument The name of the argument being tested.
* @param dimension The dimension of the argument value.
* @param expected The expected dimension.
*/
static void ensureDimensionMatch(final String argument, final int dimension, final int expected)
throws MismatchedDimensionException
{
if (dimension != expected) {
throw new MismatchedDimensionException(Errors.format(
Errors.Keys.MismatchedDimension_3, argument, dimension, expected));
}
}
/**
* Returns the number of dimensions of the <em>grid</em>. This is typically the same
* than the number of dimension of the envelope or the CRS, but not necessarily.
*
* @return The number of grid dimensions.
*/
public int getDimension() {
if (gridToCRS != null) {
return gridToCRS.getSourceDimensions();
}
return extent.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})</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);
return crs;
}
}
assert !isDefined(CRS);
throw new InvalidGridGeometryException(Errors.Keys.UnspecifiedCrs);
}
/**
* Returns the bounding box of "real world" coordinates for this grid geometry. This envelope
* is the {@linkplain #getExtent() grid extent} {@linkplain #getGridToCRS 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(int) isDefined}({@linkplain #ENVELOPE})</code>
* returned {@code false}).
*
* @see GridGeometry2D#getEnvelope2D()
*/
public Envelope getEnvelope() throws InvalidGridGeometryException {
if (envelope != null && !envelope.isAllNaN()) {
assert isDefined(ENVELOPE);
return envelope;
}
assert !isDefined(ENVELOPE);
throw new InvalidGridGeometryException(gridToCRS == null ?
Errors.Keys.UnspecifiedTransform : Errors.Keys.UnspecifiedImageSize);
}
/**
* 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 envelope (never {@code null}).
* @throws InvalidGridGeometryException if this grid geometry has no extent (i.e.
* <code>{@linkplain #isDefined(int) isDefined}({@linkplain #EXTENT})</code>
* returned {@code false}).
*
* @see GridGeometry2D#getGridRange2D()
*
* @since 3.20 (derived from 1.2)
*/
@Override
public GridEnvelope getExtent() throws InvalidGridGeometryException {
return getGridRange();
}
/**
* @deprecated Renamed {@link #getExtent()}.
*/
@Override
@Deprecated
public GridEnvelope getGridRange() throws InvalidGridGeometryException {
if (extent != null) {
assert isDefined(EXTENT);
return clone(extent);
}
assert !isDefined(EXTENT);
throw new InvalidGridGeometryException(Errors.Keys.UnspecifiedImageSize);
}
/**
* 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(int) isDefined}({@linkplain #GRID_TO_CRS})</code>
* returned {@code false}).
*
* @see GridGeometry2D#getGridToCRS2D()
*
* @since 2.3
*/
@Override
public MathTransform getGridToCRS() throws InvalidGridGeometryException {
if (gridToCRS != null) {
assert isDefined(GRID_TO_CRS);
return gridToCRS;
}
assert !isDefined(GRID_TO_CRS);
throw new InvalidGridGeometryException(Errors.Keys.UnspecifiedTransform);
}
/**
* 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(int) isDefined}({@linkplain #GRID_TO_CRS})</code>
* returned {@code false}).
*
* @see GridGeometry2D#getGridToCRS(PixelOrientation)
* @see org.geotoolkit.referencing.cs.DiscreteReferencingFactory#getAffineTransform(GridGeometry, PixelInCell)
* @see org.geotoolkit.metadata.iso.spatial.PixelTranslation
*
* @since 2.3
*/
public MathTransform getGridToCRS(final PixelInCell anchor) throws InvalidGridGeometryException {
if (gridToCRS == null) {
throw new InvalidGridGeometryException(Errors.Keys.UnspecifiedTransform);
}
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 the grid resolution in units of the {@linkplain #getCoordinateReferenceSystem()
* Coordinate Reference System} axes, or {@code null} if it can't be computed. If non-null,
* the length of the returned array is the number of CRS dimension.
*
* @return The grid resolution, or {@code null} if unknown.
*
* @since 3.10
*/
public synchronized double[] getResolution() {
double[] resolution = this.resolution;
if (resolution == null) {
if (gridToCRS instanceof LinearTransform) {
final Matrix mat = ((LinearTransform) gridToCRS).getMatrix();
resolution = new double[mat.getNumRow() - 1];
final double[] buffer = new double[mat.getNumCol() - 1];
for (int j=0; j<resolution.length; j++) {
for (int i=0; i<buffer.length; i++) {
buffer[i] = mat.getElement(j,i);
}
resolution[j] = MathFunctions.magnitude(buffer);
}
this.resolution = resolution;
}
}
return (resolution != null) ? resolution.clone() : null;
}
/**
* Returns {@code true} if all the parameters specified by the argument are set.
*
* @param bitmask Any combination of {@link #CRS}, {@link #ENVELOPE}, {@link #EXTENT}
* and {@link #GRID_TO_CRS}.
* @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 combination of known
* masks.
*
* @since 2.2
*
* @see javax.media.jai.ImageLayout#isValid
*/
public boolean isDefined(final int bitmask) throws IllegalArgumentException {
if ((bitmask & ~(CRS | ENVELOPE | EXTENT | GRID_TO_CRS)) != 0) {
throw new IllegalArgumentException(Errors.format(
Errors.Keys.IllegalArgument_2, "bitmask", bitmask));
}
return ((bitmask & CRS) == 0 || (envelope != null && envelope.getCoordinateReferenceSystem() != null))
&& ((bitmask & ENVELOPE) == 0 || (envelope != null && !envelope.isAllNaN()))
&& ((bitmask & EXTENT) == 0 || (extent != null))
&& ((bitmask & GRID_TO_CRS) == 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 (extent != null) {
code += extent.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() == getClass()) {
final GeneralGridGeometry that = (GeneralGridGeometry) object;
return Objects.equals(this.extent, that.extent) &&
Objects.equals(this.gridToCRS, that.gridToCRS) &&
Objects.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 getClass().getSimpleName() + '[' + extent + ", " + gridToCRS + ']';
}
}