/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2009, 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 formulas from the PROJ package of USGS.
* USGS's work is fully acknowledged here. This derived work has
* been relicensed under LGPL with Frank Warmerdam's permission.
*/
package org.geotools.referencing.operation.projection;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.PlanarProjection;
import org.geotools.measure.Latitude;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.referencing.NamedIdentifier;
import org.geotools.resources.i18n.VocabularyKeys;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import java.util.Collection;
import java.awt.geom.Point2D;
import static java.lang.Math.*;
/**
* <p>Equidistant Conic Projection.
* <p>
* <b>NOTE:</b>
* formulae used below are from a port, to Java, of the {@code proj4}
* package of the USGS survey. USGS work is acknowledged here.
* <p>
* <b>References:</b>
* <ul>
* <li>Proj-4.4.7 available at <A HREF="http://trac.osgeo.org/proj/">trac.osgeo.org/proj/</A>.<br>
* Relevant files are: {@code PJ_eqdc.c}, {@code pj_mlfn.c}, {@code pj_msfn.c}, {@code pj_fwd.c} and {@code pj_inv.c}.</li>
* </ul>
*
* @see <A HREF="http://mathworld.wolfram.com/ConicEquidistantProjection.html">Conic Equidistant projection on mathworld.wolfram.com</A>
* @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/equidistant_conic.html">"Equidistant Conic" on www.remotesensing.org</A>
*
* @since 2.6.1
* @source $URL: $
* @version $Id: $
* @author Ivan Boldyrev
*/
public class EquidistantConic extends MapProjection {
/**
* For compatibility with different versions during deserialization.
*/
private static final long serialVersionUID = 5885522933843653157L;
/**
* Maximum difference allowed when comparing real numbers.
*/
private static final double EPSILON = 1E-6;
/**
* Internal value of projection.
*/
private final double rho0, n, c;
/**
* First standard parallel.
*/
private final double phi1;
/**
* Second standard parallel.
*/
private double phi2;
/**
* Creates a transform from the specified group of parameter values.
*
* @param parameters The group of parameter values.
* @throws ParameterNotFoundException if a required parameter was not found.
*
* @since 2.4
*/
protected EquidistantConic(final ParameterValueGroup parameters)
throws ParameterNotFoundException
{
super(parameters);
/* Non-final placeholder for value of final this.n. */
double n;
// Fetch parameters
final Collection<GeneralParameterDescriptor> expected = getParameterDescriptors().descriptors();
phi1 = doubleValue(expected, Provider.STANDARD_PARALLEL_1, parameters);
ensureLatitudeInRange( Provider.STANDARD_PARALLEL_1, phi1, true);
phi2 = doubleValue(expected, Provider.STANDARD_PARALLEL_2, parameters);
if (Double.isNaN(phi2)) {
phi2 = phi1;
}
ensureLatitudeInRange(Provider.STANDARD_PARALLEL_2, phi2, true);
// Compute Constants
if (abs(phi1 + phi2) < EPSILON) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.ANTIPODE_LATITUDES_$2,
new Latitude(toDegrees(phi1)), new Latitude(toDegrees(phi2))));
}
double sinphi = n = sin(phi1);
double cosphi = cos(phi1);
boolean secant = abs(phi1 - phi2) >= EPSILON;
if (isSpherical) {
if (secant) {
n = (cosphi - cos(phi2)) / (phi2 - phi1);
}
c = phi1 + cos(phi1) / n;
rho0 = c - latitudeOfOrigin;
en0 = en1 = en2 = en3 = en4 = 0.0;
} else {
double ml1, m1;
m1 = msfn(sinphi, cosphi);
ml1 = mlfn(phi1, sinphi, cosphi);
if (secant) {
sinphi = sin(phi2);
cosphi = cos(phi2);
n = (m1 - msfn(sinphi, cosphi)) /
(mlfn(phi2, sinphi, cosphi) - ml1);
}
c = ml1 + m1 / n;
rho0 = c - mlfn(latitudeOfOrigin, sin(latitudeOfOrigin), cos(latitudeOfOrigin));
}
this.n = n;
}
/**
* {@inheritDoc}
*/
public ParameterDescriptorGroup getParameterDescriptors() {
return Provider.PARAMETERS;
}
/**
* {@inheritDoc}
*/
@Override
public ParameterValueGroup getParameterValues() {
final ParameterValueGroup values = super.getParameterValues();
final Collection<GeneralParameterDescriptor> expected = getParameterDescriptors().descriptors();
set(expected, Provider.STANDARD_PARALLEL_1, values, phi1);
set(expected, Provider.STANDARD_PARALLEL_2, values, phi2);
return values;
}
/**
* Compares the specified object with this map projection for equality.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
// Slight optimization
return true;
}
// Relevant parameters are already compared in MapProjection
return super.equals(object);
}
/**
* Transforms the specified (<var>λ</var>,<var>φ</var>) coordinates
* (units in radians) and stores the result in {@code ptDst} (linear distance
* on a unit sphere).
*/
protected Point2D transformNormalized(double x, double y, final Point2D ptDst)
throws ProjectionException
{
final double cosphi = cos(y);
final double sinphi = sin(y);
final double rho = c - (this.isSpherical
? y
: mlfn(y, sinphi, cosphi));
final double x1 = rho * sin(x*=this.n);
final double y1 = this.rho0 - rho * cos(x);
if (ptDst != null) {
ptDst.setLocation(x1,y1);
return ptDst;
}
return new Point2D.Double(x1,y1);
}
/**
* Transforms the specified (<var>x</var>,<var>y</var>) coordinates
* and stores the result in {@code ptDst}.
*/
protected Point2D inverseTransformNormalized(double x, double y, final Point2D ptDst)
throws ProjectionException
{
double rho = hypot(x, (y = this.rho0 - y));
double phi, lam;
if (rho != 0.0) {
if (this.n < 0.) {
rho = -rho;
x = -x;
y = -y;
}
phi = this.c - rho;
if (!isSpherical) {
phi = inv_mlfn(phi);
}
lam = atan2(x, y) / this.n;
} else {
lam = 0;
phi = (this.n > 0) ? PI/2. : -PI/2.;
}
if (ptDst != null) {
ptDst.setLocation(lam,phi);
return ptDst;
}
return new Point2D.Double(lam,phi);
}
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// PROVIDERS ////////
//////// ////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
/**
* The {@linkplain org.geotools.referencing.operation.MathTransformProvider math transform
* provider} for a {@linkplain EquidistantConic EquidistantConic} projection.
*
* @since 2.6.1
* @version $Id: $
* @author Ivan Boldyrev
*
* @see org.geotools.referencing.operation.DefaultMathTransformFactory
*/
public static final class Provider extends AbstractProvider {
/**
* For compatibility with different versions during deserialization.
*/
private static final long serialVersionUID = 1995516958029802849L;
/**
* The parameters group.
*/
static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] {
new NamedIdentifier(Citations.GEOTIFF, "CT_Equidistant_Conic"),
new NamedIdentifier(Citations.ESRI, "Equidistant_Conic"),
new NamedIdentifier(Citations.GEOTOOLS, Vocabulary.formatInternational(
VocabularyKeys.EQUIDISTANT_CONIC_PROJECTION))
}, new ParameterDescriptor[] {
SEMI_MAJOR, SEMI_MINOR,
CENTRAL_MERIDIAN, LATITUDE_OF_ORIGIN,
STANDARD_PARALLEL_1, STANDARD_PARALLEL_2,
FALSE_EASTING, FALSE_NORTHING
});
/**
* Constructs a new provider.
*/
public Provider() {
super(PARAMETERS);
}
/**
* Returns the operation type for this map projection.
*/
@Override
public Class<PlanarProjection> getOperationType() {
return PlanarProjection.class;
}
/**
* Creates a transform from the specified group of parameter values.
*
* @param parameters The group of parameter values.
* @return The created math transform.
* @throws ParameterNotFoundException if a required parameter was not found.
*/
protected MathTransform createMathTransform(final ParameterValueGroup parameters)
throws ParameterNotFoundException, FactoryException
{
return new EquidistantConic(parameters);
}
}
}