001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/ct/LambertConformalProjection.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    // OpenGIS (SEAS) dependencies
054    import java.awt.geom.Point2D;
055    
056    import org.deegree.model.csct.cs.Projection;
057    import org.deegree.model.csct.pt.Latitude;
058    import org.deegree.model.csct.resources.css.ResourceKeys;
059    import org.deegree.model.csct.resources.css.Resources;
060    
061    /**
062     * Projection conique conforme de Lambert. Les aires et les formes sont d�form�es
063     * � mesure que l'on s'�loigne de parall�les standards. Les angles sont vrais dans
064     * une r�gion limit�e. Cette projection est utilis�e pour les cartes de l'Am�rique
065     * du Nord. Elle utilise par d�faut une latitude centrale de 40�N.
066     * <br><br>
067     *
068     * R�f�rence: John P. Snyder (Map Projections - A Working Manual,
069     *            U.S. Geological Survey Professional Paper 1395, 1987)
070     *
071     * @version 1.0
072     * @author Andr� Gosselin
073     * @author Martin Desruisseaux
074     */
075    final class LambertConformalProjection extends ConicProjection {
076        /**
077         * Standards parallels in radians, for
078         * {@link #toString} implementation.
079         */
080        private final double phi1, phi2;
081    
082        /**
083         * Variables internes
084         * pour les calculs.
085         */
086        private final double n, F, rho0;
087    
088        /**
089         * Construct a new map projection from the suplied parameters.
090         *
091         * @param  parameters The parameter values in standard units.
092         * @throws MissingParameterException if a mandatory parameter is missing.
093         */
094        protected LambertConformalProjection( final Projection parameters )
095                                throws MissingParameterException {
096            //////////////////////////
097            //   Fetch parameters   //
098            //////////////////////////
099            super( parameters );
100            phi1 = latitudeToRadians( parameters.getValue( "standard_parallel1", 30 ), true );
101            phi2 = latitudeToRadians( parameters.getValue( "standard_parallel2", 45 ), true );
102    
103            //////////////////////////
104            //  Compute constants   //
105            //////////////////////////
106            if ( Math.abs( phi1 + phi2 ) < EPS ) {
107                throw new IllegalArgumentException(
108                                                    Resources.format(
109                                                                      ResourceKeys.ERROR_ANTIPODE_LATITUDES_$2,
110                                                                      new Latitude(
111                                                                                    Math.toDegrees( phi1 ) ),
112                                                                      new Latitude(
113                                                                                    Math.toDegrees( phi2 ) ) ) );
114            }
115            final double cosphi = Math.cos( phi1 );
116            final double sinphi = Math.sin( phi1 );
117            final boolean secant = Math.abs( phi1 - phi2 ) > EPS;
118            if ( isSpherical ) {
119                if ( secant ) {
120                    n = Math.log( cosphi / Math.cos( phi2 ) )
121                        / Math.log( Math.tan( ( Math.PI / 4 ) + 0.5 * phi2 )
122                                    / Math.tan( ( Math.PI / 4 ) + 0.5 * phi1 ) );
123                } else
124                    n = sinphi;
125                F = cosphi * Math.pow( Math.tan( ( Math.PI / 4 ) + 0.5 * phi1 ), n ) / n;
126                if ( Math.abs( Math.abs( centralLatitude ) - ( Math.PI / 2 ) ) >= EPS ) {
127                    rho0 = F * Math.pow( Math.tan( ( Math.PI / 4 ) + 0.5 * centralLatitude ), -n );
128                } else
129                    rho0 = 0.0;
130            } else {
131                final double m1 = msfn( sinphi, cosphi );
132                final double t1 = tsfn( phi1, sinphi );
133                if ( secant ) {
134                    final double sinphi2 = Math.sin( phi2 );
135                    final double m2 = msfn( sinphi2, Math.cos( phi2 ) );
136                    final double t2 = tsfn( phi2, sinphi2 );
137                    n = Math.log( m1 / m2 ) / Math.log( t1 / t2 );
138                } else
139                    n = sinphi;
140                F = m1 * Math.pow( t1, -n ) / n;
141                if ( Math.abs( Math.abs( centralLatitude ) - ( Math.PI / 2 ) ) >= EPS ) {
142                    rho0 = F * Math.pow( tsfn( centralLatitude, Math.sin( centralLatitude ) ), n );
143                } else
144                    rho0 = 0.0;
145            }
146        }
147    
148        /**
149         * Returns a human readable name localized for the specified locale.
150         */
151        public String getName() {
152            return Resources.getResources( null ).getString(
153                                                               ResourceKeys.LAMBERT_CONFORMAL_PROJECTION );
154        }
155    
156        /**
157         * Transforms the specified (<var>x</var>,<var>y</var>) coordinate
158         * and stores the result in <code>ptDst</code>.
159         */
160        protected Point2D transform( double x, double y, final Point2D ptDst )
161                                throws TransformException {
162            x -= centralMeridian;
163            double rho;
164            if ( Math.abs( Math.abs( y ) - ( Math.PI / 2 ) ) < EPS ) {
165                if ( y * n <= 0 ) {
166                    throw new TransformException( Resources.format( ResourceKeys.ERROR_POLE_PROJECTION_$1,
167                                                                    new Latitude( Math.toDegrees( y ) ) ) );
168                } 
169                rho = 0;
170            } else {
171                if ( isSpherical )
172                    rho = Math.pow( Math.tan( ( Math.PI / 4d ) + 0.5 * y ), -n );
173                else
174                    rho = Math.pow( tsfn( y, Math.sin( y ) ), n );
175                rho *= F;
176            }
177    
178            x = n * x;
179            y = a * ( rho0 - rho * Math.cos( x ) ) + false_northing;
180            x = a * ( rho * Math.sin( x ) ) + false_easting;
181    
182            if ( ptDst != null ) {
183                ptDst.setLocation( x, y );
184                return ptDst;
185            }
186            return new Point2D.Double( x, y );
187        }
188    
189        /**
190         * Transforms the specified (<var>x</var>,<var>y</var>) coordinate
191         * and stores the result in <code>ptDst</code>.
192         */
193        protected Point2D inverseTransform( double x, double y, final Point2D ptDst )
194                                throws TransformException {
195            x -= false_easting;
196            y -= false_northing;
197            x /= a;
198            y = rho0 - y / a;
199            double rho = Math.sqrt( x * x + y * y );
200            if ( rho > EPS ) {
201                if ( n < 0 ) {
202                    rho = -rho;
203                    x = -x;
204                    y = -y;
205                }
206                x = centralMeridian + Math.atan2( x, y ) / n;
207                if ( isSpherical ) {
208                    y = 2.0 * Math.atan( Math.pow( F / rho, 1.0 / n ) ) - ( Math.PI / 2 );
209                } else {
210                    y = cphi2( Math.pow( rho / F, 1.0 / n ) );
211                }
212            } else {
213                x = centralMeridian;
214                y = n < 0 ? -( Math.PI / 2 ) : ( Math.PI / 2 );
215            }
216            if ( ptDst != null ) {
217                ptDst.setLocation( x, y );
218                return ptDst;
219            }
220            return new Point2D.Double( x, y );
221        }
222    
223        /**
224         * Returns a hash value for this projection.
225         */
226        public int hashCode() {
227            final long code = Double.doubleToLongBits( F );
228            return ( (int) code ^ (int) ( code >>> 32 ) ) + 37 * super.hashCode();
229        }
230    
231        /**
232         * Compares the specified object with
233         * this map projection for equality.
234         */
235        public boolean equals( final Object object ) {
236            if ( object == this )
237                return true; // Slight optimization
238            if ( super.equals( object ) ) {
239                final LambertConformalProjection that = (LambertConformalProjection) object;
240                return Double.doubleToLongBits( this.n ) == Double.doubleToLongBits( that.n )
241                       && Double.doubleToLongBits( this.F ) == Double.doubleToLongBits( that.F )
242                       && Double.doubleToLongBits( this.rho0 ) == Double.doubleToLongBits( that.rho0 );
243            }
244            return false;
245        }
246    
247        /**
248         * Impl�mentation de la partie entre crochets
249         * de la cha�ne retourn�e par {@link #toString()}.
250         */
251        void toString( final StringBuffer buffer ) {
252            super.toString( buffer );
253            addParameter( buffer, "standard_parallel1", Math.toDegrees( phi1 ) );
254            addParameter( buffer, "standard_parallel2", Math.toDegrees( phi2 ) );
255        }
256    
257        /**
258         * Informations about a {@link LambertConformalProjection}.
259         *
260         * @version 1.0
261         * @author Martin Desruisseaux
262         */
263        static final class Provider extends MapProjection.Provider {
264            /**
265             * Construct a new provider.
266             */
267            public Provider() {
268                super( "Lambert_Conformal_Conic_2SP", ResourceKeys.LAMBERT_CONFORMAL_PROJECTION );
269                put( "standard_parallel1", 30.0, LATITUDE_RANGE );
270                put( "standard_parallel2", 45.0, LATITUDE_RANGE );
271            }
272    
273            /**
274             * Create a new map projection.
275             */
276            protected Object create( final Projection parameters ) {
277                return new LambertConformalProjection( parameters );
278            }
279        }
280    }