/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2012, 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.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
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.ReferencingFactoryFinder;
import org.geotools.referencing.factory.IdentifiedObjectSet;
import org.geotools.referencing.factory.gridshift.GridShiftLocator;
import org.geotools.referencing.factory.gridshift.NTv2GridShiftFactory;
import org.geotools.referencing.operation.MathTransformProvider;
import org.geotools.util.Utilities;
import org.geotools.util.logging.Logging;
import org.opengis.geometry.DirectPosition;
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.NoSuchIdentifierException;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.Transformation;
import au.com.objectix.jgridshift.GridShift;
import au.com.objectix.jgridshift.GridShiftFile;
/**
* The "<cite>NTv2</cite>" coordinate transformation method (EPSG:9615).
* <p>
* This transformation depends on an external resource (the NTv2 grid file). If the file
* is not available, a {@link NoSuchIdentifierException recoverable NoSuchIdentifierException}
* will be thrown on instantiation.
*
* @see {@link IdentifiedObjectSet IdentifiedObjectSet exception handling}.
* @source $URL$
* @version $Id$
* @author Oscar Fonts
*/
public class NTv2Transform extends AbstractMathTransform implements MathTransform2D, Serializable {
/** Serial number for interoperability with different versions. */
private static final long serialVersionUID = -3082112044314062512L;
/** Logger */
protected static final Logger LOGGER = Logging.getLogger("org.geotools.referencing");
/**
* The original grid name
*/
private URI grid = null;
/** The grid file name as set in the constructor. */
private URL gridLocation = null;
/**
* The grid shift to be used
*/
private GridShiftFile gridShift;
/**
* The factory that loads the grid shift files
*/
private static NTv2GridShiftFactory FACTORY = new NTv2GridShiftFactory();
/**
* The inverse of this transform. Will be created only when needed.
*/
private transient MathTransform2D inverse;
/**
* Constructs a {@code NTv2Transform} from the specified grid shift file.
*
* This constructor checks for grid shift file availability, but
* doesn't actually load the full grid into memory to preserve resources.
*
* @param file NTv2 grid file name
* @throws NoSuchIdentifierException if the grid is not available.
*/
public NTv2Transform(URI file) throws NoSuchIdentifierException {
if (file == null) {
throw new NoSuchIdentifierException("No NTv2 Grid File specified.", null);
}
this.grid = file;
gridLocation = locateGrid(grid.toString());
if(gridLocation == null) {
throw new NoSuchIdentifierException("Could not locate NTv2 Grid File " + file, null);
}
// Search for grid file
if (!FACTORY.isNTv2Grid(gridLocation)) {
throw new NoSuchIdentifierException("NTv2 Grid File not available.",
file.toString());
}
}
URL locateGrid(String grid) {
for (GridShiftLocator locator : ReferencingFactoryFinder.getGridShiftLocators(null)) {
URL result = locator.locateGrid(grid);
if(result != null) {
return result;
}
};
return null;
}
/**
* Returns a hash value for this transform.
*/
@Override
public int hashCode() {
return this.grid.hashCode();
}
/**
* Compares the specified object with this one for equality.
* Checks if {@code object} is {@code this} same instance, or a NTv2Transform
* with the same parameter values.
*
* @param object The object to compare with this transform.
* @return {@code true} if the given object is {@code this}, or
* a NTv2Transform with same parameter values, which would
* mean that given identical source position, the
* {@linkplain #transform(DirectPosition,DirectPosition) transformed}
* position would be the same.
*/
@Override
public boolean equals(final Object object) {
if(object==this) return true;
if (object!=null && getClass().equals(object.getClass())) {
final NTv2Transform that = (NTv2Transform) object;
return Utilities.equals(this.getParameterValues(),
that.getParameterValues());
}
return false;
}
/**
* Returns the inverse of this transform.
*
* @return the inverse of this transform
*/
@Override
public synchronized MathTransform2D inverse() {
if (inverse == null) {
inverse = new Inverse();
}
return inverse;
}
/**
* Transforms a list of coordinate point ordinal values. This method is
* provided for efficiently transforming many points. The supplied array
* of ordinal values will contain packed ordinal values. For example, if
* the source dimension is 3, then the ordinals will be packed in this
* order:
* (<var>x<sub>0</sub></var>,<var>y<sub>0</sub></var>,<var>z<sub>0</sub></var>,
*
* <var>x<sub>1</sub></var>,<var>y<sub>1</sub></var>,<var>z<sub>1</sub></var>
* ...).
*
* @param srcPts the array containing the source point coordinates.
* @param srcOff the offset to the first point to be transformed in the
* source array.
* @param dstPts the array into which the transformed point coordinates are
* returned. May be the same than {@code srcPts}.
* @param dstOff the offset to the location of the first transformed point
* that is stored in the destination array.
* @param numPts the number of point objects to be transformed.
*
* @throws TransformException if an IO error occurs reading the grid file.
*/
@Override
public void transform(double[] srcPts, int srcOff, double[] dstPts,
int dstOff, int numPts) throws TransformException {
bidirectionalTransform(srcPts,srcOff, dstPts, dstOff, numPts, true);
}
/**
* Inverse transform. See {@link #transform(double[], int, double[],
* int, int)}
*
* @param srcPts the array containing the source point coordinates.
* @param srcOff the offset to the first point to be transformed in the
* source array.
* @param dstPts the array into which the transformed point coordinates are
* returned. May be the same than {@code srcPts}.
* @param dstOff the offset to the location of the first transformed point
* that is stored in the destination array.
* @param numPts the number of point objects to be transformed.
*
* @throws TransformException if an IO error occurs reading the grid file.
*/
public void inverseTransform(double[] srcPts, int srcOff, double[] dstPts,
int dstOff, int numPts) throws TransformException {
bidirectionalTransform(srcPts,srcOff, dstPts, dstOff, numPts, false);
}
/**
* Performs the actual transformation.
*
* @param srcPts the array containing the source point coordinates.
* @param srcOff the offset to the first point to be transformed in the
* source array.
* @param dstPts the array into which the transformed point coordinates are
* returned. May be the same than {@code srcPts}.
* @param dstOff the offset to the location of the first transformed point
* that is stored in the destination array.
* @param numPts the number of point objects to be transformed.
* @param forward {@code true} for direct transform, {@code false} for inverse transform.
*
* @throws TransformException if an IO error occurs reading the grid file.
*/
private void bidirectionalTransform(double[] srcPts, int srcOff, double[] dstPts,
int dstOff, int numPts, boolean forward) throws TransformException {
boolean shifted;
if (gridShift == null) { // Create grid when first needed.
try {
gridShift = FACTORY.createNTv2Grid(gridLocation);
} catch (FactoryException e) {
throw new TransformException("NTv2 Grid " + gridLocation +
" Could not be created", e);
}
}
try {
GridShift shift = new GridShift();
while(--numPts >= 0) {
shift.setLonPositiveEastDegrees(srcPts[srcOff++]);
shift.setLatDegrees(srcPts[srcOff++]);
if (forward) {
shifted = gridShift.gridShiftForward(shift);
} else {
shifted = gridShift.gridShiftReverse(shift);
}
if (shifted) {
dstPts[dstOff++]=shift.getShiftedLonPositiveEastDegrees();
dstPts[dstOff++]=shift.getShiftedLatDegrees();
} else {
if(LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Point (" + srcPts[srcOff-2] + ", " + srcPts[srcOff-1] +
") is not covered by '" + this.grid + "' NTv2 grid," +
" it will not be shifted.");
}
dstPts[dstOff++]=srcPts[srcOff-2];
dstPts[dstOff++]=srcPts[srcOff-1];
}
}
} catch (IOException e) {
throw new TransformException(e.getLocalizedMessage(), e);
}
}
@Override
public int getSourceDimensions() {
return 2;
}
@Override
public int getTargetDimensions() {
return 2;
}
/**
* Returns the parameter values for this math transform.
*
* @return A copy of the parameter values for this math transform.
*/
@Override
public ParameterValueGroup getParameterValues() {
final ParameterValue<URI> file = new Parameter<URI>(Provider.FILE);
file.setValue(grid);
return new ParameterGroup(Provider.PARAMETERS,
new ParameterValue[] { file }
);
}
/**
* Inverse of a {@link NTv2Transform}.
*
* @version $Id$
* @author Oscar Fonts
*/
private final class Inverse extends AbstractMathTransform.Inverse
implements MathTransform2D, Serializable
{
/** Serial number for interoperability with different versions. */
private static final long serialVersionUID = -4707304160205218546L;
/**
* Default constructor.
*/
public Inverse() {
NTv2Transform.this.super();
}
/**
* Returns the parameter values for this math transform.
*
* @return A copy of the parameter values for this math transform.
*/
@Override
public ParameterValueGroup getParameterValues() {
return null;
}
/**
* Inverse transform an array of points.
*
* @param source
* @param srcOffset
* @param dest
* @param dstOffset
* @param length
*
* @throws TransformException if the input point is outside the area
* covered by this grid.
*/
public void transform(final double[] source, final int srcOffset,
final double[] dest, final int dstOffset, final int length)
throws TransformException {
NTv2Transform.this.inverseTransform(source, srcOffset, dest,
dstOffset, length);
}
/**
* Returns the original transform.
*/
@Override
public MathTransform2D inverse() {
return (MathTransform2D) super.inverse();
}
/**
* Restore reference to this object after deserialization.
*
* @param in DOCUMENT ME!
* @throws IOException DOCUMENT ME!
* @throws ClassNotFoundException DOCUMENT ME!
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
NTv2Transform.this.inverse = this;
}
}
/**
* The {@link NTv2Transform} provider.
*
* @author Oscar Fonts
*/
public static class Provider extends MathTransformProvider {
private static final long serialVersionUID = -3710592152744574801L;
/**
* The operation parameter descriptor for the "Latitude and longitude difference file"
* parameter value. The default value is "".
*/
public static final DefaultParameterDescriptor<URI> FILE = new DefaultParameterDescriptor<URI>(
toMap(new NamedIdentifier[] {
new NamedIdentifier(Citations.EPSG, "Latitude and longitude difference file"),
new NamedIdentifier(Citations.EPSG, "8656")
}),
URI.class, null, null, null, null, null, true);
/**
* The parameters group.
*/
static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] {
new NamedIdentifier(Citations.EPSG, "NTv2"),
new NamedIdentifier(Citations.EPSG, "9615")
}, new ParameterDescriptor[] {
FILE
});
/**
* Constructs a provider.
*/
public Provider() {
super(2, 2, PARAMETERS);
}
/**
* Returns the operation type.
*/
@Override
public Class<Transformation> getOperationType() {
return Transformation.class;
}
/**
* Creates a math 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.
* @throws FactoryException if there is a problem creating this
* math transform.
*/
protected MathTransform createMathTransform(final ParameterValueGroup values)
throws ParameterNotFoundException, FactoryException
{
return new NTv2Transform(value(FILE, values));
}
}
}