/*
* 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.
*
* This package contains documentation from OpenGIS specifications.
* OpenGIS consortium's work is fully acknowledged here.
*/
package org.geotools.referencing.operation.transform;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Collections;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import javax.measure.quantity.Length;
import javax.measure.converter.UnitConverter;
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.datum.Ellipsoid;
import org.opengis.referencing.operation.Conversion;
import org.opengis.referencing.operation.MathTransform;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.parameter.DefaultParameterDescriptor;
import org.geotools.parameter.FloatParameter;
import org.geotools.referencing.NamedIdentifier;
import org.geotools.referencing.operation.MathTransformProvider;
import org.geotools.resources.i18n.VocabularyKeys;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
/**
* Transforms three dimensional {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS
* geographic} points to {@linkplain org.geotools.referencing.crs.DefaultGeocentricCRS geocentric}
* coordinate points. Input points must be longitudes, latitudes and heights above the ellipsoid.
*
* @since 2.0
* @source $URL$
* @version $Id$
* @author Frank Warmerdam
* @author Martin Desruisseaux (IRD)
*/
public class GeocentricTransform extends AbstractMathTransform implements Serializable {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -3352045463953828140L;
/**
* Maximal error tolerance in metres during assertions, in metres. If assertions
* are enabled (JDK 1.4 only), then every coordinates transformed with
* {@link #inverseTransform} will be transformed again with {@link #mathTransform}.
* If the distance between the resulting position and the original position
* is greater than {@code MAX_ERROR}, then a {@link AssertionError} is thrown.
*/
private static final double MAX_ERROR = 0.01;
/**
* Cosine of 67.5 decimal degrees.
*/
private static final double COS_67P5 = 0.38268343236508977;
/**
* Toms region 1 constant.
*/
private static final double AD_C = 1.0026000;
/**
* Semi-major axis of ellipsoid in meters.
*/
private final double a;
/**
* Semi-minor axis of ellipsoid in meters.
*/
private final double b;
/**
* Square of semi-major axis (<var>a</var>²).
*/
private final double a2;
/**
* Square of semi-minor axis (<var>b</var>²).
*/
private final double b2;
/**
* Eccentricity squared.
*/
private final double e2;
/**
* 2nd eccentricity squared.
*/
private final double ep2;
/**
* {@code true} if geographic coordinates include an ellipsoidal
* height (i.e. are 3-D), or {@code false} if they are strictly 2-D.
*/
private final boolean hasHeight;
/**
* The inverse of this transform. Will be created only when needed.
*/
private transient MathTransform inverse;
/**
* Constructs a transform from the specified ellipsoid.
*
* @param ellipsoid The ellipsoid.
* @param hasHeight {@code true} if geographic coordinates
* include an ellipsoidal height (i.e. are 3-D),
* or {@code false} if they are only 2-D.
*/
public GeocentricTransform(final Ellipsoid ellipsoid, final boolean hasHeight) {
this(ellipsoid.getSemiMajorAxis(),
ellipsoid.getSemiMinorAxis(),
ellipsoid.getAxisUnit(), hasHeight);
}
/**
* Constructs a transform from the specified parameters.
*
* @param semiMajor The semi-major axis length.
* @param semiMinor The semi-minor axis length.
* @param units The axis units.
* @param hasHeight {@code true} if geographic coordinates
* include an ellipsoidal height (i.e. are 3-D),
* or {@code false} if they are only 2-D.
*/
public GeocentricTransform(final double semiMajor,
final double semiMinor,
final Unit<Length> units,
final boolean hasHeight)
{
this.hasHeight = hasHeight;
final UnitConverter converter = units.getConverterTo(SI.METER);
a = converter.convert(semiMajor);
b = converter.convert(semiMinor);
a2 = a*a;
b2 = b*b;
e2 = (a2 - b2) / a2;
ep2 = (a2 - b2) / b2;
checkArgument("a", a, Double.MAX_VALUE);
checkArgument("b", b, a);
}
/**
* Checks an argument value. The argument must be greater
* than 0 and finite, otherwise an exception is thrown.
*
* @param name The argument name.
* @param value The argument value.
* @param max The maximal legal argument value.
*/
private static void checkArgument(final String name,
final double value,
final double max) throws IllegalArgumentException
{
if (!(value>=0 && value<=max)) {
// Use '!' in order to trap NaN
throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, name, value));
}
}
/**
* Returns the parameter descriptors for this math transform.
*/
@Override
public ParameterDescriptorGroup getParameterDescriptors() {
return Provider.PARAMETERS;
}
/**
* Returns the parameter values for this math transform.
*
* @return A copy of the parameter values for this math transform.
*/
@Override
public ParameterValueGroup getParameterValues() {
return getParameterValues(getParameterDescriptors());
}
/**
* Returns the parameter values using the specified descriptor.
*
* @param descriptor The parameter descriptor.
* @return A copy of the parameter values for this math transform.
*/
private ParameterValueGroup getParameterValues(final ParameterDescriptorGroup descriptor) {
final ParameterValue[] parameters = new ParameterValue[hasHeight ? 2 : 3];
int index = 0;
if (!hasHeight) {
final ParameterValue p = new org.geotools.parameter.Parameter(Provider.DIM);
p.setValue(2);
parameters[index++] = p;
}
parameters[index++] = new FloatParameter(Provider.SEMI_MAJOR, a);
parameters[index++] = new FloatParameter(Provider.SEMI_MINOR, b);
return new org.geotools.parameter.ParameterGroup(descriptor, parameters);
}
/**
* Gets the dimension of input points, which is 2 or 3.
*/
public int getSourceDimensions() {
return hasHeight ? 3 : 2;
}
/**
* Gets the dimension of output points, which is 3.
*/
public final int getTargetDimensions() {
return 3;
}
/**
* Converts geodetic coordinates (longitude, latitude, height) to geocentric
* coordinates (x, y, z) according to the current ellipsoid parameters.
*/
public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) {
transform(srcPts, srcOff, dstPts, dstOff, numPts, false);
}
/**
* Implementation of geodetic to geocentric conversion. This implementation allows the caller
* to use height in computation. This is used for assertion with {@link #checkTransform}.
*/
private void transform(double[] srcPts, int srcOff,
final double[] dstPts, int dstOff,
int numPts, boolean hasHeight)
{
final int dimSource = getSourceDimensions();
hasHeight |= (dimSource>=3);
if (srcPts==dstPts && needCopy(srcOff, dimSource, dstOff, 3, numPts)) {
// Source and destination arrays overlaps: copy in a temporary buffer.
final double[] old = srcPts;
srcPts = new double[numPts * (hasHeight ? 3 : 2)];
System.arraycopy(old, srcOff, srcPts, 0, srcPts.length);
srcOff = 0;
}
while (--numPts >= 0) {
final double L = Math.toRadians(srcPts[srcOff++]); // Longitude
final double P = Math.toRadians(srcPts[srcOff++]); // Latitude
final double h = hasHeight ? srcPts[srcOff++] : 0; // Height above the ellipsoid (m)
final double cosLat = Math.cos(P);
final double sinLat = Math.sin(P);
final double rn = a / Math.sqrt(1 - e2 * (sinLat*sinLat));
dstPts[dstOff++] = (rn + h) * cosLat * Math.cos(L); // X: Toward prime meridian
dstPts[dstOff++] = (rn + h) * cosLat * Math.sin(L); // Y: Toward East
dstPts[dstOff++] = (rn * (1-e2) + h) * sinLat; // Z: Toward North
}
}
/**
* Converts geodetic coordinates (longitude, latitude, height) to geocentric
* coordinates (x, y, z) according to the current ellipsoid parameters.
*/
@Override
public void transform(float[] srcPts, int srcOff,
final float[] dstPts, int dstOff, int numPts)
{
final int dimSource = getSourceDimensions();
final boolean hasHeight = (dimSource>=3);
if (srcPts==dstPts && needCopy(srcOff, dimSource, dstOff, 3, numPts)) {
// Source and destination arrays overlaps: copy in a temporary buffer.
final float[] old = srcPts;
srcPts = new float[numPts*dimSource];
System.arraycopy(old, srcOff, srcPts, 0, srcPts.length);
srcOff = 0;
}
while (--numPts >= 0) {
final double L = Math.toRadians(srcPts[srcOff++]); // Longitude
final double P = Math.toRadians(srcPts[srcOff++]); // Latitude
final double h = hasHeight ? srcPts[srcOff++] : 0; // Height above the ellipsoid (m)
final double cosLat = Math.cos(P);
final double sinLat = Math.sin(P);
final double rn = a / Math.sqrt(1 - e2 * (sinLat*sinLat));
dstPts[dstOff++] = (float) ((rn + h) * cosLat * Math.cos(L)); // X: Toward prime meridian
dstPts[dstOff++] = (float) ((rn + h) * cosLat * Math.sin(L)); // Y: Toward East
dstPts[dstOff++] = (float) ((rn * (1-e2) + h) * sinLat); // Z: Toward North
}
}
/**
* Converts geocentric coordinates (x, y, z) to geodetic coordinates
* (longitude, latitude, height), according to the current ellipsoid
* parameters. The method used here is derived from "An Improved
* Algorithm for Geocentric to Geodetic Coordinate Conversion", by
* Ralph Toms, Feb 1996.
*
* @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.
*/
public void inverseTransform(double[] srcPts, int srcOff,
final double[] dstPts, int dstOff, final int numPts)
{
final int dimTarget = getSourceDimensions();
if (srcPts == dstPts && needCopy(srcOff, 3, dstOff, dimTarget, numPts)) {
// Source and destination arrays overlaps: copy in a temporary buffer.
final double[] old = srcPts;
srcPts = new double[numPts*3];
System.arraycopy(old, srcOff, srcPts, 0, srcPts.length);
srcOff = 0;
}
inverseTransform(null, srcPts, srcOff, null, dstPts, dstOff, numPts, dimTarget);
}
/**
* Converts geocentric coordinates (x, y, z) to geodetic coordinates
* (longitude, latitude, height), according to the current ellipsoid
* parameters. The method used here is derived from "An Improved
* Algorithm for Geocentric to Geodetic Coordinate Conversion", by
* Ralph Toms, Feb 1996.
*
* @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.
*/
public void inverseTransform(float[] srcPts, int srcOff,
final float[] dstPts, int dstOff, final int numPts)
{
final int dimTarget = getSourceDimensions();
if (srcPts == dstPts && needCopy(srcOff, 3, dstOff, dimTarget, numPts)) {
// Source and destination arrays overlaps: copy in a temporary buffer.
final float[] old = srcPts;
srcPts = new float[numPts*3];
System.arraycopy(old, srcOff, srcPts, 0, srcPts.length);
srcOff = 0;
}
inverseTransform(srcPts, null, srcOff, dstPts, null, dstOff, numPts, dimTarget);
}
/**
* Implementation of the inverse transformation.
*/
private void inverseTransform(final float[] srcPts1, final double[] srcPts2, int srcOff,
final float[] dstPts1, final double[] dstPts2, int dstOff,
int numPts, final int dimTarget)
{
final boolean hasHeight = (dimTarget>=3);
boolean computeHeight = hasHeight;
assert (computeHeight=true)==true; // Force computeHeight to true if assertions are enabled.
while (--numPts >= 0) {
final double x, y, z;
if (srcPts2 != null) {
x = srcPts2[srcOff++]; // Toward prime meridian
y = srcPts2[srcOff++]; // Toward East
z = srcPts2[srcOff++]; // Toward North
} else {
x = srcPts1[srcOff++]; // Toward prime meridian
y = srcPts1[srcOff++]; // Toward East
z = srcPts1[srcOff++]; // Toward North
}
// Note: The Java version of 'atan2' work correctly for x==0.
// No need for special handling like in the C version.
// No special handling neither for latitude. Formulas
// below are generic enough, considering that 'atan'
// work correctly with infinities (1/0).
// Note: Variable names follow the notation used in Toms, Feb 1996
final double W2 = x*x + y*y; // square of distance from Z axis
final double W = Math.sqrt(W2); // distance from Z axis
final double T0 = z * AD_C; // initial estimate of vertical component
final double S0 = Math.sqrt(T0*T0 + W2); // initial estimate of horizontal component
final double sin_B0 = T0 / S0; // sin(B0), B0 is estimate of Bowring aux variable
final double cos_B0 = W / S0; // cos(B0)
final double sin3_B0 = sin_B0 * sin_B0 * sin_B0; // cube of sin(B0)
final double T1 = z + b * ep2 * sin3_B0; // corrected estimate of vertical component
final double sum = W - a*e2*(cos_B0*cos_B0*cos_B0); // numerator of cos(phi1)
final double S1 = Math.sqrt(T1*T1 + sum * sum); // corrected estimate of horizontal component
final double sin_p1 = T1 / S1; // sin(phi1), phi1 is estimated latitude
final double cos_p1 = sum / S1; // cos(phi1)
final double longitude = Math.toDegrees(Math.atan2(y , x ));
final double latitude = Math.toDegrees(Math.atan(sin_p1 / cos_p1));
final double height;
if (dstPts2 != null) {
dstPts2[dstOff++] = longitude;
dstPts2[dstOff++] = latitude;
} else {
dstPts1[dstOff++] = (float) longitude;
dstPts1[dstOff++] = (float) latitude;
}
if (computeHeight) {
final double rn = a/Math.sqrt(1-e2*(sin_p1*sin_p1)); // Earth radius at location
if (cos_p1 >= +COS_67P5) height = W / +cos_p1 - rn;
else if (cos_p1 <= -COS_67P5) height = W / -cos_p1 - rn;
else height = z / sin_p1 + rn*(e2 - 1.0);
if (hasHeight) {
if (dstPts2 != null) {
dstPts2[dstOff++] = height;
} else {
dstPts1[dstOff++] = (float) height;
}
}
// If assertion are enabled, then transform the
// result and compare it with the input array.
double distance;
assert MAX_ERROR > (distance=checkTransform(new double[]
{x,y,z, longitude, latitude, height})) : distance;
}
}
}
/**
* Transform the last half if the specified array and returns
* the distance with the first half. Array {@code points}
* must have a length of 6.
*/
private double checkTransform(final double[] points) {
transform(points, 3, points, 3, 1, true);
final double dx = points[0]-points[3];
final double dy = points[1]-points[4];
final double dz = points[2]-points[5];
return Math.sqrt(dx*dx + dy*dy + dz*dz);
}
/**
* Returns the inverse of this transform.
*/
@Override
public synchronized MathTransform inverse() {
if (inverse == null) {
inverse = new Inverse();
}
return inverse;
}
/**
* Returns a hash value for this transform.
*/
@Override
public int hashCode() {
final long code = Double.doubleToLongBits( a ) +
37*(Double.doubleToLongBits( b ) +
37*(Double.doubleToLongBits( a2) +
37*(Double.doubleToLongBits( b2) +
37*(Double.doubleToLongBits( e2) +
37*(Double.doubleToLongBits(ep2))))));
return (int) code ^ (int) (code >>> 32) ^ (int)serialVersionUID;
}
/**
* Compares the specified object with this math transform for equality.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
// Slight optimization
return true;
}
if (super.equals(object)) {
final GeocentricTransform that = (GeocentricTransform) object;
return Double.doubleToLongBits(this. a ) == Double.doubleToLongBits(that. a ) &&
Double.doubleToLongBits(this. b ) == Double.doubleToLongBits(that. b ) &&
Double.doubleToLongBits(this. a2) == Double.doubleToLongBits(that. a2) &&
Double.doubleToLongBits(this. b2) == Double.doubleToLongBits(that. b2) &&
Double.doubleToLongBits(this. e2) == Double.doubleToLongBits(that. e2) &&
Double.doubleToLongBits(this.ep2) == Double.doubleToLongBits(that.ep2) &&
this.hasHeight == that.hasHeight;
}
return false;
}
/**
* Inverse of a geocentric transform.
*
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
private final class Inverse extends AbstractMathTransform.Inverse implements Serializable {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 6942084702259211803L;
/**
* Default constructor.
*/
public Inverse() {
GeocentricTransform.this.super();
}
/**
* Returns the parameter descriptors for this math transform.
*/
@Override
public ParameterDescriptorGroup getParameterDescriptors() {
return ProviderInverse.PARAMETERS;
}
/**
* Returns the parameter values for this math transform.
*
* @return A copy of the parameter values for this math transform.
*/
@Override
public ParameterValueGroup getParameterValues() {
return GeocentricTransform.this.getParameterValues(getParameterDescriptors());
}
/**
* Inverse transform an array of points.
*/
public void transform(final double[] source, final int srcOffset,
final double[] dest, final int dstOffset, final int length)
{
GeocentricTransform.this.inverseTransform(source, srcOffset, dest, dstOffset, length);
}
/**
* Inverse transform an array of points.
*/
@Override
public void transform(final float[] source, final int srcOffset,
final float[] dest, final int dstOffset, final int length)
{
GeocentricTransform.this.inverseTransform(source, srcOffset, dest, dstOffset, length);
}
/**
* Restore reference to this object after deserialization.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
GeocentricTransform.this.inverse = this;
}
}
/**
* The provider for {@link GeocentricTransform}. This provider will constructs transforms
* from {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS geographic} to
* {@linkplain org.geotools.referencing.crs.DefaultGeocentricCRS geocentric} coordinate
* reference systems.
*
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public static class Provider extends MathTransformProvider {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 7043216580786030251L;
/**
* The operation parameter descriptor for the "semi_major" parameter value.
* Valid values range from 0 to infinity.
*/
public static final ParameterDescriptor<Double> SEMI_MAJOR = createDescriptor(
new NamedIdentifier[] {
new NamedIdentifier(Citations.OGC, "semi_major"),
new NamedIdentifier(Citations.EPSG, "semi-major axis") //epsg does not specifically define this parameter
},
Double.NaN, 0, Double.POSITIVE_INFINITY, SI.METER);
/**
* The operation parameter descriptor for the "semi_minor" parameter value.
* Valid values range from 0 to infinity.
*/
public static final ParameterDescriptor<Double> SEMI_MINOR = createDescriptor(
new NamedIdentifier[] {
new NamedIdentifier(Citations.OGC, "semi_minor"),
new NamedIdentifier(Citations.EPSG, "semi-minor axis") //epsg does not specifically define this parameter
},
Double.NaN, 0, Double.POSITIVE_INFINITY, SI.METER);
/**
* The number of geographic dimension (2 or 3). This is a Geotools-specific argument.
* The default value is 3, which is the value implied in OGC's WKT.
*/
static final ParameterDescriptor<Integer> DIM = DefaultParameterDescriptor.create(
Collections.singletonMap(NAME_KEY, new NamedIdentifier(Citations.GEOTOOLS, "dim")),
3, 2, 3, false);
/**
* The parameters group.
*/
static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
"Ellipsoid_To_Geocentric", // OGC name
"Geographic/geocentric conversions", // EPSG name
"9602", // EPSG identifier
VocabularyKeys.GEOCENTRIC_TRANSFORM); // Geotools name
/**
* Constructs the parameters group.
*/
static ParameterDescriptorGroup createDescriptorGroup(final String ogc,
final String epsgName,
final String epsgCode,
final int geotools)
{
return createDescriptorGroup(new NamedIdentifier[] {
new NamedIdentifier(Citations.OGC, ogc),
new NamedIdentifier(Citations.EPSG, epsgName),
new NamedIdentifier(Citations.EPSG, epsgCode),
new NamedIdentifier(Citations.GEOTOOLS, Vocabulary.formatInternational(geotools))
}, new ParameterDescriptor[] {
SEMI_MAJOR, SEMI_MINOR, DIM
});
}
/**
* The provider for the 2D case. Will be constructed when first needed.
*/
transient Provider noHeight;
/**
* Constructs a provider with default parameters.
*/
public Provider() {
super(3, 3, PARAMETERS);
}
/**
* Constructs a provider from a set of parameters.
*
* @param sourceDimensions Number of dimensions in the source CRS of this operation method.
* @param targetDimensions Number of dimensions in the target CRS of this operation method.
* @param parameters The set of parameters (never {@code null}).
*/
Provider(final int sourceDimensions,
final int targetDimensions,
final ParameterDescriptorGroup parameters)
{
super(sourceDimensions, targetDimensions, parameters);
}
/**
* Returns the operation type.
*/
@Override
public Class<Conversion> getOperationType() {
return Conversion.class;
}
/**
* Creates a 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.
*/
protected MathTransform createMathTransform(final ParameterValueGroup values)
throws ParameterNotFoundException
{
final int dimGeographic = intValue(DIM, values);
final double semiMajor = doubleValue(SEMI_MAJOR, values);
final double semiMinor = doubleValue(SEMI_MINOR, values);
final boolean hasHeight = (dimGeographic != 2); // Value may be 0, which default as 3.
MathTransform transform = new GeocentricTransform(semiMajor, semiMinor, SI.METER, hasHeight);
if (!hasHeight) {
if (noHeight == null) {
noHeight = new Provider(2, 3, PARAMETERS);
}
transform = new Delegate(transform, noHeight);
}
return transform;
}
}
/**
* The provider for inverse of {@link GeocentricTransform}. This provider will construct
* transforms from {@linkplain org.geotools.referencing.crs.DefaultGeocentricCRS geocentric}
* to {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS geographic} coordinate
* reference systems.
*
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public static class ProviderInverse extends Provider {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -7356791540110076789L;
/**
* The parameters group.
*
* @todo The EPSG code seems to be the same than for the direct transform.
*/
static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
"Geocentric_To_Ellipsoid", // OGC name
"Geographic/geocentric conversions", // EPSG name
"9602", // EPSG identifier
VocabularyKeys.GEOCENTRIC_TRANSFORM); // Geotools name
/**
* Creates a provider.
*/
public ProviderInverse() {
super(3, 3, PARAMETERS);
}
/**
* Constructs a provider from a set of parameters.
*
* @param sourceDimensions Number of dimensions in the source CRS of this operation method.
* @param targetDimensions Number of dimensions in the target CRS of this operation method.
* @param parameters The set of parameters (never {@code null}).
*/
ProviderInverse(final int sourceDimensions,
final int targetDimensions,
final ParameterDescriptorGroup parameters)
{
super(sourceDimensions, targetDimensions, parameters);
}
/**
* Creates a 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.
*/
@Override
public MathTransform createMathTransform(final ParameterValueGroup values)
throws ParameterNotFoundException
{
final int dimGeographic = intValue(DIM, values);
final double semiMajor = doubleValue(SEMI_MAJOR, values);
final double semiMinor = doubleValue(SEMI_MINOR, values);
final boolean hasHeight = (dimGeographic != 2); // Value may be 0, which default as 3.
MathTransform transform = new GeocentricTransform(semiMajor, semiMinor, SI.METER, hasHeight).inverse();
if (!hasHeight) {
if (noHeight == null) {
noHeight = new ProviderInverse(3, 2, PARAMETERS);
}
transform = new Delegate(transform, noHeight);
}
return transform;
}
}
}