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