/*
* SEAGIS - An OpenSource implementation of OpenGIS specification
* (C) 2001, Institut de Recherche pour le Développement
*
* 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; either
* version 2.1 of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* Contacts:
* FRANCE: Surveillance de l'Environnement Assistée par Satellite
* Institut de Recherche pour le Développement / US-Espace
* mailto:seasnet@teledetection.fr
*
* CANADA: Observatoire du Saint-Laurent
* Institut Maurice-Lamontagne
* mailto:osl@osl.gc.ca
*
* This package contains documentation from OpenGIS specifications.
* OpenGIS consortium's work is fully acknowledged here.
*/
package net.seagis.cs;
// OpenGIS dependencies
import org.opengis.cs.CS_Ellipsoid;
import org.opengis.cs.CS_LinearUnit;
// Miscellaneous
import java.util.Map;
import java.lang.Double; // For JavaDoc
import javax.units.Unit;
import java.awt.geom.Point2D;
import java.rmi.RemoteException;
// Resources
import net.seagis.resources.XMath;
import net.seagis.resources.Utilities;
import net.seagis.resources.css.Resources;
import net.seagis.resources.css.ResourceKeys;
/**
* The figure formed by the rotation of an ellipse about an axis.
* In this context, the axis of rotation is always the minor axis. It is named geodetic
* ellipsoid if the parameters are derived by the measurement of the shape and the size
* of the Earth to approximate the geoid as close as possible.
*
* @version 1.00
* @author OpenGIS (www.opengis.org)
* @author Martin Desruisseaux
*
* @see org.opengis.cs.CS_Ellipsoid
*/
public class Ellipsoid extends Info
{
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -1047804526105439230L;
/**
* WGS 1984 ellipsoid. This ellipsoid is used in GPS system
* and is the default for most net.seagis
packages.
*/
public static final Ellipsoid WGS84 = (Ellipsoid) pool.intern(
createFlattenedSphere("WGS84", 6378137.0, 298.257223563, Unit.METRE));
/**
* The equatorial radius.
* @see #getSemiMajorAxis
*/
private final double semiMajorAxis;
/**
* The polar radius.
* @see #getSemiMinorAxis
*/
private final double semiMinorAxis;
/**
* The inverse of the flattening value, or
* {@link Double#POSITIVE_INFINITY} if the
* ellipsoid is a sphere.
*
* @see #getInverseFlattening
*/
private final double inverseFlattening;
/**
* Is the Inverse Flattening definitive for this ellipsoid?
*
* @see #isIvfDefinitive
*/
private final boolean ivfDefinitive;
/**
* The units of the semi-major
* and semi-minor axis values.
*/
private final Unit unit;
/**
* Construct a new sphere using the specified radius.
*
* @param name Name of this sphere.
* @param radius The equatorial and polar radius.
* @param unit The units of the semi-major and semi-minor axis values.
*/
public Ellipsoid(final String name, final double radius, final Unit unit)
{this(name, check("radius", radius), radius, Double.POSITIVE_INFINITY, false, unit);}
/**
* Construct a new ellipsoid using the specified axis length.
*
* @param name Name of this ellipsoid.
* @param semiMajorAxis The equatorial radius.
* @param semiMinorAxis The polar radius.
* @param unit The units of the semi-major and semi-minor axis values.
*
* @see org.opengis.cs.CS_CoordinateSystemFactory#createEllipsoid
*/
public Ellipsoid(final String name, final double semiMajorAxis, final double semiMinorAxis, final Unit unit)
{this(name, semiMajorAxis, semiMinorAxis, semiMajorAxis/(semiMajorAxis-semiMinorAxis), false, unit);}
/**
* Construct a new ellipsoid using the specified axis length.
*
* @param name Name of this ellipsoid.
* @param semiMajorAxis The equatorial radius.
* @param semiMinorAxis The polar radius.
* @param inverseFlattening The inverse of the flattening value.
* @param ivfDefinitive Is the Inverse Flattening definitive for this ellipsoid?
* @param unit The units of the semi-major and semi-minor axis values.
*/
private Ellipsoid(final String name, final double semiMajorAxis, final double semiMinorAxis, final double inverseFlattening, final boolean ivfDefinitive, final Unit unit)
{
super(name);
this.unit = unit;
this.semiMajorAxis = check("semiMajorAxis", semiMajorAxis);
this.semiMinorAxis = check("semiMinorAxis", semiMinorAxis);
this.inverseFlattening = check("inverseFlattening", inverseFlattening);
this.ivfDefinitive = ivfDefinitive;
ensureNonNull ("unit", unit);
ensureLinearUnit(unit);
}
/**
* Construct a new ellipsoid using the specified axis length.
*
* @param properties The set of properties (see {@link Info}).
* @param semiMajorAxis The equatorial radius.
* @param semiMinorAxis The polar radius.
* @param inverseFlattening The inverse of the flattening value.
* @param ivfDefinitive Is the Inverse Flattening definitive for this ellipsoid?
* @param unit The units of the semi-major and semi-minor axis values.
*/
Ellipsoid(final Map properties, final double semiMajorAxis, final double semiMinorAxis, final double inverseFlattening, final boolean ivfDefinitive, final Unit unit)
{
super(properties);
this.unit = unit;
this.semiMajorAxis = semiMajorAxis;
this.semiMinorAxis = semiMinorAxis;
this.inverseFlattening = inverseFlattening;
this.ivfDefinitive = ivfDefinitive;
// Accept null values.
}
/**
* Construct a new ellipsoid using the specified axis length
* and inverse flattening value.
*
* @param name Name of this ellipsoid.
* @param semiMajorAxis The equatorial radius.
* @param inverseFlattening The inverse flattening value.
* @param unit The units of the semi-major and semi-minor axis values.
*
* @see org.opengis.cs.CS_CoordinateSystemFactory#createFlattenedSphere
*/
public static Ellipsoid createFlattenedSphere(final String name, final double semiMajorAxis, final double inverseFlattening, final Unit unit)
{return new Ellipsoid(name, semiMajorAxis, semiMajorAxis*(1-1/inverseFlattening), inverseFlattening, true, unit);}
/**
* Check the argument validity. Argument
* value
should be greater
* than zero.
*
* @param name Argument name.
* @param value Argument value.
* @return value
.
* @throws IllegalArgumentException if value
is not greater than 0.
*/
private static double check(final String name, final double value) throws IllegalArgumentException
{
if (value>0) return value;
throw new IllegalArgumentException(Resources.format(ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2, name, new Double(value)));
}
/**
* Gets the equatorial radius.
* The returned length is expressed in this object's axis units.
*
* @see org.opengis.cs.CS_Ellipsoid#getSemiMajorAxis()
*/
public double getSemiMajorAxis()
{return semiMajorAxis;}
/**
* Gets the polar radius.
* The returned length is expressed in this object's axis units.
*
* @see org.opengis.cs.CS_Ellipsoid#getSemiMinorAxis()
*/
public double getSemiMinorAxis()
{return semiMinorAxis;}
/**
* The ratio of the distance between the center and a focus of the ellipse
* to the length of its semimajor axis. The eccentricity can alternately be
* computed from the equation: e=sqrt(2f-f²)
.
*/
public double getEccentricity()
{
final double f=1-getSemiMinorAxis()/getSemiMajorAxis();
return Math.sqrt(2*f - f*f);
}
/**
* Returns the value of the inverse of the flattening constant.
* Flattening is a value used to indicate how closely an ellipsoid approaches a
* spherical shape. The inverse flattening is related to the equatorial/polar
* radius (re and rp respectively)
* by the formula ivf=re/(re-rp)
.
* For perfect spheres, this method returns {@link Double#POSITIVE_INFINITY}
* (which is the correct value).
*
* @see org.opengis.cs.CS_Ellipsoid#getInverseFlattening()
*/
public double getInverseFlattening()
{return inverseFlattening;}
/**
* Is the Inverse Flattening definitive for this ellipsoid?
* Some ellipsoids use the IVF as the defining value, and calculate the
* polar radius whenever asked. Other ellipsoids use the polar radius to
* calculate the IVF whenever asked. This distinction can be important to
* avoid floating-point rounding errors.
*/
public boolean isIvfDefinitive()
{return ivfDefinitive;}
/**
* Returns an estimation of orthodromic distance between two geographic coordinates.
* The orthodromic distance is the shortest distance between two points on a sphere's surface.
* The orthodromic path is always on a great circle. An other possible distance measurement
* is the loxodromic distance, which is a longer distance on a path with a constant direction
* on the compas.
*
* @param P1 Longitude and latitude of first point (in degrees).
* @param P2 Longitude and latitude of second point (in degrees).
* @return The orthodromic distance (in the units of this ellipsoid).
*/
public double orthodromicDistance(final Point2D P1, final Point2D P2)
{return orthodromicDistance(P1.getX(), P1.getY(), P2.getX(), P2.getY());}
/**
* Returns an estimation of orthodromic distance between two geographic coordinates.
* The orthodromic distance is the shortest distance between two points on a sphere's surface.
* The orthodromic path is always on a great circle. An other possible distance measurement
* is the loxodromic distance, which is a longer distance on a path with a constant direction
* on the compas.
*
* @param x1 Longitude of first point (in degrees).
* @param y1 Latitude of first point (in degrees).
* @param x2 Longitude of second point (in degrees).
* @param y2 Latitude of second point (in degrees).
* @return The orthodromic distance (in the units of this ellipsoid).
*/
public double orthodromicDistance(double x1, double y1, double x2, double y2)
{
/*
* Le calcul de la distance orthodromique sur une surface ellipsoïdale est complexe,
* sujet à des erreurs d'arrondissements et sans solution à proximité des pôles.
* Nous utilisont plutôt un calcul basé sur une forme sphérique de la terre. Un
* programme en Fortran calculant les distances orthodromiques sur une surface
* ellipsoïdale peut être téléchargé à partir du site de NOAA:
*
* ftp://ftp.ngs.noaa.gov/pub/pcsoft/for_inv.3d/source/
*/
y1 = Math.toRadians(y1);
y2 = Math.toRadians(y2);
final double y = 0.5*(y1+y2);
final double dx = Math.toRadians(Math.abs(x2-x1) % 360);
double rho = Math.sin(y1)*Math.sin(y2) + Math.cos(y1)*Math.cos(y2)*Math.cos(dx);
//----- BEGIN JDK 1.4 DEPENDENCIES ----
assert Math.abs(rho) < 1.0000001 : rho;
//----- END OF JDK 1.4 DEPENDENCIES ----
if (rho>+1) rho=+1; // Catch rounding error.
if (rho<-1) rho=-1; // Catch rounging error.
return Math.acos(rho)/XMath.hypot(Math.sin(y)/getSemiMajorAxis(), Math.cos(y)/getSemiMinorAxis());
// 'hypot' calcule l'inverse du rayon **apparent** de la terre à la latitude 'y'.
}
/**
* Returns the units of the semi-major
* and semi-minor axis values.
*
* @see org.opengis.cs.CS_Ellipsoid#getAxisUnit()
*/
public Unit getAxisUnit()
{return unit;}
/**
* Compares the specified object with
* this ellipsoid for equality.
*/
public boolean equals(final Object object)
{
if (super.equals(object))
{
final Ellipsoid that = (Ellipsoid) object;
return this.ivfDefinitive == that.ivfDefinitive &&
Double.doubleToLongBits(this.semiMajorAxis) == Double.doubleToLongBits(that.semiMajorAxis) &&
Double.doubleToLongBits(this.semiMinorAxis) == Double.doubleToLongBits(that.semiMinorAxis) &&
Double.doubleToLongBits(this.inverseFlattening) == Double.doubleToLongBits(that.inverseFlattening) &&
Utilities.equals(this.unit, that.unit);
}
return false;
}
/**
* Returns a hash value for this ellipsoid.
*/
public int hashCode()
{
final long longCode=Double.doubleToLongBits(getSemiMajorAxis());
return (((int)(longCode >>> 32)) ^ (int)longCode) + 37*super.hashCode();
}
/**
* Fill the part inside "[...]".
* Used for formatting Well Know Text (WKT).
*/
String addString(final StringBuffer buffer)
{
buffer.append(", ");
buffer.append(semiMajorAxis);
buffer.append(", ");
buffer.append(Double.isInfinite(inverseFlattening) ? 0 : inverseFlattening);
return "SPHEROID";
}
/**
* Returns an OpenGIS interface for this ellipsoid.
* The returned object is suitable for RMI use.
*
* Note: The returned type is a generic {@link Object} in order
* to avoid too early class loading of OpenGIS interface.
*/
final Object toOpenGIS(final Object adapters)
{return new Export(adapters);}
/////////////////////////////////////////////////////////////////////////
//////////////// ////////////////
//////////////// OPENGIS ADAPTER ////////////////
//////////////// ////////////////
/////////////////////////////////////////////////////////////////////////
/**
* Wrap a {@link Ellipsoid} object for use with OpenGIS.
* This class is suitable for RMI use.
*
* @version 1.0
* @author Martin Desruisseaux
*/
private final class Export extends Info.Export implements CS_Ellipsoid
{
/**
* Construct a remote object.
*/
protected Export(final Object adapters)
{super(adapters);}
/**
* Gets the equatorial radius.
*/
public double getSemiMajorAxis() throws RemoteException
{return Ellipsoid.this.getSemiMajorAxis();}
/**
* Gets the polar radius.
*/
public double getSemiMinorAxis() throws RemoteException
{return Ellipsoid.this.getSemiMinorAxis();}
/**
* Returns the value of the inverse of the flattening constant.
*/
public double getInverseFlattening() throws RemoteException
{
final double ivf=Ellipsoid.this.getInverseFlattening();
return Double.isInfinite(ivf) ? 0 : ivf;
}
/**
* Is the Inverse Flattening definitive for this ellipsoid?
*/
public boolean isIvfDefinitive() throws RemoteException
{return Ellipsoid.this.isIvfDefinitive();}
/**
* Returns the LinearUnit.
*/
public CS_LinearUnit getAxisUnit() throws RemoteException
{return (CS_LinearUnit) adapters.export(Ellipsoid.this.getAxisUnit());}
}
}