/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 1999-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 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 java.util.Collection; import java.awt.geom.Point2D; import org.opengis.parameter.GeneralParameterDescriptor; import org.opengis.parameter.ParameterValueGroup; import org.opengis.parameter.ParameterNotFoundException; import static java.lang.Math.*; /** * Mercator Cylindrical Projection. The parallels and the meridians are straight lines and * cross at right angles; this projection thus produces rectangular charts. The scale is true * along the equator (by default) or along two parallels equidistant of the equator (if a scale * factor other than 1 is used). This projection is used to represent areas close to the equator. * It is also often used for maritime navigation because all the straight lines on the chart are * loxodrome lines, i.e. a ship following this line would keep a constant azimuth on its * compass. *

* This implementation handles both the 1 and 2 stardard parallel cases. * For {@code Mercator_1SP} (EPSG code 9804), the line of contact is the equator. * For {@code Mercator_2SP} (EPSG code 9805) lines of contact are symmetrical * about the equator. *

* References: *

* * @see Mercator projection on MathWorld * @see "mercator_1sp" on RemoteSensing.org * @see "mercator_2sp" on RemoteSensing.org * * @since 2.1 * @source $URL$ * @version $Id$ * @author AndrĂ© Gosselin * @author Martin Desruisseaux (PMO, IRD) * @author Rueben Schulz * @author Simone Giannecchini */ public abstract class Mercator extends MapProjection { /** * For cross-version compatibility. */ private static final long serialVersionUID = 6146741819833248649L; /** * Maximum difference allowed when comparing real numbers. */ private static final double EPSILON = 1E-6; /** * Standard Parallel used for the {@link Mercator2SP} case. * Set to {@link Double#NaN} for the {@link Mercator1SP} case. */ protected final double standardParallel; /** * Constructs a new map projection from the supplied parameters. * * @param parameters The parameter values in standard units. * @throws ParameterNotFoundException if a mandatory parameter is missing. */ protected Mercator(final ParameterValueGroup parameters) throws ParameterNotFoundException { super(parameters); final Collection expected = getParameterDescriptors().descriptors(); if (expected.contains(AbstractProvider.STANDARD_PARALLEL_1)) { /* * scaleFactor is not a parameter in the Mercator_2SP case and is computed from * the standard parallel. The super-class constructor should have initialized * 'scaleFactor' to 1. We still use the '*=' operator rather than '=' in case a * user implementation still provides a scale factor for its custom projections. */ standardParallel = abs(doubleValue(expected, AbstractProvider.STANDARD_PARALLEL_1, parameters)); ensureLatitudeInRange(AbstractProvider.STANDARD_PARALLEL_1, standardParallel, false); if (isSpherical) { scaleFactor *= cos(standardParallel); } else { scaleFactor *= msfn(sin(standardParallel), cos(standardParallel)); } globalScale = scaleFactor * semiMajor; } else { // No standard parallel. Instead, uses the scale factor explicitely provided. standardParallel = Double.NaN; } /* * A correction that allows us to employs a latitude of origin that is not * correspondent to the equator. See Snyder and al. for reference, page 47. * The scale correction is multiplied with the global scale, which allows * MapProjection superclass to merge this correction with the scale factor * in a single multiplication. */ final double sinPhi = sin(latitudeOfOrigin); globalScale *= (cos(latitudeOfOrigin) / (sqrt(1 - excentricitySquared * sinPhi * sinPhi))); } /** * {@inheritDoc} */ @Override public ParameterValueGroup getParameterValues() { final ParameterValueGroup values = super.getParameterValues(); if (!Double.isNaN(standardParallel)) { final Collection expected = getParameterDescriptors().descriptors(); set(expected, AbstractProvider.STANDARD_PARALLEL_1, values, standardParallel); } return values; } /** * Transforms the specified (λ,φ) 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 { if (abs(y) > (PI/2 - EPSILON)) { throw new ProjectionException(y); } y = -log(tsfn(y, sin(y))); if (ptDst != null) { ptDst.setLocation(x,y); return ptDst; } return new Point2D.Double(x,y); } /** * Transforms the specified (x,y) coordinates * and stores the result in {@code ptDst}. */ protected Point2D inverseTransformNormalized(double x, double y, final Point2D ptDst) throws ProjectionException { y = exp(-y); y = cphi2(y); if (ptDst != null) { ptDst.setLocation(x,y); return ptDst; } return new Point2D.Double(x,y); } /** * Provides the transform equations for the spherical case of the Mercator projection. * * @version $Id$ * @author Martin Desruisseaux (PMO, IRD) * @author Rueben Schulz */ static abstract class Spherical extends Mercator { /** * For cross-version compatibility. */ private static final long serialVersionUID = 2383414176395616561L; /** * Constructs a new map projection from the suplied parameters. * * @param parameters The parameter values in standard units. * @throws ParameterNotFoundException if a mandatory parameter is missing. */ protected Spherical(final ParameterValueGroup parameters) throws ParameterNotFoundException { super(parameters); ensureSpherical(); } /** * Transforms the specified (λ,φ) coordinates * (units in radians) and stores the result in {@code ptDst} (linear distance * on a unit sphere). */ @Override protected Point2D transformNormalized(double x, double y, Point2D ptDst) throws ProjectionException { if (abs(y) > (PI/2 - EPSILON)) { throw new ProjectionException(y); } // Compute using ellipsoidal formulas, for comparaison later. assert (ptDst = super.transformNormalized(x, y, ptDst)) != null; y = log(tan(PI/4 + 0.5*y)); assert checkTransform(x, y, ptDst); if (ptDst != null) { ptDst.setLocation(x,y); return ptDst; } return new Point2D.Double(x,y); } /** * Transforms the specified (x,y) coordinates * and stores the result in {@code ptDst} using equations for a sphere. */ @Override protected Point2D inverseTransformNormalized(double x, double y, Point2D ptDst) throws ProjectionException { // Computes using ellipsoidal formulas, for comparaison later. assert (ptDst = super.inverseTransformNormalized(x, y, ptDst)) != null; y = PI/2 - 2.0*atan(exp(-y)); assert checkInverseTransform(x, y, ptDst); if (ptDst != null) { ptDst.setLocation(x,y); return ptDst; } return new Point2D.Double(x,y); } } /** * Returns a hash value for this projection. */ @Override public int hashCode() { final long code = Double.doubleToLongBits(standardParallel); return ((int)code ^ (int)(code >>> 32)) + 37*super.hashCode(); } /** * Compares the specified object with this map projection for equality. */ @Override public boolean equals(final Object object) { if (object == this) { // Slight optimization return true; } if (super.equals(object)) { final Mercator that = (Mercator) object; return equals(this.standardParallel, that.standardParallel); } return false; } }