/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2007-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.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.geom.AffineTransform;
import java.awt.image.RenderedImage;
import java.io.Serializable;
import org.opengis.coverage.grid.GridGeometry;
import org.opengis.metadata.spatial.PixelOrientation;
import org.apache.sis.util.Classes;
import org.geotoolkit.metadata.iso.spatial.PixelTranslation;
import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
/**
* A simple grid geometry holding the grid envelope as a {@linkplain Rectangle rectangle} and the
* <cite>grid to CRS</cite> relationship as an {@linkplain AffineTransform affine transform}.
* <p>
* This class does not specify whatever the <cite>grid to CRS</cite> transform maps pixel
* {@linkplain PixelOrientation#CENTER center} or {@linkplain PixelOrientation#UPPER_LEFT
* upper left} corner. The OGC convention is to map pixel center, while the Java2D convention
* is to map pixel corner. It is user responsibility to know the convention in use. For a more
* powerful class providing support for arbitrary convention, see {@link GridGeometry2D}.
* <p>
* This grid geometry does not hold any
* {@linkplain org.opengis.referencing.crs.CoordinateReferenceSystem Coordinate Reference System}
* information. Consequently it is not suitable for usage with {@link GridCoverage2D}. This
* {@code ImageGeometry} class is rather designed as a lightweight container for usage with
* {@link RenderedImage} instances.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.20
*
* @see GridGeometry2D
* @see GeneralGridGeometry
*
* @since 2.5
* @module
*/
public class ImageGeometry implements GridGeometry, Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -6578330391565232607L;
/**
* The extent of grid coordinates in the grid coverage.
*/
private final GridEnvelope2D extent;
/**
* The <cite>grid to CRS</cite> affine transform.
*/
private final AffineTransform2D gridToCRS;
/**
* Creates a grid geometry from the specified bounds and <cite>grid to CRS</cite>
* affine transform.
*
* @param bounds The image bounds in pixel coordinates.
* @param gridToCRS The affine transform from pixel coordinates to "real world" coordinates.
*/
public ImageGeometry(final Rectangle bounds, AffineTransform gridToCRS) {
this.extent = new GridEnvelope2D(bounds);
if (gridToCRS.getClass() == AffineTransform2D.class) {
// Cast only if this is exactly the AffineTransform2D class,
// not a subclass (otherwise it could be mutable).
this.gridToCRS = (AffineTransform2D) gridToCRS;
} else {
this.gridToCRS = new AffineTransform2D(gridToCRS);
}
}
/**
* Returns the image bounds in pixel coordinates.
*
* @since 3.20 (derived from 2.5)
*/
@Override
public GridEnvelope2D getExtent() {
return getGridRange();
}
/**
* @deprecated Renamed {@link #getExtent()}.
*/
@Override
@Deprecated
public GridEnvelope2D getGridRange() {
return extent.clone();
}
/**
* Returns the conversion from grid coordinates to real world earth coordinates.
*/
@Override
public AffineTransform2D getGridToCRS() {
return gridToCRS; // No need to clone since AffineTransform2D is immutable.
}
/**
* Returns the georeferenced image envelope in "real world" coordinates. This is the
* {@linkplain #getExtent grid extent} transformed using the {@link #getGridToCRS
* grid to CRS} transform. The transform may maps pixel center or a corner, depending
* on the value of the {@code orientation} argument.
* <p>
* According OGC specification, the transform shall maps pixel center. However Java2D usage
* is to maps the upper-left corner. Because this {@code ImageGeometry} class is primarily
* designed for use with {@linkplain RenderedImage rendered images}, this method allows to
* override the OGC behavior with the Java2D one if needed.
*
* @param orientation Whatever the transform maps pixel center or a corner. If this argument
* is not provided, the default value is {@link PixelOrientation#CENTER CENTER}.
* @return The image envelope in "real world" coordinates.
*
* @since 3.00
*/
public Rectangle2D getEnvelope(final PixelOrientation orientation) {
// Reminder: this algorithm must be consistent with GeneralEnvelope(GridEnvelope, ...).
final PixelTranslation pt = PixelTranslation.getPixelTranslation(orientation);
final Rectangle gr = extent;
final Rectangle2D.Double envelope = new Rectangle2D.Double(
gr.x - (pt.dx + 0.5), gr.y - (pt.dy + 0.5), gr.width, gr.height);
return AffineTransforms2D.transform(gridToCRS, envelope, envelope);
}
/**
* 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) + '[' + extent + ", " + gridToCRS + ']';
}
/**
* Returns a hash code value for this grid geometry.
*/
@Override
public int hashCode() {
return extent.hashCode() ^ gridToCRS.hashCode();
}
/**
* Compares this grid geometry with the specified one for equality.
*
* @param object The object to compare with.
* @return {@code true} if the given object is equal to this grid geometry.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
if (object != null && object.getClass() == getClass()) {
final ImageGeometry that = (ImageGeometry) object;
return Objects.equals(extent, that.extent) &&
Objects.equals(gridToCRS, that.gridToCRS);
}
return false;
}
}