org.geotoolkit.metadata.iso.spatial.PixelTranslation Maven / Gradle / Ivy
/*
* 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.metadata.iso.spatial;
import java.util.Map;
import java.util.HashMap;
import java.io.Serializable;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.metadata.spatial.PixelOrientation;
import static org.opengis.metadata.spatial.PixelOrientation.*;
import org.geotoolkit.lang.Static;
import org.geotoolkit.util.Utilities;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.referencing.operation.matrix.Matrices;
import org.geotoolkit.referencing.operation.MathTransforms;
/**
* The translation to apply for different values of {@link PixelOrientation}
* or {@link PixelInCell}. The translation are returned by a call to one of
* the following static method:
*
*
* - {@link #getPixelTranslation(PixelOrientation)} for the two-dimensional case
* - {@link #getPixelTranslation(PixelInCell)} for the n-dimensional case.
*
*
* This class provides also a few {@code translate(...)} convenience methods, which apply the
* pixel translation on a given {@link MathTransform} instance.
*
* {@section Example}
* If the following code snippet, {@code gridToCRS} is an {@link java.awt.geom.AffineTransform}
* from grid cell coordinates (typically pixel coordinates) to some arbitrary CRS
* coordinates. In this example, the transform maps pixels {@linkplain PixelOrientation#CENTER
* center}, while the {@linkplain PixelOrientation#UPPER_LEFT upper left} corner is desired.
* This code will switch the affine transform from the pixel center to
* upper left corner convention:
*
* {@preformat java
* final AffineTransform gridToCRS = ...;
* final PixelOrientation current = PixelOrientation.CENTER;
* final PixelOrientation expected = PixelOrientation.UPPER_LEFT;
*
* // Switch the transform from 'current' to 'expected' convention.
* final PixelTranslation source = getPixelTranslation(current);
* final PixelTranslation target = getPixelTranslation(expected);
* gridToCRS.translate(target.dx - source.dx,
* target.dy - source.dy);
* }
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.00
*
* @see org.geotoolkit.coverage.grid.GeneralGridGeometry#getGridToCRS(PixelInCell)
* @see org.geotoolkit.coverage.grid.GridGeometry2D#getGridToCRS(PixelOrientation)
*
* @since 2.5
* @module
*/
public final class PixelTranslation extends Static implements Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 2616596940766158984L;
/**
* Math transforms created by {@link #translate(MathTransform,PixelInCell,PixelInCell)}.
* Each element in this array will be created when first needed. Even index are translations
* by -0.5 while odd index are translations by +0.5.
*/
private static final MathTransform[] translations = new MathTransform[16];
/**
* The pixel orientation for this translation.
*/
public final PixelOrientation orientation;
/**
* The translation among the x axis relative to pixel center.
* The value is typically in the [-0.5 .. +0.5] range.
*/
public final double dx;
/**
* The translation among the y axis relative to pixel center.
* The value is typically in the [-0.5 .. +0.5] range.
*/
public final double dy;
/**
* The offset for various pixel orientations. Keys must be upper-case names.
*/
private static final Map ORIENTATIONS =
new HashMap(12);
static {
new PixelTranslation(CENTER, 0.0, 0.0);
new PixelTranslation(UPPER_LEFT, -0.5, -0.5);
new PixelTranslation(UPPER_RIGHT, 0.5, -0.5);
new PixelTranslation(LOWER_LEFT, -0.5, 0.5);
new PixelTranslation(LOWER_RIGHT, 0.5, 0.5);
new PixelTranslation("LEFT", -0.5, 0.0);
new PixelTranslation("RIGHT", 0.5, 0.0);
new PixelTranslation("UPPER", 0.0, -0.5);
new PixelTranslation("LOWER", 0.0, 0.5);
}
/**
* Creates a new pixel translation. The translation is added immediately in the
* {@link #ORIENTATIONS} map (this behavior would need to be revisited if this
* method was public).
*/
private PixelTranslation(final PixelOrientation orientation, final double dx, final double dy) {
this.orientation = orientation;
this.dx = dx;
this.dy = dy;
if (ORIENTATIONS.put(orientation, this) != null) {
throw new AssertionError(this);
}
}
/**
* Creates a new pixel translation. The translation is added immediately in the
* {@link #ORIENTATIONS} map (this behavior would need to be revisited if this
* method was public).
*/
private PixelTranslation(final String orientation, final double dx, final double dy) {
this(valueOf(orientation), dx, dy);
}
/**
* Returns the pixel orientation for the given {@code PixelInCell} code.
*
* @param anchor The {@code PixelInCell} code, or {@code null}.
* @return The corresponding pixel orientation, or {@code null} if the argument was null.
* @throws IllegalArgumentException if the given code is unknown.
*/
public static PixelOrientation getPixelOrientation(final PixelInCell anchor)
throws IllegalArgumentException
{
if (anchor == null) {
return null;
} else if (anchor.equals(PixelInCell.CELL_CENTER)) {
return CENTER;
} else if (anchor.equals(PixelInCell.CELL_CORNER)) {
return UPPER_LEFT;
} else {
throw new IllegalArgumentException(Errors.format(
Errors.Keys.ILLEGAL_ARGUMENT_$2, "anchor", anchor));
}
}
/**
* Returns the position relative to the pixel center.
* This method returns a value from the following table:
*
*
* Pixel in cell offset
* {@link PixelInCell#CELL_CENTER CELL_CENTER} 0.0
* {@link PixelInCell#CELL_CORNER CELL_CORNER} -0.5
*
*
* This method is typically used for n-dimensional grids,
* where the number of dimension is unknown.
*
* @param anchor The "pixel in cell" value.
* @return The translation for the given "pixel in cell" value.
*/
public static double getPixelTranslation(final PixelInCell anchor) {
if (PixelInCell.CELL_CENTER.equals(anchor)) {
return 0;
} else if (PixelInCell.CELL_CORNER.equals(anchor)) {
return -0.5;
} else {
throw new IllegalArgumentException(Errors.format(
Errors.Keys.ILLEGAL_ARGUMENT_$2, "anchor", anchor));
}
}
/**
* Returns the specified position relative to the pixel center.
* This method returns a value from the following table:
*
*
* Pixel orientation x y
* {@link PixelOrientation#CENTER CENTER} 0.0 0.0
* {@link PixelOrientation#UPPER_LEFT UPPER_LEFT} -0.5 -0.5
* {@link PixelOrientation#UPPER_RIGHT UPPER_RIGHT} +0.5 -0.5
* {@link PixelOrientation#LOWER_LEFT LOWER_LEFT} -0.5 +0.5
* {@link PixelOrientation#LOWER_RIGHT LOWER_RIGHT} +0.5 +0.5
*
*
* This method can be used for grid restricted to 2 dimensions.
*
* @param anchor The pixel orientation.
* @return The position relative to the pixel center.
* @throws IllegalArgumentException if the specified orientation is unknown.
*/
public static PixelTranslation getPixelTranslation(final PixelOrientation anchor)
throws IllegalArgumentException
{
final PixelTranslation offset = ORIENTATIONS.get(anchor);
if (offset == null) {
throw new IllegalArgumentException(Errors.format(
Errors.Keys.ILLEGAL_ARGUMENT_$2, "anchor", anchor));
}
return offset;
}
/**
* Returns the pixel orientation for the given offset, or {@code null} if none.
* This is the reverse of {@link #getPixelTranslation(PixelOrientation)}.
*
* @param dx The translation along x axis.
* @param dy The translation along y axis.
* @return The pixel orientation of the given values, or {@code null} if none.
*/
public static PixelOrientation getPixelOrientation(final double dx, final double dy) {
for (final PixelTranslation candidate : ORIENTATIONS.values()) {
if (candidate.dx == dx && candidate.dy == dy) {
return candidate.orientation;
}
}
return null;
}
/**
* Translates the specified math transform according the specified pixel orientations.
*
* @param gridToCRS A math transform from pixel coordinates to any CRS.
* @param current The pixel orientation of the given {@code gridToCRS} transform.
* @param expected The pixel orientation of the desired transform.
* @return The translation from {@code current} to {@code expected}.
*/
public static MathTransform translate(final MathTransform gridToCRS,
final PixelInCell current,
final PixelInCell expected)
{
if (Utilities.equals(current, expected)) {
return gridToCRS;
}
if (gridToCRS == null) {
return null;
}
final int dimension = gridToCRS.getSourceDimensions();
final double offset = getPixelTranslation(expected) - getPixelTranslation(current);
final int index;
if (offset == -0.5) {
index = 2*dimension;
} else if (offset == 0.5) {
index = 2*dimension + 1;
} else {
index = translations.length;
}
MathTransform mt;
if (index >= translations.length) {
mt = MathTransforms.linear(dimension, 1, offset);
} else synchronized (translations) {
mt = translations[index];
if (mt == null) {
mt = MathTransforms.linear(dimension, 1, offset);
translations[index] = mt;
}
}
return MathTransforms.concatenate(mt, gridToCRS);
}
/**
* Translates the specified math transform according the specified pixel orientations.
*
* @param gridToCRS A math transform from pixel coordinates to any CRS.
* @param current The pixel orientation of the given {@code gridToCRS} transform.
* @param expected The pixel orientation of the desired transform.
* @param xDimension The dimension of x coordinates (pixel columns). Often 0.
* @param yDimension The dimension of y coordinates (pixel rows). Often 1.
* @return The translation from {@code current} to {@code expected}.
*/
public static MathTransform translate(final MathTransform gridToCRS,
final PixelOrientation current,
final PixelOrientation expected,
final int xDimension,
final int yDimension)
{
if (Utilities.equals(current, expected)) {
return gridToCRS;
}
if (gridToCRS == null) {
return null;
}
final int dimension = gridToCRS.getSourceDimensions();
if (xDimension < 0 || xDimension >= dimension) {
throw illegalDimension("xDimension", xDimension);
}
if (yDimension < 0 || yDimension >= dimension) {
throw illegalDimension("yDimension", yDimension);
}
if (xDimension == yDimension) {
throw illegalDimension("xDimension", "yDimension");
}
final PixelTranslation source = getPixelTranslation(current);
final PixelTranslation target = getPixelTranslation(expected);
final double dx = target.dx - source.dx;
final double dy = target.dy - source.dy;
MathTransform mt;
if (dimension == 2 && (xDimension | yDimension) == 1 && dx == dy && Math.abs(dx) == 0.5) {
final int index = (dx >= 0) ? 5 : 4;
synchronized (translations) {
mt = translations[index];
if (mt == null) {
mt = MathTransforms.linear(dimension, 1, dx);
translations[index] = mt;
}
}
} else {
final Matrix matrix = Matrices.create(dimension + 1);
matrix.setElement(xDimension, dimension, dx);
matrix.setElement(yDimension, dimension, dy);
mt = MathTransforms.linear(matrix);
}
return MathTransforms.concatenate(mt, gridToCRS);
}
/**
* Formats an exception for an illegal dimension.
*/
private static IllegalArgumentException illegalDimension(final String name, final Object dimension) {
return new IllegalArgumentException(Errors.format(Errors.Keys.ILLEGAL_ARGUMENT_$2, name, dimension));
}
/**
* Returns a string representation of this pixel translation.
*/
@Override
public String toString() {
return String.valueOf(orientation) + '[' + dx + ", " + dy + ']';
}
}