001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.projection.proj;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import org.openstreetmap.josm.data.Bounds;
007import org.openstreetmap.josm.data.projection.ProjectionConfigurationException;
008import org.openstreetmap.josm.tools.Utils;
009
010/**
011 * Mercator Cylindrical Projection. The parallels and the meridians are straight lines and
012 * cross at right angles; this projection thus produces rectangular charts. The scale is true
013 * along the equator (by default) or along two parallels equidistant of the equator (if a scale
014 * factor other than 1 is used). This projection is used to represent areas close to the equator.
015 * It is also often used for maritime navigation because all the straight lines on the chart are
016 * <em>loxodrome</em> lines, i.e. a ship following this line would keep a constant azimuth on its
017 * compass.
018 * <p>
019 * This implementation handles both the 1 and 2 stardard parallel cases.
020 * For 1 SP (EPSG code 9804), the line of contact is the equator.
021 * For 2 SP (EPSG code 9805) lines of contact are symmetrical
022 * about the equator.
023 * <p>
024 * This class has been derived from the implementation of the Geotools project;
025 * git 8cbf52d, org.geotools.referencing.operation.projection.Mercator
026 * at the time of migration.
027 * <p>
028 * <b>References:</b>
029 * <ul>
030 *   <li>John P. Snyder (Map Projections - A Working Manual,<br>
031 *       U.S. Geological Survey Professional Paper 1395, 1987)</li>
032 *   <li>"Coordinate Conversions and Transformations including Formulas",<br>
033 *       EPSG Guidence Note Number 7, Version 19.</li>
034 * </ul>
035 *
036 * @author André Gosselin
037 * @author Martin Desruisseaux (PMO, IRD)
038 * @author Rueben Schulz
039 * @author Simone Giannecchini
040 *
041 * @see <A HREF="http://mathworld.wolfram.com/MercatorProjection.html">Mercator projection on MathWorld</A>
042 * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/mercator_1sp.html">"mercator_1sp" on RemoteSensing.org</A>
043 * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/mercator_2sp.html">"mercator_2sp" on RemoteSensing.org</A>
044 */
045public class Mercator extends AbstractProj implements IScaleFactorProvider {
046    /**
047     * Maximum difference allowed when comparing real numbers.
048     */
049    private static final double EPSILON = 1E-6;
050
051    protected double scaleFactor;
052
053    @Override
054    public String getName() {
055        return tr("Mercator");
056    }
057
058    @Override
059    public String getProj4Id() {
060        return "merc";
061    }
062
063    @Override
064    public void initialize(ProjParameters params) throws ProjectionConfigurationException {
065        super.initialize(params);
066        scaleFactor = 1;
067        if (params.lat_ts != null) {
068            /*
069             * scaleFactor is not a parameter in the 2 SP case and is computed from
070             * the standard parallel.
071             */
072            double standardParallel = Utils.toRadians(params.lat_ts);
073            if (spherical) {
074                scaleFactor *= Math.cos(standardParallel);
075            } else {
076                scaleFactor *= msfn(Math.sin(standardParallel), Math.cos(standardParallel));
077            }
078        }
079        /*
080         * A correction that allows us to employs a latitude of origin that is not
081         * correspondent to the equator. See Snyder and al. for reference, page 47.
082         */
083        if (params.lat0 != null) {
084            final double lat0 = Utils.toRadians(params.lat0);
085            final double sinPhi = Math.sin(lat0);
086            scaleFactor *= (Math.cos(lat0) / (Math.sqrt(1 - e2 * sinPhi * sinPhi)));
087        }
088    }
089
090    @Override
091    public double[] project(double y, double x) {
092        if (Math.abs(y) > (Math.PI/2 - EPSILON)) {
093            return new double[] {0, 0}; // this is an error and should be handled somehow
094        }
095        if (spherical) {
096            y = Math.log(Math.tan(Math.PI/4 + 0.5*y));
097        } else {
098            y = -Math.log(tsfn(y, Math.sin(y)));
099        }
100        return new double[] {x, y};
101    }
102
103    @Override
104    public double[] invproject(double x, double y) {
105        if (spherical) {
106            y = Math.PI/2 - 2.0*Math.atan(Math.exp(-y));
107        } else {
108            y = Math.exp(-y);
109            y = cphi2(y);
110        }
111        return new double[] {y, x};
112    }
113
114    @Override
115    public Bounds getAlgorithmBounds() {
116        return new Bounds(-89, -180, 89, 180, false);
117    }
118
119    @Override
120    public double getScaleFactor() {
121        return scaleFactor;
122    }
123
124    @Override
125    public boolean lonIsLinearToEast() {
126        return true;
127    }
128}