/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-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.transform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.Serializable; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; import javax.media.jai.JAI; import javax.media.jai.Warp; import javax.media.jai.WarpAffine; import javax.media.jai.WarpCubic; import javax.media.jai.WarpGeneralPolynomial; import javax.media.jai.WarpPolynomial; import javax.media.jai.WarpQuadratic; import org.geotools.metadata.iso.citation.Citations; import org.geotools.parameter.DefaultParameterDescriptor; import org.geotools.parameter.Parameter; import org.geotools.parameter.ParameterGroup; import org.geotools.referencing.NamedIdentifier; import org.geotools.referencing.operation.MathTransformProvider; import org.geotools.resources.XArray; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; import org.geotools.util.Utilities; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.parameter.ParameterNotFoundException; import org.opengis.parameter.ParameterValue; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransform2D; import org.opengis.referencing.operation.NoninvertibleTransformException; import org.opengis.referencing.operation.Transformation; /** * Wraps an arbitrary {@link Warp} object as a {@linkplain MathTransform2D two-dimensional transform}. * Calls to {@linkplain #transform(float[],int,float[],int,int) transform} methods are forwarded to * the {@link Warp#warpPoint(int,int,float[]) warpPoint} method, or something equivalent. This * implies that source coordinates may be rounded to nearest integers before the transformation * is applied. *
* This transform is typically used with {@linkplain org.geotools.coverage.processing.operation.Resample * grid coverage "Resample" operation} for reprojecting an image. Source and destination coordinates * are usually pixel coordinates in source and target image, which is why this transform may use * integer arithmetic. *
* This math transform can be created alone (by invoking its public constructors directly), or it * can be created by a factory like {@link LocalizationGrid}. *
* For more information on image warp, see * Geometric * Image Manipulation in the Programming in Java Advanced Imaging guide. * * @since 2.1 * * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux * @author Alessio Fabiani * * @see LocalizationGrid#getPolynomialTransform(int) * @see Warp * @see javax.media.jai.WarpOpImage * @see javax.media.jai.operator.WarpDescriptor */ public class WarpTransform2D extends AbstractMathTransform implements MathTransform2D, Serializable { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -7949539694656719923L; private final static boolean USE_HACK; static{ final String buildVersion=JAI.getBuildVersion(); final SimpleDateFormat df= new SimpleDateFormat("yyyy-MM-dd' 'hh:mm:ss.SSSZ"); final TimeZone tz= TimeZone.getTimeZone("UTC"); df.setTimeZone(tz); boolean hack=false; try { final Date date_ = buildVersion!=null?df.parse(buildVersion):new java.util.Date(); //reduce granularity to minute final GregorianCalendar tempCal = new GregorianCalendar(tz); tempCal.setTime(date_); tempCal.set(Calendar.HOUR_OF_DAY, 0); tempCal.set(Calendar.MINUTE, 0); tempCal.set(Calendar.SECOND, 0); tempCal.set(Calendar.MILLISECOND, 0); final Date date=tempCal.getTime(); // 113 version final GregorianCalendar version113= new GregorianCalendar(tz); version113.set(2006, 8, 12,0,0,0); version113.set(Calendar.MILLISECOND, 0); // we need the hacke only if we are using 1.1.3 hack=!date.after(version113.getTime()); } catch (ParseException e) { hack=false; } USE_HACK=hack; } /** * The maximal polynomial degree allowed. * * @since 2.4 */ public static final int MAX_DEGREE = 7; /** * The warp object. Transformations will be applied using the * {@link Warp#warpPoint(int,int,float[]) warpPoint} method or something equivalent. */ private final Warp warp; /** * The inverse math transform. */ private final WarpTransform2D inverse; /** * Constructs a warp transform that approximatively maps the given source coordinates to the * given destination coordinates. The transformation is performed using some polynomial warp * with the degree supplied in argument. The number of points required for each degree of warp * are as follows: *
*
Degree of Warp | Number of Points |
---|---|
1 | 3 |
2 | 6 |
3 | 10 |
4 | 15 |
5 | 21 |
6 | 28 |
7 | 36 |
{@linkplain #inverse}.getWarp()
if the warp object is going to be used in an
* image reprojection.
*/
public Warp getWarp() {
return warp;
}
/**
* Returns the parameter descriptors for this math transform.
*/
@Override
public ParameterDescriptorGroup getParameterDescriptors() {
if (warp instanceof WarpPolynomial) {
return Provider.PARAMETERS;
} else {
return super.getParameterDescriptors();
}
}
/**
* Returns the parameter values for this math transform.
*/
@Override
public ParameterValueGroup getParameterValues() {
if (warp instanceof WarpPolynomial) {
final WarpPolynomial poly = (WarpPolynomial) warp;
final ParameterValue[] p = new ParameterValue[7];
int c = 0;
p[c++] = new Parameter(Provider.DEGREE, Integer.valueOf(poly.getDegree()));
p[c++] = new Parameter(Provider.X_COEFFS, poly.getXCoeffs());
p[c++] = new Parameter(Provider.Y_COEFFS, poly.getYCoeffs());
float s;
if ((s=poly.getPreScaleX ()) != 1) p[c++] = new Parameter(Provider. PRE_SCALE_X, s);
if ((s=poly.getPreScaleY ()) != 1) p[c++] = new Parameter(Provider. PRE_SCALE_Y, s);
if ((s=poly.getPostScaleX()) != 1) p[c++] = new Parameter(Provider.POST_SCALE_X, s);
if ((s=poly.getPostScaleY()) != 1) p[c++] = new Parameter(Provider.POST_SCALE_Y, s);
return new ParameterGroup(getParameterDescriptors(), XArray.resize(p, c));
} else {
return super.getParameterValues();
}
}
/**
* Returns the dimension of input points.
*/
public int getSourceDimensions() {
return 2;
}
/**
* Returns the dimension of output points.
*/
public int getTargetDimensions() {
return 2;
}
/**
* Tests if this transform is the identity transform.
*/
@Override
public boolean isIdentity() {
return false;
}
/**
* Transforms source coordinates (usually pixel indices) into destination coordinates
* (usually "real world" coordinates).
*
* @param ptSrc the specified coordinate point to be transformed.
* @param ptDst the specified coordinate point that stores the result of transforming
* {@code ptSrc}, or {@code null}.
* @return the coordinate point after transforming {@code ptSrc} and storing the result in
* {@code ptDst}.
*/
@Override
public Point2D transform(Point2D ptSrc, Point2D ptDst) {
/*
* We have to copy the coordinate in a temporary point object because we don't know
* neither the ptSrc or ptDst type. Since mapDestPoint returns a clone of the point
* given in argument, giving ptSrc directly would result in a lost of precision if
* its type is java.awt.Point (for example), even if ptDst had a greater precision.
*
* There is also an other reason for creating a temporary object:
* JAI's Warp is designed for mapping pixel coordinates in J2SE's image. In JAI, pixel
* coordinates map by definition to the pixel's upper left corner. But for interpolation
* purpose, JAI needs to map pixel's center. This introduce a shift of 0.5, which is
* documented (for example) in WarpAffine.mapDestPoint(Point2D).
*/
ptSrc = new PointDouble(ptSrc.getX() - 0.5, ptSrc.getY() - 0.5);
final Point2D result = warp.mapDestPoint(ptSrc);
result.setLocation(result.getX() + 0.5, result.getY() + 0.5);
if (ptDst == null) {
// Do not returns 'result' directly, since it has tricked 'clone()' method.
ptDst = new Point2D.Float();
}
ptDst.setLocation(result);
return ptDst;
}
/**
* Transforms source coordinates (usually pixel indices) into destination coordinates
* (usually "real world" coordinates).
*/
@Override
public void transform(final float[] srcPts, int srcOff,
final float[] dstPts, int dstOff, int numPts)
{
final int postIncrement;
if (srcPts == dstPts && srcOff < dstOff) {
srcOff += (numPts-1)*2;
dstOff += (numPts-1)*2;
postIncrement = -4;
} else {
postIncrement = 0;
}
final Point2D.Float ptSrc = new PointFloat();
final float[] ptDst = new float[2];
while (--numPts >= 0) {
ptSrc.x = srcPts[srcOff++] - 0.5f; // See the comment in transform(Point2D...)
ptSrc.y = srcPts[srcOff++] - 0.5f; // for an explanation about the 0.5 shift.
final Point2D result = warp.mapDestPoint(ptSrc);
dstPts[dstOff++] = (float) (result.getX() + 0.5);
dstPts[dstOff++] = (float) (result.getY() + 0.5);
dstOff += postIncrement;
}
}
/**
* Transforms source coordinates (usually pixel indices) into destination coordinates
* (usually "real world" coordinates).
*/
public void transform(final double[] srcPts, int srcOff,
final double[] dstPts, int dstOff, int numPts)
{
final int postIncrement;
if (srcPts == dstPts && srcOff < dstOff) {
srcOff += (numPts-1)*2;
dstOff += (numPts-1)*2;
postIncrement = -4;
} else {
postIncrement = 0;
}
final Point2D.Double ptSrc = new PointDouble();
final float[] ptDst = new float[2];
while (--numPts >= 0) {
ptSrc.x = srcPts[srcOff++] - 0.5; // See the comment in transform(Point2D...)
ptSrc.y = srcPts[srcOff++] - 0.5; // for an explanation about the 0.5 shift.
final Point2D result = warp.mapDestPoint(ptSrc);
dstPts[dstOff++] = result.getX() + 0.5;
dstPts[dstOff++] = result.getY() + 0.5;
dstOff += postIncrement;
}
}
/**
* Returns the inverse transform.
*
* @throws NoninvertibleTransformException if no inverse warp were specified at construction time.
*/
@Override
public MathTransform2D inverse() throws NoninvertibleTransformException {
if (inverse != null) {
return inverse;
} else {
return (MathTransform2D) super.inverse();
}
}
/**
* Returns a hash value for this transform.
*/
@Override
public int hashCode() {
return (int)serialVersionUID ^ super.hashCode() ^ warp.hashCode();
}
/**
* Compares this transform with the specified object for equality.
*/
@Override
public boolean equals(final Object object) {
if (super.equals(object)) {
final WarpTransform2D that = (WarpTransform2D) object;
return Utilities.equals(this.warp, that.warp);
}
return false;
}
/**
* A {@code Point2D.Float} that returns itself when {@link #clone} is invoked.
* This trick is used for avoiding the creation of thousands of temporary objects
* when transforming an array of points using {@link Warp#mapDestPoint}.
*/
private static final class PointFloat extends Point2D.Float {
@Override
public PointFloat clone() {
return this;
}
}
/**
* A {@code Point2D.Double} that returns itself when {@link #clone} is invoked.
* This trick is used for avoiding the creation of thousands of temporary objects
* when transforming an array of points using {@link Warp#mapDestPoint}.
*/
@SuppressWarnings("serial")
private static final class PointDouble extends Point2D.Double {
public PointDouble() {
super();
}
public PointDouble(double x, double y) {
super(x,y);
}
@Override
public PointDouble clone() {
return this;
}
}
/**
* The provider for the {@link WarpTransform2D}. This provider constructs a JAI
* {@linkplain WarpPolynomial image warp} from a set of polynomial coefficients,
* and wrap it in a {@link WarpTransform2D} object.
*
* @version $Id$
* @author Martin Desruisseaux
*/
public static class Provider extends MathTransformProvider {
/** Serial number for interoperability with different versions. */
private static final long serialVersionUID = -7949539694656719923L;
/** Descriptor for the "{@link WarpPolynomial#getDegree degree}" parameter value. */
public static final ParameterDescriptor