Statistics
| Revision:

root / branches / F2 / libraries / libJCRS / src / org / geotools / referencing / operation / projection / TransverseMercator.java @ 12205

History | View | Annotate | Download (34.5 KB)

1
/*
2
 *    GeoTools - OpenSource mapping toolkit
3
 *    http://geotools.org
4
 *
5
 *   (C) 2003-2006, Geotools Project Managment Committee (PMC)
6
 *   (C) 2002, Centre for Computational Geography
7
 *   (C) 2001, Institut de Recherche pour le Dveloppement
8
 *   (C) 2000, Frank Warmerdam
9
 *   (C) 1999, Fisheries and Oceans Canada
10
 *
11
 *    This library is free software; you can redistribute it and/or
12
 *    modify it under the terms of the GNU Lesser General Public
13
 *    License as published by the Free Software Foundation; either
14
 *    version 2.1 of the License, or (at your option) any later version.
15
 *
16
 *    This library is distributed in the hope that it will be useful,
17
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19
 *    Lesser General Public License for more details.
20
 *
21
 *    This package contains formulas from the PROJ package of USGS.
22
 *    USGS's work is fully acknowledged here. This derived work has
23
 *    been relicensed under LGPL with Frank Warmerdam's permission.
24
 */
25
package org.geotools.referencing.operation.projection;
26

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

    
33
import javax.units.NonSI;
34

    
35
// OpenGIS dependencies
36
import org.opengis.metadata.Identifier;
37
import org.opengis.parameter.ParameterDescriptor;
38
import org.opengis.parameter.ParameterDescriptorGroup;
39
import org.opengis.parameter.ParameterNotFoundException;
40
import org.opengis.parameter.ParameterValueGroup;
41
import org.opengis.referencing.operation.CylindricalProjection;
42
import org.opengis.referencing.operation.MathTransform;
43

    
44
// Geotools dependencies
45
import org.geotools.metadata.iso.citation.CitationImpl;
46
import org.geotools.referencing.NamedIdentifier;
47
import org.geotools.referencing.operation.projection.CassiniSoldner.Provider;
48
import org.geotools.referencing.operation.transform.ConcatenatedTransform;
49
import org.geotools.referencing.operation.transform.ProjectiveTransform;
50
//import org.geotools.resources.i18n.VocabularyKeys;
51
//import org.geotools.resources.i18n.Vocabulary;
52
import org.geotools.resources.cts.ResourceKeys;
53
import org.geotools.resources.cts.Resources;
54

    
55

    
56
/**
57
 * Transverse Mercator Projection (EPSG code 9807). This
58
 * is a cylindrical projection, in which the cylinder has been rotated 90.
59
 * Instead of being tangent to the equator (or to an other standard latitude),
60
 * it is tangent to a central meridian. Deformation are more important as we
61
 * are going futher from the central meridian. The Transverse Mercator
62
 * projection is appropriate for region wich have a greater extent north-south
63
 * than east-west.
64
 * <p>
65
 *
66
 * The elliptical equations used here are series approximations, and their accuracy
67
 * decreases as points move farther from the central meridian of the projection.
68
 * The forward equations here are accurate to a less than a mm &plusmn;10 degrees from 
69
 * the central meridian, a few mm &plusmn;15 degrees from the 
70
 * central meridian and a few cm &plusmn;20 degrees from the central meridian.
71
 * The spherical equations are not approximations and should always give the 
72
 * correct values.
73
 * <p>
74
 *
75
 * There are a number of versions of the transverse mercator projection 
76
 * including the Universal (UTM) and Modified (MTM) Transverses Mercator 
77
 * projections. In these cases the earth is divided into zones. For the UTM
78
 * the zones are 6 degrees wide, numbered from 1 to 60 proceeding east from 
79
 * 180 degrees longitude, and between lats 84 degrees North and 80 
80
 * degrees South. The central meridian is taken as the center of the zone
81
 * and the latitude of origin is the equator. A scale factor of 0.9996 and 
82
 * false easting of 500000m is used for all zones and a false northing of 10000000m
83
 * is used for zones in the southern hemisphere.
84
 * <p>
85
 *
86
 * NOTE: formulas used below are not those of Snyder, but rather those
87
 *       from the {@code proj4} package of the USGS survey, which
88
 *       have been reproduced verbatim. USGS work is acknowledged here.
89
 * <p>
90
 *
91
 * <strong>References:</strong><ul>
92
 *   <li> Proj-4.4.6 available at <A HREF="http://www.remotesensing.org/proj">www.remotesensing.org/proj</A><br>
93
 *        Relevent files are: PJ_tmerc.c, pj_mlfn.c, pj_fwd.c and pj_inv.c </li>
94
 *   <li> John P. Snyder (Map Projections - A Working Manual,
95
 *        U.S. Geological Survey Professional Paper 1395, 1987)</li>
96
 *   <li> "Coordinate Conversions and Transformations including Formulas",
97
 *        EPSG Guidence Note Number 7, Version 19.</li>
98
 * </ul>
99
 *
100
 * @see <A HREF="http://mathworld.wolfram.com/MercatorProjection.html">Transverse Mercator projection on MathWorld</A>
101
 * @see <A HREF="http://www.remotesensing.org/geotiff/proj_list/transverse_mercator.html">"Transverse_Mercator" on Remote Sensing</A>
102
 *
103
 * @since 2.1
104
 * @source $URL: http://svn.geotools.org/geotools/tags/2.3.0/module/referencing/src/org/geotools/referencing/operation/projection/TransverseMercator.java $
105
 * @version $Id: TransverseMercator.java 12205 2007-06-19 07:15:42Z jlgomez $
106
 * @author Andr Gosselin
107
 * @author Martin Desruisseaux
108
 * @author Rueben Schulz
109
 */
110
public class TransverseMercator extends MapProjection {
111

    
112
        private final double scaleFactorLocal;
113
        private final double initialLongitude;
114
        private final double zoneWidth;
115
        
116
        /**
117
     * A derived quantity of excentricity, computed by <code>e' = (a鲲-b)/b = es/(1-es)</code>
118
     * where <var>a</var> is the semi-major axis length and <var>b</bar> is the semi-minor axis
119
     * length.
120
     */
121
    private final double esp;
122
    
123
    /**
124
     * Meridian distance at the {@code latitudeOfOrigin}.
125
     * Used for calculations for the ellipsoid.
126
     */
127
    private final double ml0;
128

    
129
     /**
130
     * Constant needed for the <code>mlfn<code> method.
131
     * Setup at construction time.
132
     */
133
    private final double en0,en1,en2,en3,en4;
134
    
135
    /**
136
     * Constants used to calculate {@link #en0}, {@link #en1},
137
     * {@link #en2}, {@link #en3}, {@link #en4}.
138
     */
139
    private static final double C00= 1.0,
140
                                C02= 0.25,
141
                                C04= 0.046875,
142
                                C06= 0.01953125,
143
                                C08= 0.01068115234375,
144
                                C22= 0.75,
145
                                C44= 0.46875,
146
                                C46= 0.01302083333333333333,
147
                                C48= 0.00712076822916666666,
148
                                C66= 0.36458333333333333333,
149
                                C68= 0.00569661458333333333,
150
                                C88= 0.3076171875;
151

    
152
    /**
153
     * Contants used for the forward and inverse transform for the eliptical
154
     * case of the Transverse Mercator.
155
     */
156
    private static final double FC1= 1.00000000000000000000000,  // 1/1
157
                                FC2= 0.50000000000000000000000,  // 1/2
158
                                FC3= 0.16666666666666666666666,  // 1/6
159
                                FC4= 0.08333333333333333333333,  // 1/12
160
                                FC5= 0.05000000000000000000000,  // 1/20
161
                                FC6= 0.03333333333333333333333,  // 1/30
162
                                FC7= 0.02380952380952380952380,  // 1/42
163
                                FC8= 0.01785714285714285714285;  // 1/56
164

    
165
    /**
166
     * Relative iteration precision used in the <code>mlfn<code> method. This 
167
     * overrides the value in the MapProjection class.
168
     */
169
    private static final double TOL = 1E-11;
170

    
171
    /**
172
     * Constructs a new map projection from the supplied parameters.
173
     *
174
     * @param  parameters The parameter values in standard units.
175
     * @throws ParameterNotFoundException if a mandatory parameter is missing.
176
     */
177
    protected TransverseMercator(final ParameterValueGroup parameters)
178
            throws ParameterNotFoundException
179
    {
180
        //Fetch parameters 
181
        super(parameters);
182
        
183
        //  Compute constants
184
        esp = excentricitySquared / (1.0 - excentricitySquared);
185
          
186
        double t;
187
        en0 = C00 - excentricitySquared  *  (C02 + excentricitySquared  * 
188
             (C04 + excentricitySquared  *  (C06 + excentricitySquared  * C08)));
189
        en1 =       excentricitySquared  *  (C22 - excentricitySquared  *
190
             (C04 + excentricitySquared  *  (C06 + excentricitySquared  * C08)));
191
        en2 =  (t = excentricitySquared  *         excentricitySquared) * 
192
             (C44 - excentricitySquared  *  (C46 + excentricitySquared  * C48));
193
        en3 = (t *= excentricitySquared) *  (C66 - excentricitySquared  * C68);
194
        en4 =   t * excentricitySquared  *  C88;
195
        ml0 = mlfn(latitudeOfOrigin, Math.sin(latitudeOfOrigin), Math.cos(latitudeOfOrigin));
196

    
197
        final Collection expected = getParameterDescriptors().descriptors();
198
        if (expected.contains(Provider_TransverseMercatorZonedGridSystem.INITIAL_LONGITUDE)) {
199
                initialLongitude = Math.abs(doubleValue(expected,
200
                                Provider_TransverseMercatorZonedGridSystem.INITIAL_LONGITUDE, parameters));
201
            ensureLatitudeInRange(Provider_TransverseMercatorZonedGridSystem.INITIAL_LONGITUDE, initialLongitude, false);
202
        } else {
203
            // standard parallel is the equator (Plate Carree or Equirectangular)
204
                initialLongitude = Double.NaN;
205
        }
206
        if (expected.contains(Provider_TransverseMercatorZonedGridSystem.ZONE_WIDTH)) {
207
                zoneWidth = Math.abs(doubleValue(expected,
208
                                Provider_TransverseMercatorZonedGridSystem.ZONE_WIDTH, parameters));
209
            ensureLatitudeInRange(Provider_TransverseMercatorZonedGridSystem.ZONE_WIDTH, zoneWidth, false);
210
        } else {
211
            // standard parallel is the equator (Plate Carree or Equirectangular)
212
                zoneWidth = Double.NaN;
213
        }
214
        if (expected.contains(Provider_TransverseMercatorZonedGridSystem.SCALE_FACTOR_LOCAL)) {
215
                scaleFactorLocal = Math.abs(doubleValue(expected,
216
                                Provider_TransverseMercatorZonedGridSystem.SCALE_FACTOR_LOCAL, parameters));
217
            ensureLatitudeInRange(Provider_TransverseMercatorZonedGridSystem.SCALE_FACTOR_LOCAL, scaleFactorLocal, false);
218
        } else {
219
            // standard parallel is the equator (Plate Carree or Equirectangular)
220
                scaleFactorLocal = Double.NaN;
221
        }
222
    }
223
    
224
    /**
225
     * {@inheritDoc}
226
     */
227
    public ParameterDescriptorGroup getParameterDescriptors() {
228
        return Provider.PARAMETERS;
229
    }
230
    
231
    /**
232
     * Transforms the specified (<var>x</var>,<var>y</var>) coordinate (units in radians)
233
     * and stores the result in {@code ptDst} (linear distance on a unit sphere).
234
     */
235
    protected Point2D transformNormalized(double x, double y, Point2D ptDst) 
236
            throws ProjectionException 
237
    {
238
        double sinphi = Math.sin(y);
239
        double cosphi = Math.cos(y);
240
        
241
        double t = (Math.abs(cosphi)>EPS) ? sinphi/cosphi : 0;
242
        t *= t;
243
        double al = cosphi*x;
244
        double als = al*al;
245
        al /= Math.sqrt(1.0 - excentricitySquared * sinphi*sinphi);
246
        double n = esp * cosphi*cosphi;
247

    
248
        /* NOTE: meridinal distance at latitudeOfOrigin is always 0 */
249
        y = (mlfn(y, sinphi, cosphi) - ml0 + 
250
            sinphi*al*x*
251
            FC2 * ( 1.0 +
252
            FC4 * als * (5.0 - t + n*(9.0 + 4.0*n) +
253
            FC6 * als * (61.0 + t * (t - 58.0) + n*(270.0 - 330.0*t) +
254
            FC8 * als * (1385.0 + t * ( t*(543.0 - t) - 3111.0))))));
255
        
256
        x = al*(FC1 + FC3 * als*(1.0 - t + n +
257
            FC5 * als * (5.0 + t*(t - 18.0) + n*(14.0 - 58.0*t) +
258
            FC7 * als * (61.0+ t*(t*(179.0 - t) - 479.0 )))));
259
               
260
        if (ptDst != null) {
261
            ptDst.setLocation(x,y);
262
            return ptDst;
263
        }
264
        return new Point2D.Double(x,y);          
265
    }
266
    
267
    /**
268
     * Transforms the specified (<var>x</var>,<var>y</var>) coordinate
269
     * and stores the result in {@code ptDst}.
270
     */
271
    protected Point2D inverseTransformNormalized(double x, double y, Point2D ptDst) 
272
            throws ProjectionException 
273
    {
274
        double phi = inv_mlfn(ml0 + y);
275
        
276
        if (Math.abs(phi) >= (Math.PI/2)) {
277
            y = y<0.0 ? -(Math.PI/2) : (Math.PI/2);
278
            x = 0.0;
279
        } else {
280
            double sinphi = Math.sin(phi);
281
            double cosphi = Math.cos(phi);
282
            double t = (Math.abs(cosphi) > EPS) ? sinphi/cosphi : 0.0;
283
            double n = esp * cosphi*cosphi;
284
            double con = 1.0 - excentricitySquared * sinphi*sinphi;
285
            double d = x*Math.sqrt(con);
286
            con *= t;
287
            t *= t;
288
            double ds = d*d;
289
            
290
            y = phi - (con*ds / (1.0 - excentricitySquared)) *
291
                FC2 * (1.0 - ds *
292
                FC4 * (5.0 + t*(3.0 - 9.0*n) + n*(1.0 - 4*n) - ds *
293
                FC6 * (61.0 + t*(90.0 - 252.0*n + 45.0*t) + 46.0*n - ds *
294
                FC8 * (1385.0 + t*(3633.0 + t*(4095.0 + 1574.0*t))))));
295
            
296
            x = d*(FC1 - ds * FC3 * (1.0 + 2.0*t + n -
297
                ds*FC5*(5.0 + t*(28.0 + 24* t + 8.0*n) + 6.0*n -
298
                ds*FC7*(61.0 + t*(662.0 + t*(1320.0 + 720.0*t))))))/cosphi;
299
        }
300
        
301
        if (ptDst != null) {
302
            ptDst.setLocation(x,y);
303
            return ptDst;
304
        }
305
        return new Point2D.Double(x,y);        
306
    }
307
    
308
    /**
309
     * {@inheritDoc}
310
     */
311
    protected double getToleranceForAssertions(final double longitude, final double latitude) {
312
        if (Math.abs(longitude - centralMeridian) > 0.26) {   //15 degrees
313
            // When far from the valid area, use a larger tolerance.
314
            return 2.5;
315
        } else if (Math.abs(longitude - centralMeridian) > 0.22) {  //12.5 degrees
316
            return 1.0;
317
        } else if (Math.abs(longitude - centralMeridian) > 0.17) {  //10 degrees
318
            return 0.5;
319
        }
320
        // a normal tolerance
321
        return 1E-6;
322
    }
323
    
324

    
325
    /**
326
     * Provides the transform equations for the spherical case of the
327
     * TransverseMercator projection.
328
     * 
329
     * @version $Id: TransverseMercator.java 12205 2007-06-19 07:15:42Z jlgomez $
330
     * @author Andr Gosselin
331
     * @author Martin Desruisseaux
332
     * @author Rueben Schulz
333
     */
334
    private static final class Spherical extends TransverseMercator {
335
        /**
336
         * Constructs a new map projection from the suplied parameters.
337
         *
338
         * @param  parameters The parameter values in standard units.
339
         * @throws ParameterNotFoundException if a mandatory parameter is missing.
340
         */
341
        protected Spherical(final ParameterValueGroup parameters)
342
                throws ParameterNotFoundException
343
        {
344
            super(parameters);
345
            //assert isSpherical;
346
        }
347
        
348
        /**
349
         * {@inheritDoc}
350
         */
351
        protected Point2D transformNormalized(double x, double y, Point2D ptDst)
352
                throws ProjectionException 
353
        {
354
            // Compute using ellipsoidal formulas, for comparaison later.
355
            double normalizedLongitude = x;
356
            //assert (ptDst = super.transformNormalized(x, y, ptDst)) != null;
357
                    
358
            double cosphi = Math.cos(y);
359
            double b = cosphi * Math.sin(x);
360
            if (Math.abs(Math.abs(b) - 1.0) <= EPS) {
361
                throw new ProjectionException(Resources.format(ResourceKeys.ERROR_VALUE_TEND_TOWARD_INFINITY));
362
            }
363
            
364
            //Using Snyder's equation for calculating y, instead of the one used in Proj4
365
            //poential problems when y and x = 90 degrees, but behaves ok in tests   
366
            y = Math.atan2(Math.tan(y),Math.cos(x)) - latitudeOfOrigin;   /* Snyder 8-3 */
367
            x = 0.5 * Math.log((1.0+b)/(1.0-b));    /* Snyder 8-1 */
368

    
369
            //assert Math.abs(ptDst.getX()-x) <= getToleranceForSphereAssertions(normalizedLongitude,0) : ptDst.getX()-x;
370
            //assert Math.abs(ptDst.getY()-y) <= getToleranceForSphereAssertions(normalizedLongitude,0) : ptDst.getY()-y;
371
            if (ptDst != null) {
372
                ptDst.setLocation(x,y);
373
                return ptDst;
374
            }
375
            return new Point2D.Double(x,y);
376
        }        
377
        
378
        /**
379
         * {@inheritDoc}
380
         */
381
        protected Point2D inverseTransformNormalized(double x, double y, Point2D ptDst) 
382
                throws ProjectionException 
383
        {
384
            // Compute using ellipsoidal formulas, for comparaison later.
385
            //assert (ptDst = super.inverseTransformNormalized(x, y, ptDst)) != null;
386
            
387
            double t = Math.exp(x);
388
            double sinhX = 0.5 * (t-1.0/t);                //sinh(x)
389
            double cosD = Math.cos(latitudeOfOrigin + y);
390
            double phi = Math.asin(Math.sqrt((1.0-cosD*cosD)/(1.0+sinhX*sinhX)));
391
            // correct for the fact that we made everything positive using sqrt(x*x)
392
            y = ((y + latitudeOfOrigin)<0.0) ? -phi : phi;   
393
            x = (Math.abs(sinhX)<=EPS && Math.abs(cosD)<=EPS) ? 
394
                    0.0 : Math.atan2(sinhX,cosD);
395
           
396
            //assert Math.abs(ptDst.getX()-x) <= getToleranceForSphereAssertions(x,0) : ptDst.getX()-x;
397
            //assert Math.abs(ptDst.getY()-y) <= getToleranceForSphereAssertions(x,0) : ptDst.getY()-y;
398
            if (ptDst != null) {
399
                ptDst.setLocation(x,y);
400
                return ptDst;
401
            }
402
            return new Point2D.Double(x,y);
403
        }
404
        
405
        /*
406
         *  Allow a larger tolerance when comparing spherical to elliptical calculations
407
         *  when we are far from the central meridian (elliptical calculations are
408
         *  not valid here).
409
         *
410
         *  longitude here is standardized (in radians) and centralMeridian has 
411
         *  already been removed from it.
412
         */
413
        protected double getToleranceForSphereAssertions(final double longitude, final double latitude) {
414
            if (Math.abs(Math.abs(longitude)- Math.PI/2.0) < TOL) {  //90 degrees
415
                // elliptical equations are at their worst here
416
                return 1E18;
417
            }
418
            if (Math.abs(longitude) > 0.26) {   //15 degrees
419
                // When far from the valid area, use a very larger tolerance.          
420
                return 1000000;
421
            }
422
            // a normal tolerance
423
            return 1E-6;
424
        }
425
    }
426
    
427
    
428
    /**
429
     * Calculates the meridian distance. This is the distance along the central 
430
     * meridian from the equator to {@code phi}. Accurate to < 1e-5 meters 
431
     * when used in conjuction with typical major axis values.
432
     *
433
     * @param phi latitude to calculate meridian distance for.
434
     * @param sphi sin(phi).
435
     * @param cphi cos(phi).
436
     * @return meridian distance for the given latitude.
437
     */
438
    private final double mlfn(final double phi, double sphi, double cphi) {        
439
        cphi *= sphi;
440
        sphi *= sphi;
441
        return en0 * phi - cphi *
442
              (en1 + sphi *
443
              (en2 + sphi *
444
              (en3 + sphi *
445
              (en4))));
446
    }
447
    
448
    /**
449
     * Calculates the latitude ({@code phi}) from a meridian distance.
450
     * Determines phi to TOL (1e-11) radians, about 1e-6 seconds.
451
     * 
452
     * @param arg meridian distance to calulate latitude for.
453
     * @return the latitude of the meridian distance.
454
     * @throws ProjectionException if the itteration does not converge.
455
     */
456
    private final double inv_mlfn(double arg) throws ProjectionException {
457
        double s, t, phi, k = 1.0/(1.0 - excentricitySquared);
458
        int i;
459
        phi = arg;
460
        for (i=MAX_ITER; true;) { // rarely goes over 5 iterations
461
            if (--i < 0) {
462
                throw new ProjectionException(Resources.format(ResourceKeys.ERROR_NO_CONVERGENCE));
463
            }
464
            s = Math.sin(phi);
465
            t = 1.0 - excentricitySquared * s * s;
466
            t = (mlfn(phi, s, Math.cos(phi)) - arg) * (t * Math.sqrt(t)) * k;
467
            phi -= t;
468
            if (Math.abs(t) < TOL) {
469
                return phi;
470
            }
471
        }
472
    }
473
    
474
    /**
475
     * Convenience method computing the zone code from the central meridian.
476
     * Information about zones convention must be specified in argument. Two
477
     * widely set of arguments are of Universal Transverse Mercator (UTM) and
478
     * Modified Transverse Mercator (MTM) projections:<br>
479
     * <p>
480
     *
481
     * UTM projection (zones numbered from 1 to 60):<br>
482
     * <p>
483
     *        {@code getZone(-177, 6);}<br>
484
     * <p>
485
     * MTM projection (zones numbered from 1 to 120):<br>
486
     * <p>
487
     *        {@code getZone(-52.5, -3);}<br>
488
     *
489
     * @param  centralLongitudeZone1 Longitude in the middle of zone 1, in decimal degrees
490
     *         relative to Greenwich. Positive longitudes are toward east, and negative
491
     *         longitudes toward west.
492
     * @param  zoneWidth Number of degrees of longitudes in one zone. A positive value
493
     *         means that zones are numbered from west to east (i.e. in the direction of
494
     *         positive longitudes). A negative value means that zones are numbered from
495
     *         east to west.
496
     * @return The zone number. First zone is numbered 1.
497
     */
498
    private int getZone(final double centralLongitudeZone1, final double zoneWidth) {
499
        final double zoneCount = Math.abs(360/zoneWidth);
500
        double t;
501
        t  = centralLongitudeZone1 - 0.5*zoneWidth; // Longitude at the beginning of the first zone.
502
        t  = Math.toDegrees(centralMeridian) - t;   // Degrees of longitude between the central longitude and longitude 1.
503
        t  = Math.floor(t/zoneWidth + EPS);         // Number of zones between the central longitude and longitude 1.
504
        t -= zoneCount*Math.floor(t/zoneCount);     // If negative, bring back to the interval 0 to (zoneCount-1).
505
        return ((int) t)+1;
506
    }
507
    
508
    /**
509
     * Convenience method returning the meridian in the middle of current zone. This meridian is
510
     * typically the central meridian. This method may be invoked to make sure that the central
511
     * meridian is correctly set.
512
     *
513
     * @param  centralLongitudeZone1 Longitude in the middle of zone 1, in decimal degrees
514
     *         relative to Greenwich. Positive longitudes are toward east, and negative
515
     *         longitudes toward west.
516
     * @param  zoneWidth Number of degrees of longitudes in one zone. A positive value
517
     *         means that zones are numbered from west to east (i.e. in the direction of
518
     *         positive longitudes). A negative value means that zones are numbered from
519
     *         east to west.
520
     * @return The central meridian.
521
     */
522
    private double getCentralMedirian(final double centralLongitudeZone1, final double zoneWidth) {
523
        double t;
524
        t  = centralLongitudeZone1 + (getZone(centralLongitudeZone1, zoneWidth)-1)*zoneWidth;
525
        t -= 360*Math.floor((t+180)/360); // Bring back into [-180..+180] range.
526
        return t;
527
    }
528

    
529
    /**
530
     * Convenience method computing the zone code from the central meridian.
531
     *
532
     * @return The zone number, using the scalefactor and false easting 
533
     *         to decide if this is a UTM or MTM case. Returns 0 if the 
534
     *         case of the projection cannot be determined.
535
     */
536
    public int getZone() {
537
        // UTM
538
        if (scaleFactor == 0.9996 && falseEasting == 500000.0) {
539
            return getZone(-177, 6);
540
        }
541
        // MTM
542
        if (scaleFactor == 0.9999 && falseEasting == 304800.0){
543
            return getZone(-52.5, -3);
544
        }
545
        // unknown
546
        throw new IllegalStateException(Resources.format(ResourceKeys.ERROR_UNKNOW_TYPE_$1));//.UNKNOW_PROJECTION_TYPE));
547
    }
548

    
549
    /**
550
     * Convenience method returning the meridian in the middle of current zone. This meridian is
551
     * typically the central meridian. This method may be invoked to make sure that the central
552
     * meridian is correctly set.
553
     *
554
     * @return The central meridian, using the scalefactor and false easting 
555
     *         to decide if this is a UTM or MTM case. Returns {@link Double#NaN}
556
     *         if the case of the projection cannot be determined.
557
     */
558
    public double getCentralMeridian() {
559
        // UTM
560
        if (scaleFactor == 0.9996 && falseEasting == 500000.0) {
561
            return getCentralMedirian(-177, 6);
562
        }
563
        // MTM
564
        if (scaleFactor == 0.9999 && falseEasting == 304800.0){
565
            return getCentralMedirian(-52.5, -3);
566
        }
567
        // unknown
568
        throw new IllegalStateException(Resources.format(ResourceKeys.ERROR_UNKNOW_TYPE_$1));//.UNKNOW_PROJECTION_TYPE));
569
    }
570

    
571
    /**
572
     * Returns a hash value for this projection.
573
     */
574
    public int hashCode() { 
575
        final long code = Double.doubleToLongBits(ml0);
576
        return ((int)code ^ (int)(code >>> 32)) + 37*super.hashCode();
577
    }
578

    
579
    /**
580
     * Compares the specified object with this map projection for equality.
581
     */
582
    public boolean equals(final Object object) {
583
        if (object == this) {
584
            // Slight optimization
585
            return true;
586
        }
587
        // Relevant parameters are already compared in MapProjection
588
        return super.equals(object);
589
    }
590
    
591
    
592
    
593
    
594
    //////////////////////////////////////////////////////////////////////////////////////////
595
    //////////////////////////////////////////////////////////////////////////////////////////
596
    ////////                                                                          ////////
597
    ////////                                 PROVIDER                                 ////////
598
    ////////                                                                          ////////
599
    //////////////////////////////////////////////////////////////////////////////////////////
600
    //////////////////////////////////////////////////////////////////////////////////////////
601

    
602
    /**
603
     * The {@link org.geotools.referencing.operation.MathTransformProvider}
604
     * for a {@link TransverseMercator} projection.
605
     *
606
     * @see org.geotools.referencing.operation.DefaultMathTransformFactory
607
     *
608
     * @since 2.1
609
     * @version $Id: TransverseMercator.java 12205 2007-06-19 07:15:42Z jlgomez $
610
     * @author Martin Desruisseaux
611
     * @author Rueben Schulz
612
     */
613
    public static class Provider extends AbstractProvider {
614
        /**
615
         * Returns a descriptor group for the specified parameters.
616
         */
617
        static ParameterDescriptorGroup createDescriptorGroup(final Identifier[] identifiers) {
618
            return createDescriptorGroup(identifiers, new ParameterDescriptor[] {
619
                SEMI_MAJOR,       SEMI_MINOR,
620
                CENTRAL_MERIDIAN, LATITUDE_OF_ORIGIN,
621
                SCALE_FACTOR,     FALSE_EASTING,
622
                FALSE_NORTHING
623
            });
624
        }
625

    
626
        /**
627
         * The parameters group.
628
         */
629
        static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] {
630
                new NamedIdentifier(CitationImpl.OGC,      "Transverse_Mercator"),
631
                new NamedIdentifier(CitationImpl.EPSG,     "Transverse Mercator"),
632
                new NamedIdentifier(CitationImpl.EPSG,     "Gauss-Kruger"),
633
                new NamedIdentifier(CitationImpl.EPSG,     "9807"),
634
                new NamedIdentifier(CitationImpl.GEOTIFF,  "CT_TransverseMercator"),
635
                new NamedIdentifier(CitationImpl.ESRI,     "Transverse_Mercator"),
636
                new NamedIdentifier(CitationImpl.ESRI,     "Gauss_Kruger")//,
637
                //new NamedIdentifier(CitationImpl.GEOTOOLS, Vocabulary.formatInternational(
638
                //                    VocabularyKeys.TRANSVERSE_MERCATOR_PROJECTION))
639
            });
640

    
641
        /**
642
         * Constructs a new provider.
643
         */
644
        public Provider() {
645
            super(PARAMETERS);
646
        }
647

    
648
        /**
649
         * Constructs a new provider with the specified parameters.
650
         */
651
        Provider(final ParameterDescriptorGroup descriptor) {
652
            super(descriptor);
653
        }
654

    
655
        /**
656
         * Returns the operation type for this map projection.
657
         */
658
        protected Class getOperationType() {
659
            return CylindricalProjection.class;
660
        }
661

    
662
        /**
663
         * Creates a transform from the specified group of parameter values.
664
         *
665
         * @param  parameters The group of parameter values.
666
         * @return The created math transform.
667
         * @throws ParameterNotFoundException if a required parameter was not found.
668
         */
669
        public MathTransform createMathTransform(final ParameterValueGroup parameters)
670
                throws ParameterNotFoundException
671
        {
672
            if (isSpherical(parameters)) {
673
                return new Spherical(parameters);
674
            } else {
675
                return new TransverseMercator(parameters);
676
            }
677
        }
678
    }
679

    
680
    /**
681
     * The {@link org.geotools.referencing.operation.MathTransformProvider} for a South Orientated
682
     * {@link TransverseMercator} projection (EPSG code 9808). Note that at the contrary of what
683
     * this class name suggest, the projected coordinates are still increasing toward North. This
684
     * is because all {@link MapProjection}s must complies with standard axis orientations. The
685
     * real axis flip is performed by the CRS framework outside this package.
686
     * See "<cite>Axis units and orientation</cite>" in
687
     * {@linkplain org.geotools.referencing.operation.projection package description} for details.
688
     * <p>
689
     * The usual Transverse Mercator formulas are:
690
     * <p>
691
     * <ul>
692
     *   <li><var>easting</var> = <var>{@linkplain #falseEasting false easting}</var> + <var>px</var></li>
693
     *   <li><var>northing</var> = <var>{@linkplain #falseNorthing false northing}</var> + <var>py</var></li>
694
     * </ul>
695
     * <p>
696
     * The Transverse Mercator South Orientated Projection formulas are:
697
     * <p>
698
     * <ul>
699
     *   <li><var>westing</var> = <var>{@linkplain #falseEasting false easting}</var> - <var>px</var></li>
700
     *   <li><var>southing</var> = <var>{@linkplain #falseNorthing false northing}</var> - <var>py</var></li>
701
     * </ul>
702
     * <p>
703
     * Where the <var>px</var> and <var>py</var> terms are the same in both cases.
704
     * Transforms created by this provider actually computes
705
     * (<var>easting</var>,<var>northing</var>) = (-<var>westing</var>,-<var>southing</var>).
706
     * This is equivalent to a {@link TransverseMercator} projection with
707
     * {@link #falseEasting falseEasting} and {@link #falseNorthing falseNorthing} sign reverted.
708
     * This operation is implemented as a concatenation of a North-orientated transverse mercator
709
     * projection with an affine transform for (<var>false easting</var>,<var>false northing</var>)
710
     * correction.
711
     *
712
     * @since 2.2
713
     * @version $Id: TransverseMercator.java 12205 2007-06-19 07:15:42Z jlgomez $
714
     * @author Martin Desruisseaux
715
     */
716
    public static class Provider_SouthOrientated extends Provider {
717
        /**
718
         * Constructs a new provider.
719
         */
720
        public Provider_SouthOrientated() {
721
            super(createDescriptorGroup(new NamedIdentifier[] {
722
                new NamedIdentifier(CitationImpl.EPSG, "Transverse Mercator (South Orientated)"),
723
                new NamedIdentifier(CitationImpl.EPSG, "9808")
724
            }));
725
        }
726

    
727
        /**
728
         * Creates a transform from the specified group of parameter values.
729
         *
730
         * @param  parameters The group of parameter values.
731
         * @return The created math transform.
732
         * @throws ParameterNotFoundException if a required parameter was not found.
733
         */
734
        public MathTransform createMathTransform(final ParameterValueGroup parameters)
735
                throws ParameterNotFoundException
736
        {
737
            final MapProjection projection = (MapProjection) super.createMathTransform(parameters);
738
            if (projection.falseEasting==0 && projection.falseNorthing==0) {
739
                return projection;
740
            }
741
            final AffineTransform step = AffineTransform.getTranslateInstance(
742
                    -2*projection.falseEasting, -2*projection.falseNorthing);
743
            return ConcatenatedTransform.create(ProjectiveTransform.create(step), projection);
744
        }
745
    }
746
    public static class Provider_TransverseMercatorZonedGridSystem extends Provider {
747
        /**
748
         * Constructs a new provider.
749
         */
750
               public static final ParameterDescriptor INITIAL_LONGITUDE = createDescriptor(
751
                        new NamedIdentifier[] {
752
                            new NamedIdentifier(CitationImpl.OGC,     "Initial longitude"),
753
                            new NamedIdentifier(CitationImpl.EPSG,    "Initial_longitude")
754
                        }, 0.0, -180.0, 180.0, NonSI.DEGREE_ANGLE);
755

    
756
               public static final ParameterDescriptor ZONE_WIDTH = createDescriptor(
757
                        new NamedIdentifier[] {
758
                            new NamedIdentifier(CitationImpl.OGC,     "Zone width"),
759
                            new NamedIdentifier(CitationImpl.EPSG,    "Zone_width")
760
                        }, 0.0, 0.0, 12.0, NonSI.DEGREE_ANGLE);
761

    
762
               public static final ParameterDescriptor SCALE_FACTOR_LOCAL = createDescriptor(
763
                        new NamedIdentifier[] {
764
                            new NamedIdentifier(CitationImpl.OGC,      "scale_factor"),
765
                            new NamedIdentifier(CitationImpl.EPSG,     "Scale factor on initial line"),
766
                            new NamedIdentifier(CitationImpl.EPSG,    "Scale factor at natural origin"),
767
                            new NamedIdentifier(CitationImpl.GEOTIFF, "ScaleAtNatOrigin"),
768
                            new NamedIdentifier(CitationImpl.GEOTIFF, "ScaleAtCenter")
769
                        },
770
                        1, 0, Double.POSITIVE_INFINITY, Unit.ONE);
771

    
772
               static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] {
773
                new NamedIdentifier(CitationImpl.OGC,      "Transverse_Mercator_Zoned_Grid_System"),
774
                            new NamedIdentifier(CitationImpl.EPSG,     "Transverse Mercator Zoned Grid System")
775
//                new NamedIdentifier(CitationImpl.GEOTOOLS, Vocabulary.formatInternational(
776
//                                    VocabularyKeys.EQUIDISTANT_CYLINDRICAL_PROJECTION))
777
            }, new ParameterDescriptor[] {
778
                SEMI_MAJOR,       SEMI_MINOR,
779
                CENTRAL_MERIDIAN,
780
                INITIAL_LONGITUDE, ZONE_WIDTH,
781
                LATITUDE_OF_ORIGIN, SCALE_FACTOR_LOCAL,
782
                FALSE_EASTING,    FALSE_NORTHING
783
            });
784

    
785
        public Provider_TransverseMercatorZonedGridSystem() {
786
            super(PARAMETERS);
787
        }
788
 
789
        protected Class getOperationType() {
790
                return CylindricalProjection.class;
791
        }
792

    
793
        /**
794
         * Creates a transform from the specified group of parameter values.
795
         *
796
         * @param  parameters The group of parameter values.
797
         * @return The created math transform.
798
         * @throws ParameterNotFoundException if a required parameter was not found.
799
         */
800
        public MathTransform createMathTransform(final ParameterValueGroup parameters)
801
        throws ParameterNotFoundException
802
        {
803
                    //return null;
804
                        return new CassiniSoldner(parameters);
805
                }
806
    }
807
}