/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-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.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.StringTokenizer;
import java.util.prefs.Preferences;
import javax.media.jai.Warp;
import javax.media.jai.WarpGrid;
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.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.opengis.geometry.DirectPosition;
import org.opengis.parameter.InvalidParameterTypeException;
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.FactoryException;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.Transformation;
/**
* Basic implementation of JAI's WarpGrid Transformation. This class encapsulates WarpGrid into the
* GeoTools transformations conventions.
* @author jezekjan
*
*/
public class WarpGridTransform2D extends WarpTransform2D {
/** Inverse Math Transform */
private MathTransform2D inverse;
/** World to grid math transform */
private MathTransform worldToGrid;
/** Warp object */
private final Warp warp;
/** warp position (Warp object desn't offer getWarpPosition in the same format as needed)*/
private final float[] warpPositions;
private float[] inversePos;
/**
* Constructs WarpGridTransform2D by settings values defining grid values.
* See http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/javax/media/jai/WarpGrid.html
* for more.
*
* @param xStart Start of the grid in X direction
* @param xStep Length of the cell in X direction
* @param xNumCells Number of cells in X direction
* @param yStart Start of the grid in Y direction
* @param yStep Length of the sell in Y direction
* @param yNumCells Number of cells in Y direction
* @param warpPositions Array of warp position where the dimension must be equal to (xNumCells+1) * (yNumCells+1)
*
* @throws IllegalArgumentException if the lenght of warpPosition is incorrect.
*/
public WarpGridTransform2D(int xStart, int xStep, int xNumCells, int yStart, int yStep,
int yNumCells, float[] warpPositions) throws IllegalArgumentException{
super(new WarpGrid(xStart, xStep, xNumCells, yStart, yStep, yNumCells, warpPositions), null);
this.warp = super.getWarp();
this.warpPositions = warpPositions;
}
/**
* Returns a URL from the string representation. If the string has no
* path, the default path preference is added.
*
* @param str a string representation of a URL
* @return a URL created from the string representation
* @throws MalformedURLException if the URL cannot be created
*/
private static URL makeURL(final String str) throws MalformedURLException {
//has '/' or '\' or ':', so probably full path to file
if ((str.indexOf('\\') >= 0) || (str.indexOf('/') >= 0) || (str.indexOf(':') >= 0)) {
return makeURLfromString(str);
} else {
// just a file name , prepend base location
final Preferences prefs = Preferences.userNodeForPackage(WarpGridTransform2D.class);
final String baseLocation = prefs.get("GRID_LOCATION", "");
return makeURLfromString(baseLocation + "/" + str);
}
}
/**
* Returns a URL based on a string representation. If no protocol is given,
* it is assumed to be a local file.
*
* @param str a string representation of a URL
* @return a URL created from the string representation
* @throws MalformedURLException if the URL cannot be created
*/
private static URL makeURLfromString(final String str)
throws MalformedURLException {
try {
return new URL(str);
} catch (MalformedURLException e) {
//try making this with a file protocal
return new URL("file", "", str);
}
}
public ParameterDescriptorGroup getParameterDescriptors() {
return Provider.PARAMETERS;
}
/**
* Returns the parameter values for this math transform.
*/
public ParameterValueGroup getParameterValues() {
if (this.warp instanceof WarpGrid) {
// final WarpGrid wGrid = (WarpGrid) warp;
final ParameterValue[] p = new ParameterValue[7];
int c = 0;
p[c++] = new Parameter(Provider.X_START, ((WarpGrid) getWarp()).getXStart()); //new Integer(((WarpGrid)super.getWarp()).getXStart()));
p[c++] = new Parameter(Provider.X_STEP, ((WarpGrid) getWarp()).getXStep());
p[c++] = new Parameter(Provider.X_NUMCELLS, ((WarpGrid) getWarp()).getXNumCells());
p[c++] = new Parameter(Provider.Y_START, ((WarpGrid) getWarp()).getYStart());
p[c++] = new Parameter(Provider.Y_STEP, ((WarpGrid) getWarp()).getYStep());
p[c++] = new Parameter(Provider.Y_NUMCELLS, ((WarpGrid) getWarp()).getYNumCells());
p[c++] = new Parameter(Provider.WARP_POSITIONS, (Object) this.warpPositions.clone());
return new ParameterGroup(getParameterDescriptors(),
(ParameterValue[]) XArray.resize(p, c));
} else {
return super.getParameterValues();
}
}
public void setWorldtoGridTransform(MathTransform worldToGrid) {
this.worldToGrid = worldToGrid;
}
public MathTransform getWorldtoGridTransform() {
return worldToGrid;
}
public void transform(final double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts)
{
// TODO Auto-generated method stub
//transformToGrid(srcPts, srcOff, srcPts, srcOff, numPts, false);
try {
double[] helperPts = new double[srcPts.length];
if (worldToGrid != null) {
worldToGrid.transform(srcPts, srcOff, helperPts, srcOff, numPts);
}
super.transform(helperPts, srcOff, dstPts, dstOff, numPts);
if (worldToGrid != null) {
worldToGrid.inverse().transform(dstPts, dstOff, dstPts, dstOff, numPts);
}
} catch (NoninvertibleTransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void transform(float[] srcPts, int srcOff, float[] dstPts, int dstOff, int numPts) {
try {
if (worldToGrid != null) {
worldToGrid.transform(srcPts, srcOff, srcPts, srcOff, numPts);
}
} catch (TransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
super.transform(srcPts, srcOff, dstPts, dstOff, numPts);
try {
if (worldToGrid != null) {
worldToGrid.inverse().transform(dstPts, dstOff, dstPts, dstOff, numPts);
}
} catch (NoninvertibleTransformException e) {
e.printStackTrace();
} catch (TransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Point2D transform(Point2D ptSrc, Point2D ptDst) {
try {
if (worldToGrid != null) {
worldToGrid.transform((DirectPosition) ptSrc, (DirectPosition) ptSrc);
}
} catch (TransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ptDst = super.transform(ptSrc, ptDst);
try {
if (worldToGrid != null) {
worldToGrid.inverse().transform((DirectPosition) ptDst, (DirectPosition) ptDst);
}
} catch (NoninvertibleTransformException e) {
e.printStackTrace();
} catch (TransformException e) {
e.printStackTrace();
}
return ptDst;
}
/**
* TODO - make static as soon as we get rid of worldToGrid transform
* Calculation inverse values. Calculation is not exact, but should provide good results
* when shifts are smaller than grid cells.
* @param xStart
* @param xStep
* @param xNumCells
* @param yStart
* @param yStep
* @param yNumCells
* @param warpPositions
* @return
*/
private WarpGridTransform2D calculateInverse(int xStart, int xStep, int xNumCells, int yStart,
int yStep, int yNumCells, float[] warpPositions) {
if ( inversePos == null){
inversePos = new float[warpPositions.length];
for (int i = 0; i <= yNumCells; i++) {
for (int j = 0; j <= xNumCells; j++) {
inversePos[(i * ((1 + xNumCells) * 2)) + (2 * j)] = (2 * ((j * xStep) + xStart))
- warpPositions[(i * ((1 + xNumCells) * 2)) + (2 * j)];
inversePos[(i * ((1 + xNumCells) * 2)) + (2 * j) + 1] = (2 * ((i * yStep) + yStart))
- warpPositions[(i * ((1 + xNumCells) * 2)) + (2 * j) + 1];
}
}
}
WarpGridTransform2D wgt = new WarpGridTransform2D(xStart, xStep, xNumCells, yStart, yStep,
yNumCells, inversePos);
wgt.setWorldtoGridTransform(this.worldToGrid);
return wgt;
}
public MathTransform2D inverse() throws NoninvertibleTransformException {
// TODO Auto-generated method stub
// if (inverse == null) {
inverse = calculateInverse(((WarpGrid) getWarp()).getXStart(),
((WarpGrid) getWarp()).getXStep(), ((WarpGrid) getWarp()).getXNumCells(),
((WarpGrid) getWarp()).getYStart(), ((WarpGrid) getWarp()).getYStep(),
((WarpGrid) getWarp()).getYNumCells(), warpPositions);
// }
return inverse;
}
/**
*
* The provider for the {@linkplain WarpGridTransform2D}. This provider constructs a JAI
* {@linkplain WarpGrid image warp} from a set of mapped positions,
* and wrap it in a {@linkplain WarpGridTransform2D} object.
*
* @author jezekjan
*
*/
public static class Provider extends MathTransformProvider {
/** Serial number for interoperability with different versions. */
private static final long serialVersionUID = -1126785723468L;
/** Descriptor for the "{@link WarpGrid#getXStart xStart}" parameter value. */
public static final ParameterDescriptor X_START = new DefaultParameterDescriptor("xStart",
Integer.class, null, null);
/** Descriptor for the "{@link WarpGrid#getXStep xStep}" parameter value. */
public static final ParameterDescriptor X_STEP = new DefaultParameterDescriptor("xStep",
Integer.class, null, null);
/** Descriptor for the "{@link WarpGrid#getXNumCells xNumCells}" parameter value. */
public static final ParameterDescriptor X_NUMCELLS = new DefaultParameterDescriptor("xNumCells",
Integer.class, null, null);
/** Descriptor for the "{@link WarpGrid#getYStart yStart}" parameter value. */
public static final ParameterDescriptor Y_START = new DefaultParameterDescriptor("yStart",
Integer.class, null, null);
/** Descriptor for the "{@link WarpGrid#getYStep yStep}" parameter value. */
public static final ParameterDescriptor Y_STEP = new DefaultParameterDescriptor("yStep",
Integer.class, null, null);
/** Descriptor for the "{@link WarpGrid#getYNumCells yNumCells}" parameter value. */
public static final ParameterDescriptor Y_NUMCELLS = new DefaultParameterDescriptor("yNumCells",
Integer.class, null, null);
/** Descriptor for the warpPositions parameter value. This the target coordinates of weach cell (not deltas) */
public static final ParameterDescriptor<float[]> WARP_POSITIONS = new DefaultParameterDescriptor("warpPositions",
float[].class, null, null);
/**
* The parameters group.
*/
private static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] {
new NamedIdentifier(Citations.GEOTOOLS, "Warp Grid")
},
new ParameterDescriptor[] {
X_START, X_STEP, X_NUMCELLS, Y_START, Y_STEP, Y_NUMCELLS, WARP_POSITIONS
});
public ParameterDescriptorGroup getParameters(){
return PARAMETERS;
}
/**
* Create a provider for warp transforms.
*/
public Provider() {
super(2, 2, PARAMETERS);
}
/**
* Returns the operation type.
*/
public Class getOperationType() {
return Transformation.class;
}
/**
* Creates a warp transform from the specified group of parameter values.
*
* @param values The group of parameter values.
* @return The created math transform.
* @throws ParameterNotFoundException if a required parameter was not found.
*/
public MathTransform createMathTransform(final ParameterValueGroup values)
throws ParameterNotFoundException {
final int xStart = intValue(X_START, values);
final int xStep = intValue(X_STEP, values);
final int xNumCells = intValue(X_NUMCELLS, values);
final int yStart = intValue(Y_START, values);
final int yStep = intValue(Y_STEP, values);
final int yNumCells = intValue(Y_NUMCELLS, values);
final float[] warpPos = (float[]) value(WARP_POSITIONS, values);
WarpGridTransform2D wgt = new WarpGridTransform2D(xStart, xStep, xNumCells, yStart,
yStep, yNumCells, warpPos);
return wgt;
}
}
/**
* The provider for {@link NADCONTransform}. This provider will construct
* transforms from {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS
* geographic} to {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS
* geographic} coordinate reference systems.
*
*/
public static class ProviderFile extends MathTransformProvider {
/** Serial number for interoperability with different versions. */
private static final long serialVersionUID = -42356975310348L;
/**
* The operation parameter descriptor for the "Latitude_difference_file"
* parameter value. The default value is "conus.las".
*/
public static final ParameterDescriptor X_DIFF_FILE = new DefaultParameterDescriptor("X_difference_file",
String.class, null, "");
/**
* The operation parameter descriptor for the "Longitude_difference_file"
* parameter value. The default value is "conus.los".
*/
public static final ParameterDescriptor Y_DIFF_FILE = new DefaultParameterDescriptor("Y_difference_file",
String.class, null, "");
/**
* The parameters group.
*/
static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] {
new NamedIdentifier(Citations.EPSG, "9613"),
new NamedIdentifier(Citations.GEOTOOLS, "Warp Grid (from file)")
}, new ParameterDescriptor[] { X_DIFF_FILE, Y_DIFF_FILE });
/**
* Constructs a provider.
*/
public ProviderFile() {
super(2, 2, PARAMETERS);
}
/**
* Returns the operation type.
*/
public Class getOperationType() {
return Transformation.class;
}
public MathTransform createMathTransform(final ParameterValueGroup values)
throws ParameterNotFoundException, InvalidParameterTypeException, FactoryException {
try {
return createWarpGrid(values.parameter("X_difference_file").stringValue(),
values.parameter("Y_difference_file").stringValue());
} catch (MalformedURLException e) {
throw new FactoryException(Errors.format(ErrorKeys.UNSUPPORTED_FILE_TYPE_$2), e);
} catch (IOException e) {
throw new FactoryException(Errors.format(ErrorKeys.FILE_DOES_NOT_EXIST_$1), e);
}
}
/**
*
* @param latGridName
* @param longGridName
* @return
*/
private static WarpGridTransform2D createWarpGrid(final String xGridName,
final String yGridName) throws MalformedURLException, IOException, FactoryException {
final URL xGridURL = makeURL(xGridName);
final URL yGridURL = makeURL(yGridName);
return loadTextGrid(xGridURL, yGridURL);
}
/**
*
* @param latGridUrl
* @param longGridUrl
* @throws IOException
* @throws FactoryException
*/
private static WarpGridTransform2D loadTextGrid(URL xGridUrl, URL longGridUrl)
throws IOException, FactoryException {
String xLine;
String longLine;
StringTokenizer xSt;
StringTokenizer longSt;
////////////////////////
//setup
////////////////////////
InputStreamReader xIsr = new InputStreamReader(xGridUrl.openStream());
BufferedReader xBr = new BufferedReader(xIsr);
InputStreamReader longIsr = new InputStreamReader(longGridUrl.openStream());
BufferedReader longBr = new BufferedReader(longIsr);
////////////////////////
//read header info
////////////////////////
xLine = xBr.readLine(); //skip header description
xLine = xBr.readLine();
xSt = new StringTokenizer(xLine, " ");
if (xSt.countTokens() > 8) {
throw new FactoryException(Errors.format(ErrorKeys.HEADER_UNEXPECTED_LENGTH_$1,
String.valueOf(xSt.countTokens())));
}
int nc = Integer.parseInt(xSt.nextToken());
int nr = Integer.parseInt(xSt.nextToken());
int nz = Integer.parseInt(xSt.nextToken());
float xStart = Float.parseFloat(xSt.nextToken());
float xStep = Float.parseFloat(xSt.nextToken());
float yStart = Float.parseFloat(xSt.nextToken());
float yStep = Float.parseFloat(xSt.nextToken());
// float angle = Float.parseFloat(latSt.nextToken());
float xmax = xStart + ((nc - 1) * xStart);
float ymax = yStart + ((nr - 1) * yStep);
//now read long shift grid
longLine = longBr.readLine(); //skip header description
longLine = longBr.readLine();
longSt = new StringTokenizer(longLine, " ");
if (longSt.countTokens() > 8) {
throw new FactoryException(Errors.format(ErrorKeys.HEADER_UNEXPECTED_LENGTH_$1,
String.valueOf(longSt.countTokens())));
}
//check that latitude grid header is the same as for latitude grid
if ((nc != Integer.parseInt(longSt.nextToken()))
|| (nr != Integer.parseInt(longSt.nextToken()))
|| (nz != Integer.parseInt(longSt.nextToken()))
|| (xStart != Float.parseFloat(longSt.nextToken()))
|| (xStep != Float.parseFloat(longSt.nextToken()))
|| (yStart != Float.parseFloat(longSt.nextToken()))
|| (yStep != Float.parseFloat(longSt.nextToken()))) {
// || (angle != Float.parseFloat(longSt.nextToken()))) {
throw new FactoryException(Errors.format(ErrorKeys.GRID_LOCATIONS_UNEQUAL));
}
////////////////////////
//read grid shift data into LocalizationGrid
////////////////////////
int i = 0;
int j = 0;
float[] warpPos = new float[2 * (nc) * (nr)];
for (i = 0; i < nr; i++) {
for (j = 0; j < nc;) {
xLine = xBr.readLine();
xSt = new StringTokenizer(xLine, " ");
longLine = longBr.readLine();
longSt = new StringTokenizer(longLine, " ");
while (xSt.hasMoreTokens() && longSt.hasMoreTokens()) {
warpPos[(2 * j) + (nc * i * 2)] = xStart + (j * xStep)
+ (float) -Float.parseFloat(xSt.nextToken());
warpPos[(2 * j) + (nc * i * 2) + 1] = yStart + (i * yStep)
+ (float) Float.parseFloat(longSt.nextToken());
++j;
}
}
}
return new WarpGridTransform2D((int) xStart, (int) xStep, nc - 1, (int) yStart,
(int) yStep, nr - 1, warpPos);
}
}
}