001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
006: * (C) 2001, Institut de Recherche pour le Développement
007: * (C) 2000, Frank Warmerdam
008: * (C) 1999, Fisheries and Oceans Canada
009: *
010: * This library is free software; you can redistribute it and/or
011: * modify it under the terms of the GNU Lesser General Public
012: * License as published by the Free Software Foundation; either
013: * version 2.1 of the License, or (at your option) any later version.
014: *
015: * This library is distributed in the hope that it will be useful,
016: * but WITHOUT ANY WARRANTY; without even the implied warranty of
017: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
018: * Lesser General Public License for more details.
019: *
020: * This package contains formulas from the PROJ package of USGS.
021: * USGS's work is fully acknowledged here. This derived work has
022: * been relicensed under LGPL with Frank Warmerdam's permission.
023: */
024: package org.geotools.referencing.operation.projection;
025:
026: // J2SE dependencies
027: import java.util.Collection;
028: import java.awt.geom.Point2D;
029:
030: // OpenGIS dependencies
031: import org.opengis.parameter.ParameterValueGroup;
032: import org.opengis.parameter.ParameterNotFoundException;
033:
034: // Geotools dependencies
035: import org.geotools.measure.Latitude;
036: import org.geotools.resources.i18n.Errors;
037: import org.geotools.resources.i18n.ErrorKeys;
038:
039: /**
040: * Mercator Cylindrical Projection. The parallels and the meridians are straight lines and
041: * cross at right angles; this projection thus produces rectangular charts. The scale is true
042: * along the equator (by default) or along two parallels equidistant of the equator (if a scale
043: * factor other than 1 is used). This projection is used to represent areas close to the equator.
044: * It is also often used for maritime navigation because all the straight lines on the chart are
045: * <em>loxodrome</em> lines, i.e. a ship following this line would keep a constant azimuth on its
046: * compass.
047: * <p>
048: *
049: * This implementation handles both the 1 and 2 stardard parallel cases.
050: * For {@code Mercator_1SP} (EPSG code 9804), the line of contact is the equator.
051: * For {@code Mercator_2SP} (EPSG code 9805) lines of contact are symmetrical
052: * about the equator.
053: * <p>
054: *
055: * <strong>References:</strong><ul>
056: * <li>John P. Snyder (Map Projections - A Working Manual,<br>
057: * U.S. Geological Survey Professional Paper 1395, 1987)</li>
058: * <li>"Coordinate Conversions and Transformations including Formulas",<br>
059: * EPSG Guidence Note Number 7, Version 19.</li>
060: * </ul>
061: *
062: * @see <A HREF="http://mathworld.wolfram.com/MercatorProjection.html">Mercator projection on MathWorld</A>
063: * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/mercator_1sp.html">"mercator_1sp" on RemoteSensing.org</A>
064: * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/mercator_2sp.html">"mercator_2sp" on RemoteSensing.org</A>
065: *
066: * @since 2.1
067: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/projection/Mercator.java $
068: * @version $Id: Mercator.java 25697 2007-05-31 14:26:35Z desruisseaux $
069: * @author André Gosselin
070: * @author Martin Desruisseaux
071: * @author Rueben Schulz
072: * @author Simone Giannecchini
073: */
074: public abstract class Mercator extends MapProjection {
075: /**
076: * Maximum difference allowed when comparing real numbers.
077: */
078: private static final double EPSILON = 1E-6;
079:
080: /**
081: * Standard Parallel used for the {@link Mercator2SP} case.
082: * Set to {@link Double#NaN} for the {@link Mercator1SP} case.
083: */
084: protected final double standardParallel;
085:
086: /**
087: * Constructs a new map projection from the supplied parameters.
088: *
089: * @param parameters The parameter values in standard units.
090: * @throws ParameterNotFoundException if a mandatory parameter is missing.
091: */
092: protected Mercator(final ParameterValueGroup parameters)
093: throws ParameterNotFoundException {
094: // Fetch parameters
095: super (parameters);
096: final Collection expected = getParameterDescriptors()
097: .descriptors();
098: if (expected.contains(AbstractProvider.STANDARD_PARALLEL_1)) {
099: // scaleFactor is not a parameter in the Mercator_2SP case and is computed from
100: // the standard parallel. The super-class constructor should have initialized
101: // 'scaleFactor' to 1. We still use the '*=' operator rather than '=' in case a
102: // user implementation still provides a scale factor for its custom projections.
103: standardParallel = Math.abs(doubleValue(expected,
104: AbstractProvider.STANDARD_PARALLEL_1, parameters));
105: ensureLatitudeInRange(AbstractProvider.STANDARD_PARALLEL_1,
106: standardParallel, false);
107: if (isSpherical) {
108: scaleFactor *= Math.cos(standardParallel);
109: } else {
110: scaleFactor *= msfn(Math.sin(standardParallel), Math
111: .cos(standardParallel));
112: }
113: globalScale = scaleFactor * semiMajor;
114: } else {
115: // No standard parallel. Instead, uses the scale factor explicitely provided.
116: standardParallel = Double.NaN;
117: }
118: /*
119: * A correction that allows us to employs a latitude of origin that is not
120: * correspondent to the equator. See Snyder and al. for reference, page 47.
121: * The scale correction is multiplied with the global scale, which allows
122: * MapProjection superclass to merge this correction with the scale factor
123: * in a single multiplication.
124: */
125: final double sinPhi = Math.sin(latitudeOfOrigin);
126: globalScale *= (Math.cos(latitudeOfOrigin) / (Math.sqrt(1
127: - excentricitySquared * sinPhi * sinPhi)));
128: }
129:
130: /**
131: * {@inheritDoc}
132: */
133: public ParameterValueGroup getParameterValues() {
134: final ParameterValueGroup values = super .getParameterValues();
135: if (!Double.isNaN(standardParallel)) {
136: final Collection expected = getParameterDescriptors()
137: .descriptors();
138: set(expected, AbstractProvider.STANDARD_PARALLEL_1, values,
139: standardParallel);
140: }
141: return values;
142: }
143:
144: /**
145: * Transforms the specified (<var>λ</var>,<var>φ</var>) coordinates
146: * (units in radians) and stores the result in {@code ptDst} (linear distance
147: * on a unit sphere).
148: */
149: protected Point2D transformNormalized(double x, double y,
150: final Point2D ptDst) throws ProjectionException {
151: if (Math.abs(y) > (Math.PI / 2 - EPSILON)) {
152: throw new ProjectionException(Errors.format(
153: ErrorKeys.POLE_PROJECTION_$1, new Latitude(Math
154: .toDegrees(y))));
155: }
156:
157: y = -Math.log(tsfn(y, Math.sin(y)));
158:
159: if (ptDst != null) {
160: ptDst.setLocation(x, y);
161: return ptDst;
162: }
163: return new Point2D.Double(x, y);
164: }
165:
166: /**
167: * Transforms the specified (<var>x</var>,<var>y</var>) coordinates
168: * and stores the result in {@code ptDst}.
169: */
170: protected Point2D inverseTransformNormalized(double x, double y,
171: final Point2D ptDst) throws ProjectionException {
172: y = Math.exp(-y);
173: y = cphi2(y);
174:
175: if (ptDst != null) {
176: ptDst.setLocation(x, y);
177: return ptDst;
178: }
179: return new Point2D.Double(x, y);
180: }
181:
182: /**
183: * Provides the transform equations for the spherical case of the Mercator projection.
184: *
185: * @version $Id: Mercator.java 25697 2007-05-31 14:26:35Z desruisseaux $
186: * @author Martin Desruisseaux
187: * @author Rueben Schulz
188: */
189: static abstract class Spherical extends Mercator {
190: /**
191: * Constructs a new map projection from the suplied parameters.
192: *
193: * @param parameters The parameter values in standard units.
194: * @throws ParameterNotFoundException if a mandatory parameter is missing.
195: */
196: protected Spherical(final ParameterValueGroup parameters)
197: throws ParameterNotFoundException {
198: super (parameters);
199: ensureSpherical();
200: }
201:
202: /**
203: * Transforms the specified (<var>λ</var>,<var>φ</var>) coordinates
204: * (units in radians) and stores the result in {@code ptDst} (linear distance
205: * on a unit sphere).
206: */
207: protected Point2D transformNormalized(double x, double y,
208: Point2D ptDst) throws ProjectionException {
209: if (Math.abs(y) > (Math.PI / 2 - EPSILON)) {
210: throw new ProjectionException(Errors.format(
211: ErrorKeys.POLE_PROJECTION_$1, new Latitude(Math
212: .toDegrees(y))));
213: }
214: // Compute using ellipsoidal formulas, for comparaison later.
215: assert (ptDst = super .transformNormalized(x, y, ptDst)) != null;
216:
217: y = Math.log(Math.tan((Math.PI / 4) + 0.5 * y));
218:
219: assert checkTransform(x, y, ptDst);
220: if (ptDst != null) {
221: ptDst.setLocation(x, y);
222: return ptDst;
223: }
224: return new Point2D.Double(x, y);
225: }
226:
227: /**
228: * Transforms the specified (<var>x</var>,<var>y</var>) coordinates
229: * and stores the result in {@code ptDst} using equations for a sphere.
230: */
231: protected Point2D inverseTransformNormalized(double x,
232: double y, Point2D ptDst) throws ProjectionException {
233: // Compute using ellipsoidal formulas, for comparaison later.
234: assert (ptDst = super .inverseTransformNormalized(x, y,
235: ptDst)) != null;
236:
237: y = (Math.PI / 2) - 2.0 * Math.atan(Math.exp(-y));
238:
239: assert checkInverseTransform(x, y, ptDst);
240: if (ptDst != null) {
241: ptDst.setLocation(x, y);
242: return ptDst;
243: }
244: return new Point2D.Double(x, y);
245: }
246: }
247:
248: /**
249: * Returns a hash value for this projection.
250: */
251: public int hashCode() {
252: final long code = Double.doubleToLongBits(standardParallel);
253: return ((int) code ^ (int) (code >>> 32)) + 37
254: * super .hashCode();
255: }
256:
257: /**
258: * Compares the specified object with this map projection for equality.
259: */
260: public boolean equals(final Object object) {
261: if (object == this ) {
262: // Slight optimization
263: return true;
264: }
265: if (super .equals(object)) {
266: final Mercator that = (Mercator) object;
267: return equals(this .standardParallel, that.standardParallel);
268: }
269: return false;
270: }
271: }
|