001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/ct/MapProjection.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/exse/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification
012     (C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/)
013     SEAGIS Contacts:  Surveillance de l'Environnement Assist�e par Satellite
014     Institut de Recherche pour le D�veloppement / US-Espace
015     mailto:seasnet@teledetection.fr
016    
017    
018     This library is free software; you can redistribute it and/or
019     modify it under the terms of the GNU Lesser General Public
020     License as published by the Free Software Foundation; either
021     version 2.1 of the License, or (at your option) any later version.
022    
023     This library is distributed in the hope that it will be useful,
024     but WITHOUT ANY WARRANTY; without even the implied warranty of
025     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
026     Lesser General Public License for more details.
027    
028     You should have received a copy of the GNU Lesser General Public
029     License along with this library; if not, write to the Free Software
030     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
031    
032     Contact:
033    
034     Andreas Poth
035     lat/lon GmbH
036     Aennchenstr. 19
037     53115 Bonn
038     Germany
039     E-Mail: poth@lat-lon.de
040    
041     Klaus Greve
042     Department of Geography
043     University of Bonn
044     Meckenheimer Allee 166
045     53115 Bonn
046     Germany
047     E-Mail: klaus.greve@uni-bonn.de
048    
049     
050     ---------------------------------------------------------------------------*/
051    package org.deegree.model.csct.ct;
052    
053    // Coordinates
054    import java.awt.Shape;
055    import java.awt.geom.Point2D;
056    
057    import javax.media.jai.ParameterList;
058    
059    import org.deegree.model.csct.cs.Projection;
060    import org.deegree.model.csct.pt.Latitude;
061    import org.deegree.model.csct.pt.Longitude;
062    import org.deegree.model.csct.resources.Geometry;
063    import org.deegree.model.csct.resources.css.ResourceKeys;
064    import org.deegree.model.csct.resources.css.Resources;
065    
066    /**
067     * Provides transformation services between ellipsoidal and cartographic
068     * projections. Ellipsoidal height values remain unchanged.
069     *
070     *
071     * @version 1.0
072     * @author Andr� Gosselin
073     * @author Martin Desruisseaux
074     */
075    abstract class MapProjection extends AbstractMathTransform implements MathTransform2D {
076    
077    
078        /**
079         * Marge de tol�rance pour les comparaisons de nombre r�els.
080         */
081        static final double EPS = 1.0E-6;
082    
083        /**
084         * Marge de tol�rance pour les calculs it�ratifs.
085         */
086        static final double TOL = 1E-10;
087    
088        /**
089         * Classification string for this projection
090         * (e.g. "Transverse_Mercator").
091         */
092        private final String classification;
093    
094        /**
095         * Indique si le mod�le terrestre est sph�rique. La valeur <code>true</code>
096         * indique que le mod�le est sph�rique, c'est-�-dire que les champs {@link #a}
097         * et {@link #b} ont la m�me valeur.
098         */
099        protected final boolean isSpherical;
100    
101        /**
102         * Excentricit� de l'ellipse. L'excentricit� est 0
103         * si l'ellipso�de est sph�rique, c'est-�-dire si
104         * {@link #isSpherical} est <code>true</code>.
105         */
106        protected final double e;
107    
108        /**
109         * Carr� de l'excentricit� de l'ellipse: e� = (a�-b�)/a�.
110         */
111        protected final double es;
112    
113        /**
114         * Longueur de l'axe majeur de la terre, en m�tres.
115         * Sa valeur par d�faut d�pend de l'�llipso�de par
116         * d�faut (par exemple "WGS 1984").
117         */
118        protected final double a;
119    
120        /**
121         * Longueur de l'axe mineur de la terre, en m�tres.
122         * Sa valeur par d�faut d�pend de l'�llipso�de par
123         * d�faut (par exemple "WGS 1984").
124         */
125        protected final double b;
126    
127        /**
128         * Central longitude in <u>radians</u>.  Default value is 0, the Greenwich
129         * meridian. <strong>Consider this field as final</strong>. It is not final
130         * only  because {@link TransverseMercatorProjection} need to modify it at
131         * construction time.
132         */
133        protected double centralMeridian;
134    
135        /**
136         * Central latitude in <u>radians</u>. Default value is 0, the equator.
137         * <strong>Consider this field as final</strong>. It is not final only
138         * because some class need to modify it at construction time.
139         */
140        protected double centralLatitude;
141    
142        protected double false_easting;
143    
144        protected double false_northing;
145    
146        /**
147         * The inverse of this map projection.
148         * Will be created only when needed.
149         */
150        private transient MathTransform inverse;
151    
152        /**
153         * Construct a new map projection from the suplied parameters.
154         *
155         * @param  parameters The parameter values in standard units.
156         *         The following parameter are recognized:
157         *         <ul>
158         *           <li>"semi_major"   (default to WGS 1984)</li>
159         *           <li>"semi_minor"   (default to WGS 1984)</li>
160         *           <li>"central_meridian"   (default to 0�)</li>
161         *           <li>"latitude_of_origin" (default to 0�)</li>
162         *         </ul>
163         * @throws MissingParameterException if a mandatory parameter is missing.
164         */
165        protected MapProjection( final Projection parameters ) throws MissingParameterException {
166            this.classification = parameters.getClassName();
167            this.a = parameters.getValue( "semi_major" );
168            this.b = parameters.getValue( "semi_minor" );
169            this.centralMeridian = longitudeToRadians( parameters.getValue( "central_meridian", 0 ),
170                                                       true );
171            this.centralLatitude = latitudeToRadians( parameters.getValue( "latitude_of_origin", 0 ),
172                                                      true );
173            this.isSpherical = ( a == b );
174            this.es = 1.0 - ( b * b ) / ( a * a );
175            this.e = Math.sqrt( es );
176    
177            false_easting = parameters.getValue( "false_easting" );
178            false_northing = parameters.getValue( "false_northing" );
179    
180        }
181    
182        /**
183         * Returns a human readable name localized for the specified locale.
184         */
185        public abstract String getName();
186    
187        /**
188         * Gets the dimension of input points.
189         */
190        public final int getDimSource() {
191            return 2;
192        }
193    
194        /**
195         * Gets the dimension of output points.
196         */
197        public final int getDimTarget() {
198            return 2;
199        }
200    
201        /**
202         * Convertit en radians une longitude exprim�e en degr�s. Au passage, cette m�thode v�rifiera
203         * si la longitude est bien dans les limites permises (�180�). Cette m�thode est utile pour
204         * v�rifier la validit� des param�tres de la projection, comme {@link #setCentralLongitude}.
205         *
206         * @param  x Longitude � v�rifier, en degr�s.
207         * @param  edge <code>true</code> pour accepter les longitudes de �180�.
208         * @return Longitude en radians.
209         * @throws IllegalArgumentException si la longitude est invalide.
210         */
211        static double longitudeToRadians( final double x, boolean edge )
212                                throws IllegalArgumentException {
213            if ( edge ? ( x >= Longitude.MIN_VALUE && x <= Longitude.MAX_VALUE )
214                     : ( x > Longitude.MIN_VALUE && x < Longitude.MAX_VALUE ) ) {
215                return Math.toRadians( x );
216            }
217            throw new IllegalArgumentException(
218                                                Resources.format(
219                                                                  ResourceKeys.ERROR_LONGITUDE_OUT_OF_RANGE_$1,
220                                                                  new Longitude( x ) ) );
221        }
222    
223        /**
224         * Convertit en radians une latitude exprim�e en degr�s. Au passage, cette m�thode v�rifiera
225         * si la latitude est bien dans les limites permises (�90�). Cette m�thode est utile pour
226         * v�rifier la validit� des param�tres de la projection, comme {@link #setCentralLongitude}.
227         *
228         * @param  y Latitude � v�rifier, en degr�s.
229         * @param  edge <code>true</code> pour accepter les latitudes de �90�.
230         * @return Latitude en radians.
231         * @throws IllegalArgumentException si la latitude est invalide.
232         */
233        static double latitudeToRadians( final double y, boolean edge )
234                                throws IllegalArgumentException {
235            if ( edge ? ( y >= Latitude.MIN_VALUE && y <= Latitude.MAX_VALUE )
236                     : ( y > Latitude.MIN_VALUE && y < Latitude.MAX_VALUE ) ) {
237                return Math.toRadians( y );
238            }
239            throw new IllegalArgumentException(
240                                                Resources.format(
241                                                                  ResourceKeys.ERROR_LATITUDE_OUT_OF_RANGE_$1,
242                                                                  new Latitude( y ) ) );
243        }
244    
245     
246        //////////////////////////////////////////////////////////////////////
247        ////                                                              ////
248        ////                          TRANSFORMS                          ////
249        ////                                                              ////
250        //////////////////////////////////////////////////////////////////////
251        /**
252         * Transforms the specified coordinate and stores the result in <code>ptDst</code>.
253         * This method is guaranteed to be invoked with values of <var>x</var> in the range
254         * <code>[-PI..PI]</code> and values of <var>y</var> in the range <code>[-PI/2..PI/2]</code>.
255         *
256         * @param x     The longitude of the coordinate, in <strong>radians</strong>.
257         * @param y     The  latitude of the coordinate, in <strong>radians</strong>.
258         * @param ptDst the specified coordinate point that stores the
259         *              result of transforming <code>ptSrc</code>, or
260         *              <code>null</code>. Ordinates will be in metres.
261         * @return the coordinate point after transforming <code>ptSrc</code>
262         *         and stroring the result in <code>ptDst</code>.
263         * @throws TransformException if the point can't be transformed.
264         */
265        protected abstract Point2D transform( double x, double y, final Point2D ptDst )
266                                throws TransformException;
267    
268        /**
269         * Transforms the specified <code>ptSrc</code>
270         * and stores the result in <code>ptDst</code>.
271         *
272         * @param ptSrc the specified coordinate point to be transformed.
273         *              Ordinates must be in degrees.
274         * @param ptDst the specified coordinate point that stores the
275         *              result of transforming <code>ptSrc</code>, or
276         *              <code>null</code>. Ordinates will be in metres.
277         * @return the coordinate point after transforming <code>ptSrc</code>
278         *         and stroring the result in <code>ptDst</code>.
279         * @throws TransformException if the point can't be transformed.
280         */
281        public final Point2D transform( final Point2D ptSrc, Point2D ptDst )
282                                throws TransformException {
283            final double x = ptSrc.getX();
284            final double y = ptSrc.getY();
285            if ( !( x >= Longitude.MIN_VALUE && x <= Longitude.MAX_VALUE ) ) {
286                throw new TransformException(
287                                              Resources.format(
288                                                                ResourceKeys.ERROR_LONGITUDE_OUT_OF_RANGE_$1,
289                                                                new Longitude( x ) ) );
290            }
291            if ( !( y >= Latitude.MIN_VALUE && y <= Latitude.MAX_VALUE ) ) {
292                throw new TransformException(
293                                              Resources.format(
294                                                                ResourceKeys.ERROR_LATITUDE_OUT_OF_RANGE_$1,
295                                                                new Latitude( y ) ) );
296            }
297            ptDst = transform( Math.toRadians( x ), Math.toRadians( y ), ptDst );
298            return ptDst;
299        }
300    
301        /**
302         * Transforms a list of coordinate point ordinal values.
303         * Ordinates must be (<var>longitude</var>,<var>latitude</var>)
304         * pairs in degrees.
305         *
306         * @throws TransformException if a point can't be transformed. This method try
307         *         to transform every points even if some of them can't be transformed.
308         *         Non-transformable points will have value {@link Double#NaN}. If more
309         *         than one point can't be transformed, then this exception may be about
310         *         an arbitrary point.
311         */
312        public final void transform( final double[] src, int srcOffset, final double[] dest,
313                                    int dstOffset, int numPts )
314                                throws TransformException {
315            /*
316             * V�rifie s'il faudra parcourir le tableau en sens inverse.
317             * Ce sera le cas si les tableaux source et destination se
318             * chevauchent et que la destination est apr�s la source.
319             */
320            final boolean reverse = ( src == dest && srcOffset < dstOffset && srcOffset + ( 2 * numPts ) > dstOffset );
321            if ( reverse ) {
322                srcOffset += 2 * numPts;
323                dstOffset += 2 * numPts;
324            }
325            final Point2D.Double point = new Point2D.Double();
326            TransformException firstException = null;
327            while ( --numPts >= 0 ) {
328                try {
329                    point.x = src[srcOffset++];
330                    point.y = src[srcOffset++];
331                    transform( point, point );
332                    dest[dstOffset++] = point.x;
333                    dest[dstOffset++] = point.y;
334                } catch ( TransformException exception ) {
335                    dest[dstOffset++] = Double.NaN;
336                    dest[dstOffset++] = Double.NaN;
337                    if ( firstException == null ) {
338                        firstException = exception;
339                    }
340                }
341                if ( reverse ) {
342                    srcOffset -= 4;
343                    dstOffset -= 4;
344                }
345            }
346            if ( firstException != null )
347                throw firstException;
348        }
349    
350        /**
351         * Transforms a list of coordinate point ordinal values.
352         * Ordinates must be (<var>longitude</var>,<var>latitude</var>)
353         * pairs in degrees.
354         *
355         * @throws TransformException if a point can't be transformed. This method try
356         *         to transform every points even if some of them can't be transformed.
357         *         Non-transformable points will have value {@link Float#NaN}. If more
358         *         than one point can't be transformed, then this exception may be about
359         *         an arbitrary point.
360         */
361        public final void transform( final float[] src, int srcOffset, final float[] dest,
362                                    int dstOffset, int numPts )
363                                throws TransformException {
364            final boolean reverse = ( src == dest && srcOffset < dstOffset && srcOffset
365                                                                              + ( numPts << 1 ) > dstOffset );
366            if ( reverse ) {
367                srcOffset += 2 * numPts;
368                dstOffset += 2 * numPts;
369            }
370            final Point2D.Double point = new Point2D.Double();
371            TransformException firstException = null;
372            while ( --numPts >= 0 ) {
373                try {
374                    point.x = src[srcOffset++];
375                    point.y = src[srcOffset++];
376                    transform( point, point );
377                    dest[dstOffset++] = (float) point.x;
378                    dest[dstOffset++] = (float) point.y;
379                } catch ( TransformException exception ) {
380                    dest[dstOffset++] = Float.NaN;
381                    dest[dstOffset++] = Float.NaN;
382                    if ( firstException == null ) {
383                        firstException = exception;
384                    }
385                }
386                if ( reverse ) {
387                    srcOffset -= 4;
388                    dstOffset -= 4;
389                }
390            }
391            if ( firstException != null )
392                throw firstException;
393        }
394    
395        /**
396         * Transforme la forme g�om�trique <code>shape</code> sp�cifi�e.
397         * Cette projection peut remplacer certaines lignes droites
398         * par des courbes. Tous les points de la forme g�om�trique
399         * seront copi�s. Cette m�thode n'est donc pas � conseiller
400         * si <code>shape</code> est volumineux, par exemple s'il
401         * repr�sente une bathym�trie enti�re.
402         *
403         * @param shape Forme g�om�trique � transformer. Les coordonn�es des points
404         *              de cette forme doivent �tre exprim�es en degr�s de latitudes
405         *              et de longitudes.
406         * @return      Forme g�om�trique transform�e. Les coordonn�es des points de
407         *              cette forme seront exprim�es en m�tres.
408         * @throws TransformException si une transformation a �chou�e.
409         */
410        public final Shape createTransformedShape( final Shape shape )
411                                throws TransformException {
412            return createTransformedShape( shape, null, null, Geometry.HORIZONTAL );
413        }
414    
415        //////////////////////////////////////////////////////////////////////
416        ////                                                              ////
417        ////                      INVERSE TRANSFORMS                      ////
418        ////                                                              ////
419        //////////////////////////////////////////////////////////////////////
420    
421        /**
422         * Transforms the specified coordinate and stores the result in <code>ptDst</code>.
423         * This method shall returns <var>x</var> values in the range <code>[-PI..PI]</code>
424         * and <var>y</var> values in the range <code>[-PI/2..PI/2]</code>. It will be checked
425         * by the caller, so this method doesn't need to performs this check.
426         *
427         * @param x     The longitude of the coordinate, in metres.
428         * @param y     The  latitude of the coordinate, in metres.
429         * @param ptDst the specified coordinate point that stores the
430         *              result of transforming <code>ptSrc</code>, or
431         *              <code>null</code>. Ordinates will be in <strong>radians</strong>.
432         * @return the coordinate point after transforming <code>ptSrc</code>
433         *         and stroring the result in <code>ptDst</code>.
434         * @throws TransformException if the point can't be transformed.
435         */
436        protected abstract Point2D inverseTransform( double x, double y, final Point2D ptDst )
437                                throws TransformException;
438    
439        /**
440         * Inverse transforms the specified <code>ptSrc</code>
441         * and stores the result in <code>ptDst</code>.
442         *
443         * @param ptSrc the specified coordinate point to be transformed.
444         *              Ordinates must be in metres.
445         * @param ptDst the specified coordinate point that stores the
446         *              result of transforming <code>ptSrc</code>, or
447         *              <code>null</code>. Ordinates will be in degrees.
448         * @return the coordinate point after transforming <code>ptSrc</code>
449         *         and stroring the result in <code>ptDst</code>.
450         * @throws TransformException if the point can't be transformed.
451         */
452        public final Point2D inverseTransform( final Point2D ptSrc, Point2D ptDst )
453                                throws TransformException {
454            final double x0 = ptSrc.getX();
455            final double y0 = ptDst.getY();
456            ptDst = inverseTransform( x0, y0, ptDst );
457            final double x = Math.toDegrees( ptDst.getX() );
458            final double y = Math.toDegrees( ptDst.getY() );
459            ptDst.setLocation( x, y );
460            if ( !( x >= Longitude.MIN_VALUE && x <= Longitude.MAX_VALUE ) ) {
461                throw new TransformException(
462                                              Resources.format(
463                                                                ResourceKeys.ERROR_LONGITUDE_OUT_OF_RANGE_$1,
464                                                                new Longitude( x ) ) );
465            }
466            if ( !( y >= Latitude.MIN_VALUE && y <= Latitude.MAX_VALUE ) ) {
467                throw new TransformException(
468                                              Resources.format(
469                                                                ResourceKeys.ERROR_LATITUDE_OUT_OF_RANGE_$1,
470                                                                new Latitude( y ) ) );
471            }
472            return ptDst;
473        }
474    
475        /**
476         * Inverse transforms a list of coordinate point ordinal values.
477         * Ordinates must be (<var>x</var>,<var>y</var>) pairs in metres.
478         *
479         * @throws TransformException if a point can't be transformed. This method try
480         *         to transform every points even if some of them can't be transformed.
481         *         Non-transformable points will have value {@link Double#NaN}. If more
482         *         than one point can't be transformed, then this exception may be about
483         *         an arbitrary point.
484         */
485        public final void inverseTransform( final double[] src, int srcOffset, final double[] dest,
486                                           int dstOffset, int numPts )
487                                throws TransformException {
488            /*
489             * V�rifie s'il faudra parcourir le tableau en sens inverse.
490             * Ce sera le cas si les tableaux source et destination se
491             * chevauchent et que la destination est apr�s la source.
492             */
493            final boolean reverse = ( src == dest && srcOffset < dstOffset && srcOffset
494                                                                              + ( numPts << 1 ) > dstOffset );
495            if ( reverse ) {
496                srcOffset += src.length * numPts;
497                dstOffset += src.length * numPts;
498            }
499            final Point2D.Double point = new Point2D.Double();
500            TransformException firstException = null;
501            while ( --numPts >= 0 ) {
502                try {
503                    point.x = src[srcOffset++];
504                    point.y = src[srcOffset++];
505                    inverseTransform( point, point );
506                    dest[dstOffset++] = point.x;
507                    dest[dstOffset++] = point.y;
508                } catch ( TransformException exception ) {
509                    dest[dstOffset++] = Double.NaN;
510                    dest[dstOffset++] = Double.NaN;
511                    if ( firstException == null ) {
512                        firstException = exception;
513                    }
514                }
515                if ( reverse ) {
516                    srcOffset -= 4;
517                    dstOffset -= 4;
518                }
519            }
520            if ( firstException != null )
521                throw firstException;
522        }
523    
524        /**
525         * Inverse transforms a list of coordinate point ordinal values.
526         * Ordinates must be (<var>x</var>,<var>y</var>) pairs in metres.
527         *
528         * @throws TransformException if a point can't be transformed. This method try
529         *         to transform every points even if some of them can't be transformed.
530         *         Non-transformable points will have value {@link Float#NaN}. If more
531         *         than one point can't be transformed, then this exception may be about
532         *         an arbitrary point.
533         */
534        public final void inverseTransform( final float[] src, int srcOffset, final float[] dest,
535                                           int dstOffset, int numPts )
536                                throws TransformException {
537            final boolean reverse = ( src == dest && srcOffset < dstOffset && srcOffset
538                                                                              + ( numPts << 1 ) > dstOffset );
539            if ( reverse ) {
540                srcOffset += 2 * numPts;
541                dstOffset += 2 * numPts;
542            }
543            final Point2D.Double point = new Point2D.Double();
544            TransformException firstException = null;
545            while ( --numPts >= 0 ) {
546                try {
547                    point.x = src[srcOffset++];
548                    point.y = src[srcOffset++];
549                    inverseTransform( point, point );
550                    dest[dstOffset++] = (float) point.x;
551                    dest[dstOffset++] = (float) point.y;
552                } catch ( TransformException exception ) {
553                    dest[dstOffset++] = Float.NaN;
554                    dest[dstOffset++] = Float.NaN;
555                    if ( firstException == null ) {
556                        firstException = exception;
557                    }
558                }
559                if ( reverse ) {
560                    srcOffset -= 4;
561                    dstOffset -= 4;
562                }
563            }
564            if ( firstException != null )
565                throw firstException;
566        }
567    
568        //////////////////////////////////////////////////////////////////////
569        ////                                                              ////
570        ////             INTERNAL COMPUTATIONS FOR SUBCLASSES             ////
571        ////                                                              ////
572        //////////////////////////////////////////////////////////////////////
573    
574        /**
575         * Iteratively solve equation (7-9) from Snyder.
576         */
577        final double cphi2( final double ts )
578                                throws TransformException {
579            final double eccnth = 0.5 * e;
580            double phi = ( Math.PI / 2 ) - 2.0 * Math.atan( ts );
581            for ( int i = 0; i < 16; i++ ) {
582                final double con = e * Math.sin( phi );
583                final double dphi = ( Math.PI / 2 ) - 2.0
584                                    * Math.atan( ts * Math.pow( ( 1 - con ) / ( 1 + con ), eccnth ) )
585                                    - phi;
586                phi += dphi;
587                if ( Math.abs( dphi ) <= TOL )
588                    return phi;
589            }
590            throw new TransformException( Resources.format( ResourceKeys.ERROR_NO_CONVERGENCE ) );
591        }
592    
593        /**
594         * Compute function <code>f(s,c,es) = c/sqrt(1 - s�*es)</code>
595         * needed for the true scale latitude (Snyder, p. 47), where
596         * <var>s</var> and <var>c</var> are the sine and cosine of
597         * the true scale latitude, and {@link #es} the eccentricity
598         * squared.
599         */
600        final double msfn( final double s, final double c ) {
601            return c / Math.sqrt( 1.0 - s * s * es );
602        }
603    
604        /**
605         * Compute function (15-9) from Snyder
606         * equivalent to negative of function (7-7).
607         */
608        final double tsfn( final double phi, double sinphi ) {
609            sinphi *= e;
610            /*
611             * NOTE: change sign to get the equivalent of Snyder (7-7).
612             */
613            return Math.tan( 0.5 * ( ( Math.PI / 2d ) - phi ) )
614                   / Math.pow( ( 1 - sinphi ) / ( 1 + sinphi ), 0.5 * e );
615        }
616    
617        //////////////////////////////////////////////////////////////////////
618        ////                                                              ////
619        ////                        MISCELLANEOUS                         ////
620        ////                                                              ////
621        //////////////////////////////////////////////////////////////////////
622    
623        /**
624         * Returns the inverse of this map projection.
625         */
626        public final synchronized MathTransform inverse() {
627            if ( inverse == null )
628                inverse = new Inverse();
629            return inverse;
630        }
631    
632        /**
633         * Returns <code>false</code> since map
634         * projections are not identity transforms.
635         */
636        public final boolean isIdentity() {
637            return false;
638        }
639    
640        /**
641         * Returns a hash value for this map projection.
642         */
643        public int hashCode() {
644            long code = Double.doubleToLongBits( a );
645            code = code * 37 + Double.doubleToLongBits( b );
646            code = code * 37 + Double.doubleToLongBits( centralMeridian );
647            code = code * 37 + Double.doubleToLongBits( centralLatitude );
648            return (int) code ^ (int) ( code >>> 32 );
649        }
650    
651        /**
652         * Compares the specified object with
653         * this map projection for equality.
654         */
655        public boolean equals( final Object object ) {
656            // Do not check 'object==this' here, since this
657            // optimization is usually done in subclasses.
658            if ( super.equals( object ) ) {
659                final MapProjection that = (MapProjection) object;
660                return Double.doubleToLongBits( this.a ) == Double.doubleToLongBits( that.a )
661                       && Double.doubleToLongBits( this.b ) == Double.doubleToLongBits( that.b )
662                       && Double.doubleToLongBits( this.centralMeridian ) == Double.doubleToLongBits( that.centralMeridian )
663                       && Double.doubleToLongBits( this.centralLatitude ) == Double.doubleToLongBits( that.centralLatitude );
664            }
665            return false;
666        }
667    
668        /**
669         * Retourne une cha�ne de caract�res repr�sentant cette projection cartographique.
670         * Cette cha�ne de caract�res contiendra entre autres le nom de la projection, les
671         * coordonn�es du centre et celles de l'origine.
672         */
673        public final String toString() {
674            final StringBuffer buffer = paramMT( classification );
675            toString( buffer );
676            buffer.append( ']' );
677            return buffer.toString();
678        }
679    
680        /**
681         * Impl�mentation de la partie entre crochets
682         * de la cha�ne retourn�e par {@link #toString()}.
683         */
684        void toString( final StringBuffer buffer ) {
685            addParameter( buffer, "semi_major", a );
686            addParameter( buffer, "semi_minor", b );
687            addParameter( buffer, "central_meridian", Math.toDegrees( centralMeridian ) );
688            addParameter( buffer, "latitude_of_origin", Math.toDegrees( centralLatitude ) );
689        }
690    
691        /**
692         * Inverse of a map projection.
693         *
694         * @version 1.0
695         * @author Martin Desruisseaux
696         */
697        private final class Inverse extends AbstractMathTransform.Inverse implements MathTransform2D {
698            public Inverse() {
699                MapProjection.this.super();
700            }
701    
702            public Point2D transform( final Point2D source, final Point2D dest )
703                                    throws TransformException {
704                return MapProjection.this.inverseTransform( source, dest );
705            }
706    
707            public void transform( final double[] source, final int srcOffset, final double[] dest,
708                                  final int dstOffset, final int length )
709                                    throws TransformException {
710                MapProjection.this.inverseTransform( source, srcOffset, dest, dstOffset, length );
711            }
712    
713            public void transform( final float[] source, final int srcOffset, final float[] dest,
714                                  final int dstOffset, final int length )
715                                    throws TransformException {
716                MapProjection.this.inverseTransform( source, srcOffset, dest, dstOffset, length );
717            }
718    
719            public Shape createTransformedShape( final Shape shape )
720                                    throws TransformException {
721                return this.createTransformedShape( shape, null, null, Geometry.HORIZONTAL );
722            }
723        }
724    
725        /**
726         * Informations about a {@link MapProjection}.
727         *
728         * @version 1.0
729         * @author Martin Desruisseaux
730         */
731        static abstract class Provider extends MathTransformProvider {
732            /**
733             * Construct a new provider.
734             *
735             * @param classname The classification name.
736             * @param nameKey Resources key for a human readable name.
737             *        This is used for {@link #getName} implementation.
738             */
739            protected Provider( final String classname, final int nameKey ) {
740                super( classname, nameKey, DEFAULT_PROJECTION_DESCRIPTOR );
741            }
742    
743            /**
744             * Create a new map projection for a parameter list.
745             */
746            public final MathTransform create( final ParameterList parameters ) {
747                return (MathTransform) create( new Projection( "Generated", getClassName(), parameters ) );
748            }
749    
750            /**
751             * Create a new map projection.  NOTE: The returns type should
752             * be {@link MathTransform}, but as of JDK 1.4-beta3, it force
753             * class loading for all projection classes (MercatorProjection,
754             * etc.) before than necessary. Changing the returns type to
755             * Object is a trick to avoid too early class loading...
756             */
757            protected abstract Object create( final Projection parameters );
758        }
759    }