Statistics
| Revision:

root / org.gvsig.jcrs / libJCRS / src / org / geotools / referencing / operation / projection / IdrMercator.java @ 38

History | View | Annotate | Download (18.7 KB)

1
/*
2
 * Geotools 2 - OpenSource mapping toolkit
3
 * (C) 2003, Geotools Project Managment Committee (PMC)
4
 * (C) 2001, Institut de Recherche pour le Dveloppement
5
 * (C) 1999, Fisheries and Oceans Canada
6
 *
7
 *    This library is free software; you can redistribute it and/or
8
 *    modify it under the terms of the GNU Lesser General Public
9
 *    License as published by the Free Software Foundation; either
10
 *    version 2.1 of the License, or (at your option) any later version.
11
 *
12
 *    This library is distributed in the hope that it will be useful,
13
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 *    Lesser General Public License for more details.
16
 *
17
 *    You should have received a copy of the GNU Lesser General Public
18
 *    License along with this library; if not, write to the Free Software
19
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
 *
21
 *
22
 *    This package contains formulas from the PROJ package of USGS.
23
 *    USGS's work is fully acknowledged here.
24
 */
25
package org.geotools.referencing.operation.projection;
26

    
27
// J2SE dependencies and extensions
28
import java.awt.geom.Point2D;
29
import java.util.Collection;
30
import javax.units.NonSI;
31

    
32
// OpenGIS dependencies
33
import org.opengis.parameter.ParameterDescriptor;
34
import org.opengis.parameter.ParameterDescriptorGroup;
35
import org.opengis.parameter.ParameterNotFoundException;
36
import org.opengis.parameter.ParameterValueGroup;
37
import org.opengis.referencing.operation.CylindricalProjection;
38
import org.opengis.referencing.operation.MathTransform;
39

    
40
// Geotools dependencies
41
import org.geotools.measure.Latitude;
42
import org.geotools.metadata.iso.citation.CitationImpl;
43
import org.geotools.referencing.NamedIdentifier;
44
import org.geotools.referencing.operation.MathTransformProvider;
45
import org.geotools.resources.cts.ResourceKeys;
46
import org.geotools.resources.cts.Resources;
47

    
48

    
49
/**
50
 * Mercator Cylindrical Projection. The parallels and the meridians are straight lines and
51
 * cross at right angles; this projection thus produces rectangular charts. The scale is true
52
 * along the equator (by default) or along two parallels equidistant of the equator (if a scale
53
 * factor other than 1 is used). This projection is used to represent areas close to the equator.
54
 * It is also often used for maritime navigation because all the straight lines on the chart are
55
 * <em>loxodrome</em> lines, i.e. a ship following this line would keep a constant azimuth on its
56
 * compass.
57
 * <br><br>
58
 *
59
 * This implementation handles both the 1 and 2 stardard parallel cases.
60
 * For <code>Mercator_1SP</code> (EPSG code 9804), the line of contact is the equator. 
61
 * For <code>Mercator_2SP</code> (EPSG code 9805) lines of contact are symmetrical 
62
 * about the equator.
63
 * <br><br>
64
 *
65
 * <strong>References:</strong><ul>
66
 *   <li>John P. Snyder (Map Projections - A Working Manual,<br>
67
 *       U.S. Geological Survey Professional Paper 1395, 1987)</li>
68
 *   <li>"Coordinate Conversions and Transformations including Formulas",<br>
69
 *       EPSG Guidence Note Number 7, Version 19.</li>
70
 * </ul>
71
 *
72
 * @see <A HREF="http://mathworld.wolfram.com/MercatorProjection.html">Mercator projection on MathWorld</A>
73
 * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/mercator_1sp.html">"mercator_1sp" on Remote Sensing</A>
74
 * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/mercator_2sp.html">"mercator_2sp" on Remote Sensing</A>
75
 * 
76
 * @version $Id: IdrMercator.java 12357 2007-06-27 09:05:41Z dguerrero $
77
 * @author Andr Gosselin
78
 * @author Martin Desruisseaux
79
 * @author Rueben Schulz
80
 */
81
public class IdrMercator extends MapProjection {
82
    /**
83
     * Standard Parallel used for the <code>Mercator_2SP</code> case.
84
     * Set to {@link Double#NaN} for the <code>Mercator_1SP</code> case.
85
     */
86
    protected final double standardParallel;
87
    //protected final double latitudeOfOrigin;
88

    
89
    /**
90
     * The {@link MathTransformProvider} for a {@link IdrMercator} 1SP projection.
91
     *
92
     * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/mercator_1sp.html">"mercator_1sp" on Remote Sensing</A>
93
     * @see org.geotools.referencing.operation.DefaultMathTransformFactory
94
     *
95
     * @version $Id: IdrMercator.java 12357 2007-06-27 09:05:41Z dguerrero $
96
     * @author Martin Desruisseaux
97
     * @author Rueben Schulz
98
     */
99
    public static final class Provider1SP extends AbstractProvider {
100
        /**
101
         * The parameters group.
102
         */
103
            public static final ParameterDescriptor LATITUDE_OF_ORIGIN = createDescriptor(
104
                new NamedIdentifier[] {
105
                    new NamedIdentifier(CitationImpl.OGC,     "latitude_of_origin"),
106
                    new NamedIdentifier(CitationImpl.EPSG,    "CenterLat"),
107
                    new NamedIdentifier(CitationImpl.EPSG,    "Latitude of projection centre"),
108
                    new NamedIdentifier(CitationImpl.GEOTIFF, "NatOriginLat"),
109
                    new NamedIdentifier(CitationImpl.EPSG,    "FalseOriginLat"),
110
                    new NamedIdentifier(CitationImpl.EPSG,    "Latitude of false origin"),                
111
                    new NamedIdentifier(CitationImpl.EPSG,    "Latitude of natural origin"),
112
                    new NamedIdentifier(CitationImpl.EPSG,    "Latitude of projection centre"),
113
                    new NamedIdentifier(CitationImpl.EPSG,    "ProjCenterLat")
114
                }, 0.0, -90.0, 90.0, NonSI.DEGREE_ANGLE);
115

    
116
            static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] {
117
                new NamedIdentifier(CitationImpl.OGC,      "Mercator_1SP"),
118
                new NamedIdentifier(CitationImpl.EPSG,     "Mercator (1SP)"),
119
                new NamedIdentifier(CitationImpl.EPSG,     "9804"),
120
                new NamedIdentifier(CitationImpl.GEOTIFF,  "CT_Mercator"),
121
                new NamedIdentifier(CitationImpl.GEOTOOLS, Resources.formatInternational(
122
                                                           ResourceKeys.CYLINDRICAL_MERCATOR_PROJECTION)),
123
                new NamedIdentifier(new CitationImpl("IDR"), "IDR")
124
            }, new ParameterDescriptor[] {
125
                SEMI_MAJOR,       SEMI_MINOR,
126
                CENTRAL_MERIDIAN, SCALE_FACTOR,
127
                LATITUDE_OF_ORIGIN,
128
                FALSE_EASTING,    FALSE_NORTHING
129
            });
130

    
131
        /**
132
         * Constructs a new provider. 
133
         */
134
        public Provider1SP() {
135
            super(PARAMETERS);
136
        }
137

    
138
        /**
139
         * Returns the operation type for this map projection.
140
         */
141
        protected Class getOperationType() {
142
            return CylindricalProjection.class;
143
        }
144

    
145
        /**
146
         * Creates a transform from the specified group of parameter values.
147
         *
148
         * @param  parameters The group of parameter values.
149
         * @return The created math transform.
150
         * @throws ParameterNotFoundException if a required parameter was not found.
151
         */
152
        public MathTransform createMathTransform(final ParameterValueGroup parameters)
153
                throws ParameterNotFoundException
154
        {
155
            final Collection descriptors = PARAMETERS.descriptors();
156
            if (isSpherical(parameters)) {
157
                return new Spherical(parameters, descriptors);
158
            } else {
159
                return new IdrMercator (parameters, descriptors);
160
            }
161
        }
162
    }
163

    
164
    /**
165
     * The {@link MathTransformProvider} for a {@link IdrMercator} 2SP projection.
166
     *
167
     * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/mercator_2sp.html">"mercator_2sp" on Remote Sensing</A>
168
     * @see org.geotools.referencing.operation.DefaultMathTransformFactory
169
     *
170
     * @version $Id: IdrMercator.java 12357 2007-06-27 09:05:41Z dguerrero $
171
     * @author Martin Desruisseaux
172
     * @author Rueben Schulz
173
     */
174
    public static final class Provider2SP extends AbstractProvider {
175
        /**
176
         * The operation parameter descriptor for the {@link #standardParallel standard parallel}
177
         * parameter value. Valid values range is from -90 to 90. Default value is 0.
178
         */
179
        public static final ParameterDescriptor STANDARD_PARALLEL = createDescriptor(
180
                new NamedIdentifier[] {
181
                    new NamedIdentifier(CitationImpl.OGC,      "standard_parallel_1"),
182
                    new NamedIdentifier(CitationImpl.EPSG,     "Latitude of 1st standard parallel"),
183
                    new NamedIdentifier(CitationImpl.GEOTIFF,  "StdParallel1")
184
                },
185
                0, -90, 90, NonSI.DEGREE_ANGLE);
186

    
187
        /**
188
         * The parameters group.
189
         */
190
        static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] {
191
                new NamedIdentifier(CitationImpl.OGC,      "Mercator_2SP"),
192
                new NamedIdentifier(CitationImpl.EPSG,     "Mercator (2SP)"),
193
                new NamedIdentifier(CitationImpl.EPSG,     "9805"),
194
                new NamedIdentifier(CitationImpl.GEOTIFF,  "CT_Mercator"),
195
                new NamedIdentifier(CitationImpl.ESRI,     "Mercator"),
196
                new NamedIdentifier(CitationImpl.GEOTOOLS, Resources.formatInternational(
197
                                                           ResourceKeys.CYLINDRICAL_MERCATOR_PROJECTION)),
198
                new NamedIdentifier(new CitationImpl("IDR"), "IDR")
199
            }, new ParameterDescriptor[] {
200
                SEMI_MAJOR,       SEMI_MINOR,
201
                CENTRAL_MERIDIAN, STANDARD_PARALLEL,
202
                FALSE_EASTING,    FALSE_NORTHING
203
            });
204

    
205
        /**
206
         * Constructs a new provider. 
207
         */
208
        public Provider2SP() {
209
            super(PARAMETERS);
210
        }
211

    
212
        /**
213
         * Returns the operation type for this map projection.
214
         */
215
        protected Class getOperationType() {
216
            return CylindricalProjection.class;
217
        }
218

    
219
        /**
220
         * Creates a transform from the specified group of parameter values.
221
         *
222
         * @param  parameters The group of parameter values.
223
         * @return The created math transform.
224
         * @throws ParameterNotFoundException if a required parameter was not found.
225
         */
226
        public MathTransform createMathTransform(final ParameterValueGroup parameters)
227
                throws ParameterNotFoundException
228
        {
229
            final Collection descriptors = PARAMETERS.descriptors();
230
            if (isSpherical(parameters)) {
231
                return new Spherical(parameters, descriptors);
232
            } else {
233
                return new IdrMercator (parameters, descriptors);
234
            }
235
        }
236
    }
237

    
238

    
239
    /**
240
     * Constructs a new map projection from the supplied parameters.
241
     *
242
     * @param  parameters The parameter values in standard units.
243
     * @throws ParameterNotFoundException if a mandatory parameter is missing.
244
     */
245
    protected IdrMercator(final ParameterValueGroup parameters)
246
            throws ParameterNotFoundException
247
    {
248
        this(parameters, getDescriptor(parameters).descriptors());
249
    }
250

    
251
    /**
252
     * Work around for RFE #4093999 in Sun's bug database
253
     * ("Relax constraint on placement of this()/super() call in constructors").
254
     */
255
    private static ParameterDescriptorGroup getDescriptor(final ParameterValueGroup parameters) {
256
        try {
257
            parameters.parameter(Provider2SP.STANDARD_PARALLEL.getName().getCode());
258
            return Provider2SP.PARAMETERS;
259
        } catch (ParameterNotFoundException ignore) {
260
            return Provider1SP.PARAMETERS;
261
        }
262
    }
263

    
264
    /**
265
     * Constructs a new map projection from the supplied parameters.
266
     *
267
     * @param  parameters The parameter values in standard units.
268
     * @param  expected The expected parameter descriptors.
269
     * @throws ParameterNotFoundException if a mandatory parameter is missing.
270
     */
271
    IdrMercator(final ParameterValueGroup parameters, final Collection expected)
272
            throws ParameterNotFoundException
273
    {
274
        //Fetch parameters 
275
        super(parameters, expected);
276
        if (expected.contains(Provider2SP.STANDARD_PARALLEL)) {
277
            // scaleFactor is not a parameter in the Mercator_2SP case and is computed from
278
            // the standard parallel.   The super-class constructor should have initialized
279
            // 'scaleFactor' to 1. We still use the '*=' operator rather than '=' in case a
280
            // user implementation still provides a scale factor for its custom projections.
281
            standardParallel = Math.abs(doubleValue(expected,
282
                                        Provider2SP.STANDARD_PARALLEL, parameters));
283
            ensureLatitudeInRange(Provider2SP.STANDARD_PARALLEL, standardParallel, false);
284
            if (isSpherical) {
285
                scaleFactor *= Math.cos(standardParallel);
286
            }  else {
287
                scaleFactor *= msfn(Math.sin(standardParallel),
288
                                    Math.cos(standardParallel));
289
            }
290
            globalScale = scaleFactor*semiMajor;
291
        } else {
292
            // No standard parallel. Instead, uses the scale factor explicitely provided.
293
            standardParallel = Double.NaN;
294
        }
295
        //assert latitudeOfOrigin == 0 : latitudeOfOrigin;
296
    }
297

    
298
    /**
299
     * {@inheritDoc}
300
     */
301
    public ParameterDescriptorGroup getParameterDescriptors() {
302
        return Double.isNaN(standardParallel) ? Provider1SP.PARAMETERS
303
                                              : Provider2SP.PARAMETERS;
304
    }
305

    
306
    /**
307
     * {@inheritDoc}
308
     */
309
    public ParameterValueGroup getParameterValues() {
310
        final ParameterValueGroup values = super.getParameterValues();
311
        if (!Double.isNaN(standardParallel)) {
312
            final Collection expected = getParameterDescriptors().descriptors();
313
            set(expected, Provider2SP.STANDARD_PARALLEL, values, standardParallel);
314
        }
315
        return values;
316
    }
317
    
318
    /**
319
     * Transforms the specified (<var>x</var>,<var>y</var>) coordinate (units in radians)
320
     * and stores the result in <code>ptDst</code> (linear distance on a unit sphere).
321
     */
322
    protected Point2D transformNormalized(double x, double y, final Point2D ptDst)
323
            throws ProjectionException
324
    {
325
        if (Math.abs(y) > (Math.PI/2 - EPS)) {
326
            throw new ProjectionException(Resources.format(
327
                    ResourceKeys.ERROR_POLE_PROJECTION_$1, new Latitude(Math.toDegrees(y))));
328
        }
329

    
330
        y = - Math.log(tsfn(y, Math.sin(y)));
331

    
332
        if (ptDst != null) {
333
            ptDst.setLocation(x,y);
334
            return ptDst;
335
        }
336
        return new Point2D.Double(x,y);
337
    }
338
    
339
    /**
340
     * Transforms the specified (<var>x</var>,<var>y</var>) coordinate
341
     * and stores the result in <code>ptDst</code>.
342
     */
343
    protected Point2D inverseTransformNormalized(double x, double y, final Point2D ptDst)
344
            throws ProjectionException
345
    {
346
        y = Math.exp(-y);
347
        y = cphi2(y);
348

    
349
        if (ptDst != null) {
350
            ptDst.setLocation(x,y);
351
            return ptDst;
352
        }
353
        return new Point2D.Double(x,y);
354
    }
355

    
356

    
357
    /**
358
     * Provides the transform equations for the spherical case of the Mercator projection.
359
     *
360
     * @version $Id: IdrMercator.java 12357 2007-06-27 09:05:41Z dguerrero $
361
     * @author Martin Desruisseaux
362
     * @author Rueben Schulz
363
     */
364
    private static final class Spherical extends IdrMercator {
365
        /**
366
         * Constructs a new map projection from the suplied parameters.
367
         *
368
         * @param  parameters The parameter values in standard units.
369
         * @param  expected The expected parameter descriptors.
370
         * @throws ParameterNotFoundException if a mandatory parameter is missing.
371
         */
372
        protected Spherical(final ParameterValueGroup parameters, final Collection expected)
373
                throws ParameterNotFoundException
374
        {
375
            super(parameters, expected);
376
            //assert isSpherical;
377
        }
378

    
379
        /**
380
         * Transforms the specified (<var>x</var>,<var>y</var>) coordinate
381
         * and stores the result in <code>ptDst</code> using equations for a Sphere.
382
         */
383
        protected Point2D transformNormalized(double x, double y, Point2D ptDst)
384
                throws ProjectionException
385
        {
386
            if (Math.abs(y) > (Math.PI/2 - EPS)) {
387
                throw new ProjectionException(Resources.format(
388
                        ResourceKeys.ERROR_POLE_PROJECTION_$1, new Latitude(Math.toDegrees(y))));
389
            }
390
            // Compute using ellipsoidal formulas, for comparaison later.
391
            //assert (ptDst = super.transformNormalized(x, y, ptDst)) != null;
392
          
393
            y = Math.log(Math.tan((Math.PI/4) + 0.5*y));
394

    
395
            //assert Math.abs(ptDst.getX()-x) <= EPS*globalScale : x;
396
            //assert Math.abs(ptDst.getY()-y) <= EPS*globalScale : y;
397
            if (ptDst != null) {
398
                ptDst.setLocation(x,y);
399
                return ptDst;
400
            }
401
            return new Point2D.Double(x,y);
402
        }
403

    
404
        /**
405
         * Transforms the specified (<var>x</var>,<var>y</var>) coordinate
406
         * and stores the result in <code>ptDst</code> using equations for a sphere.
407
         */
408
        protected Point2D inverseTransformNormalized(double x, double y, Point2D ptDst)
409
                throws ProjectionException
410
        {
411
            // Compute using ellipsoidal formulas, for comparaison later.
412
            //assert (ptDst = super.inverseTransformNormalized(x, y, ptDst)) != null;
413

    
414
            y = (Math.PI/2) - 2.0*Math.atan(Math.exp(-y));
415

    
416
            //assert Math.abs(ptDst.getX()-x) <= EPS : x;
417
            //assert Math.abs(ptDst.getY()-y) <= EPS : y;
418
            if (ptDst != null) {
419
                ptDst.setLocation(x,y);
420
                return ptDst;
421
            }
422
            return new Point2D.Double(x,y);
423
        }      
424
    }
425

    
426

    
427
    /**
428
     * Returns a hash value for this projection.
429
     */
430
    public int hashCode() {
431
        final long code = Double.doubleToLongBits(standardParallel);
432
        return ((int)code ^ (int)(code >>> 32)) + 37*super.hashCode();
433
    }
434

    
435
    /**
436
     * Compares the specified object with this map projection for equality.
437
     */
438
    public boolean equals(final Object object) {
439
        if (object == this) {
440
            // Slight optimization
441
            return true;
442
        }
443
        if (super.equals(object)) {
444
            final IdrMercator that = (IdrMercator) object;
445
            return equals(this.standardParallel,  that.standardParallel);
446
        }
447
        return false;
448
    } 
449
}