/* * 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.referencing.operation.builder; import java.util.Arrays; import java.awt.image.BufferedImage; import java.awt.geom.AffineTransform; import org.opengis.coverage.grid.GridEnvelope; import org.opengis.geometry.Envelope; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.cs.AxisDirection; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.Matrix; import org.geotools.referencing.operation.matrix.MatrixFactory; import org.geotools.referencing.operation.transform.ProjectiveTransform; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.util.Utilities; /** * A helper class for building n-dimensional {@linkplain AffineTransform * affine transform} mapping {@linkplain GridEnvelope grid ranges} to {@linkplain Envelope * envelopes}. The affine transform will be computed automatically from the information * specified by the {@link #setGridRange setGridRange} and {@link #setEnvelope setEnvelope} * methods, which are mandatory. All other setter methods are optional hints about the * affine transform to be created. This builder is convenient when the following conditions * are meet: *
*
Pixels coordinates (usually (x,y) integer values inside * the rectangle specified by the grid range) are expressed in some * {@linkplain CoordinateReferenceSystem coordinate reference system} known at compile * time. This is often the case. For example the CRS attached to {@link BufferedImage} * has always ({@linkplain AxisDirection#COLUMN_POSITIVE column}, * {@linkplain AxisDirection#ROW_POSITIVE row}) axis, with the origin (0,0) in the upper * left corner, and row values increasing down.
"Real world" coordinates (inside the envelope) are expressed in arbitrary * horizontal coordinate reference system. Axis directions may be * ({@linkplain AxisDirection#NORTH North}, {@linkplain AxisDirection#WEST West}), * or ({@linkplain AxisDirection#EAST East}, {@linkplain AxisDirection#NORTH North}), * etc..
* In such case (and assuming that the image's CRS has the same characteristics than the * {@link BufferedImage}'s CRS described above): *
*
{@link #setSwapXY swapXY} shall be set to {@code true} if the "real world" axis * order is ({@linkplain AxisDirection#NORTH North}, {@linkplain AxisDirection#EAST East}) * instead of ({@linkplain AxisDirection#EAST East}, {@linkplain AxisDirection#NORTH North}). * This axis swapping is necessary for mapping the ({@linkplain AxisDirection#COLUMN_POSITIVE * column}, {@linkplain AxisDirection#ROW_POSITIVE row}) axis order associated to the * image CRS.
In addition, the "real world" axis directions shall be reversed (by invoking
* {@linkplain #reverseAxis reverseAxis}(dimension)
) if their direction is
* {@link AxisDirection#WEST WEST} (x axis) or {@link AxisDirection#NORTH NORTH}
* (y axis), in order to get them oriented toward the {@link AxisDirection#EAST
* EAST} or {@link AxisDirection#SOUTH SOUTH} direction respectively. The later may seems
* unatural, but it reflects the fact that row values are increasing down in an
* {@link BufferedImage}'s CRS.
{@linkplain #isAutomatic isAutomatic}({@linkplain #SWAP_XY})
* returns {@code true} (which is the default), then this method make the
* following assumptions:
*
* Axis order in the grid range matches exactly axis order in the envelope, except * for the special case described in the next point. In other words, if axis order in * the underlying image is (column, row) (which is the case for * a majority of images), then the envelope should probably have a (longitude, * latitude) or (easting, northing) axis order.
An exception to the above rule applies for CRS using exactly the following axis * order: ({@link AxisDirection#NORTH NORTH}|{@link AxisDirection#SOUTH SOUTH}, * {@link AxisDirection#EAST EAST}|{@link AxisDirection#WEST WEST}). An example * of such CRS is {@code EPSG:4326}. In this particular case, this method will * returns {@code true}, thus suggesting to interchange the * (y,x) axis for such CRS.
{@linkplain #isAutomatic isAutomatic}({@linkplain #SWAP_XY})
to
* {@code false}.
*
* @param swapXY {@code true} if the two first axis should be interchanged.
*/
public void setSwapXY(final boolean swapXY) {
final Boolean newValue = Boolean.valueOf(swapXY);
if (!newValue.equals(this.swapXY)) {
reset();
}
this.swapXY = newValue;
defined |= SWAP_XY;
}
/**
* Returns which (if any) axis in user space
* (not grid space) should have their direction reversed. If
* {@linkplain #isAutomatic isAutomatic}({@linkplain #REVERSE_AXIS})
* returns {@code true} (which is the default), then this method make the
* following assumptions:
* *
{@linkplain #isAutomatic isAutomatic}({@linkplain #REVERSE_AXIS})
* to {@code false}.
*
* @param reverse The reversal state of each axis. A {@code null} value means to
* reverse no axis.
*/
public void setReverseAxis(final boolean[] reverse) {
if (!Arrays.equals(reverseAxis, reverse)) {
reset();
}
this.reverseAxis = reverse;
defined |= REVERSE_AXIS;
}
/**
* Reverses a single axis in user space. Invoking this methods n time
* is equivalent to creating a boolean {@code reverse} array of the appropriate length,
* setting {@code reverse[dimension] = true} for the n axis to be reversed,
* and invoke {@linkplain #setReverseAxis setReverseAxis}(reverse)
.
*
* @param dimension The index of the axis to reverse.
*/
public void reverseAxis(final int dimension) {
if (reverseAxis == null) {
final int length;
if (gridRange != null) {
length = gridRange.getDimension();
} else {
ensureNonNull("envelope", envelope);
length = envelope.getDimension();
}
reverseAxis = new boolean[length];
}
if (!reverseAxis[dimension]) {
reset();
}
reverseAxis[dimension] = true;
defined |= REVERSE_AXIS;
}
/**
* Returns {@code true} if all properties designed by the specified bit mask
* will be computed automatically.
*
* @param mask Any combinaison of {@link #REVERSE_AXIS} or {@link #SWAP_XY}.
* @return {@code true} if all properties given by the mask will be computed automatically.
*/
public boolean isAutomatic(final int mask) {
return (defined & mask) == 0;
}
/**
* Set all properties designed by the specified bit mask as automatic. Their
* value will be computed automatically by the corresponding methods (e.g.
* {@link #getReverseAxis}, {@link #getSwapXY}). By default, all properties
* are automatic.
*
* @param mask Any combinaison of {@link #REVERSE_AXIS} or {@link #SWAP_XY}.
*/
public void setAutomatic(final int mask) {
defined &= ~mask;
}
/**
* Returns the coordinate system in use with the envelope.
*/
private CoordinateSystem getCoordinateSystem() {
if (envelope != null) {
final CoordinateReferenceSystem crs;
crs = envelope.getCoordinateReferenceSystem();
if (crs != null) {
return crs.getCoordinateSystem();
}
}
return null;
}
/**
* Creates a math transform using the information provided by setter methods.
*
* @return The math transform.
* @throws IllegalStateException if the grid range or the envelope were not set.
*/
public MathTransform createTransform() throws IllegalStateException {
if (transform == null) {
final GridEnvelope gridRange = getGridRange();
final Envelope userRange = getEnvelope();
final boolean swapXY = getSwapXY();
final boolean[] reverse = getReverseAxis();
final PixelInCell gridType = getPixelAnchor();
final int dimension = gridRange.getDimension();
/*
* Setup the multi-dimensional affine transform for use with OpenGIS.
* According OpenGIS specification, transforms must map pixel center.
* This is done by adding 0.5 to grid coordinates.
*/
final double translate;
if (PixelInCell.CELL_CENTER.equals(gridType)) {
translate = 0.5;
} else if (PixelInCell.CELL_CORNER.equals(gridType)) {
translate = 0.0;
} else {
throw new IllegalStateException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
"gridType", gridType));
}
final Matrix matrix = MatrixFactory.create(dimension + 1);
for (int i=0; i