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 standard 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}