001    //$HeadURL: $
002    /*----------------    FILE HEADER  ------------------------------------------
003     This file is part of deegree.
004     Copyright (C) 2001-2008 by:
005     Department of Geography, University of Bonn
006     http://www.giub.uni-bonn.de/deegree/
007     lat/lon GmbH
008     http://www.lat-lon.de
009    
010     This library is free software; you can redistribute it and/or
011     modify it under the terms of the GNU Lesser General Public
012     License as published by the Free Software Foundation; either
013     version 2.1 of the License, or (at your option) any later version.
014     This library is distributed in the hope that it will be useful,
015     but WITHOUT ANY WARRANTY; without even the implied warranty of
016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     Lesser General Public License for more details.
018     You should have received a copy of the GNU Lesser General Public
019     License along with this library; if not, write to the Free Software
020     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021     Contact:
022    
023     Andreas Poth
024     lat/lon GmbH
025     Aennchenstr. 19
026     53177 Bonn
027     Germany
028     E-Mail: poth@lat-lon.de
029    
030     Prof. Dr. Klaus Greve
031     Department of Geography
032     University of Bonn
033     Meckenheimer Allee 166
034     53115 Bonn
035     Germany
036     E-Mail: greve@giub.uni-bonn.de
037     ---------------------------------------------------------------------------*/
038    
039    package org.deegree.crs.projections.conic;
040    
041    
042    import javax.vecmath.Point2d;
043    
044    import org.deegree.crs.components.Unit;
045    import org.deegree.crs.coordinatesystems.GeographicCRS;
046    import org.deegree.crs.projections.ProjectionUtils;
047    
048    /**
049     * The <code>LambertConformalConic</code> projection has following properties
050     * <q>(Snyder p. 104)</q>
051     * <ul>
052     * <li>Conic</li>
053     * <li>Conformal</li>
054     * <li>Parallels are unequally spaced arcs of concentric circles, more closely spaced near the center of the map</li>
055     * <li>Meridians are equally spaced radii of the same circles, thereby cutting paralles at right angles.</li>
056     * <li>Scale is true along two standard parallels, normally or along just one.</li>
057     * <li>The Pole in the same hemisphere as the standard parallels is a point; other pole is at infinity</li>
058     * <li>Used for maps of countries and regions with predominant east-west expanse.</li>
059     * <li>Presented by Lambert in 1772.</li>
060     * </ul>
061     * <p>
062     * <q>from: http://lists.maptools.org/pipermail/proj/2003-January/000592.html </q>
063     * For east-west regions, the Lambert Conformal Conic is slightly better than the Transverse Mercator because of the
064     * ability to go farther in an east-west direction and still be able to have "round-trip" transformation accuracy.
065     * Geodetically speaking, it is NOT as good as the transverse Mercator.
066     * </p>
067     * <p>
068     * It is known to be used by following epsg transformations:
069     * <ul>
070     * <li>EPSG:3034</li>
071     * </ul>
072     * </p>
073     * 
074     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
075     * 
076     * @author last edited by: $Author:$
077     * 
078     * @version $Revision:$, $Date:$
079     * 
080     */
081    
082    public class LambertConformalConic extends ConicProjection {
083    
084        // Will contain snyder's variable 'n' from fromula (15-3) for the spherical projection or (15-8) for the ellipsoidal
085        // projection.
086        private double n;
087    
088        private double rho0;
089    
090        // Will contain snyder's variable 'F' from fromula (15-2) for the spherical projection or (15-10) for the
091        // ellipsoidal projection.
092        private double largeF;
093    
094        // used for the calcualtion of phi (in the inverse projection with an ellipsoid) by applying the pre calculated
095        // values to the series of Snyder (p.15 3-5), thus avoiding iteration.
096        private double[] preCalcedPhiSeries;
097        
098        /**
099         * 
100         * @param firstParallelLatitude
101         *            the latitiude (in radians) of the first parallel. (Snyder phi_1).
102         * @param secondParallelLatitude
103         *            the latitiude (in radians) of the second parallel. (Snyder phi_2).
104         * @param geographicCRS
105         * @param falseNorthing
106         * @param falseEasting
107         * @param naturalOrigin
108         * @param units
109         * @param scale
110         * @param identifiers
111         * @param names
112         * @param versions
113         * @param descriptions
114         * @param areasOfUse
115         */
116        public LambertConformalConic( double firstParallelLatitude, double secondParallelLatitude,
117                                                GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
118                                                Point2d naturalOrigin, Unit units, double scale, String[] identifiers,
119                                                String[] names, String[] versions, String[] descriptions, String[] areasOfUse ) {
120            super( firstParallelLatitude,
121                   secondParallelLatitude,
122                   geographicCRS,
123                   falseNorthing,
124                   falseEasting,
125                   naturalOrigin,
126                   units,
127                   scale,
128                   true, //conformal
129                   false, //not equalArea
130                   identifiers,
131                   names,
132                   versions,
133                   descriptions,
134                   areasOfUse );
135    
136            double cosphi, sinphi;
137            boolean secant;
138    
139            // if ( Math.abs( getFirstParallelLatitude() + getSecondParallelLatitude() ) < )
140            // throw new ProjectionException();
141    
142            // If only one tangential parallel is used, the firstparallelLatitude will also have the same value as the
143            // projectionLatitude, in this case the constant 'n' from Snyder will have the value sin(phi).
144            n = sinphi = Math.sin( getFirstParallelLatitude() );
145            cosphi = Math.cos( getFirstParallelLatitude() );
146            secant = Math.abs( getFirstParallelLatitude() - getSecondParallelLatitude() ) >= ProjectionUtils.EPS10;
147            if ( isSpherical() ) {
148                if ( secant ) {
149                    // two parallels are used, calc snyder (p.107 15-3), else n will contain sin(firstParallelLatitude),
150                    // according to Snyder (p.107 just before 15-4).
151                    n = Math.log( cosphi / Math.cos( getSecondParallelLatitude() ) ) / Math.log( Math.tan( ProjectionUtils.QUARTERPI + ( .5 * getSecondParallelLatitude() ) ) / Math.tan( ProjectionUtils.QUARTERPI + ( .5 * getFirstParallelLatitude() ) ) );
152                }
153                // Snyder (p.107 15-2)
154                largeF = ( cosphi * Math.pow( Math.tan( ProjectionUtils.QUARTERPI + ( .5 * getFirstParallelLatitude() ) ), n ) ) / n;
155    
156                // Snyder (p.106 15-1a) pay attention to the '-n' power term...
157                rho0 = ( Math.abs( Math.abs( getProjectionLatitude() ) - ProjectionUtils.HALFPI ) < ProjectionUtils.EPS10 ) ? 0.
158                                                                                                             : largeF * Math.pow( Math.tan( ProjectionUtils.QUARTERPI + ( .5 * getProjectionLatitude() ) ),
159                                                                                                                                  -n );
160            } else {
161                preCalcedPhiSeries = ProjectionUtils.preCalcedThetaSeries( getSquaredEccentricity() );
162                // Calc
163                double m1 = ProjectionUtils.calcMFromSnyder( sinphi, cosphi, getSquaredEccentricity() );
164                double t1 = ProjectionUtils.tanHalfCoLatitude( getFirstParallelLatitude(), sinphi, getEccentricity() );
165                if ( secant ) {
166                    sinphi = Math.sin( getSecondParallelLatitude() );
167                    cosphi = Math.cos( getSecondParallelLatitude() );
168                    // Basic math, the log ( x/ y ) = log(x) - log(y) if the base is the same.
169                    n = Math.log( m1 / ProjectionUtils.calcMFromSnyder( sinphi, cosphi, getSquaredEccentricity() ) );
170                    n /= Math.log( t1 / ProjectionUtils.tanHalfCoLatitude( getSecondParallelLatitude(), sinphi, getEccentricity() ) );
171                }
172                // Snyder (p.108 15-10), n will contain sin(getFirstLatitudePhi()) if only a tangential cone is used.
173                largeF = ( m1 * Math.pow( t1, -n ) ) / n;
174    
175                // Snyder (p.108 15-7). or 0 if the projectionlatitude is on one of the poles e.g.± pi*0.5.
176                rho0 = ( Math.abs( Math.abs( getProjectionLatitude() ) - ProjectionUtils.HALFPI ) < ProjectionUtils.EPS10 ) ? 0.
177                                                                                                             : largeF * Math.pow( ProjectionUtils.tanHalfCoLatitude( getProjectionLatitude(),
178                                                                                                                                                              getSinphi0(),
179                                                                                                                                                              getEccentricity() ),
180                                                                                                                                  n );
181    
182            }
183        }
184    
185        /**
186         * 
187         * @param firstParallelLatitude
188         *            the latitiude (in radians) of the first parallel. (Snyder phi_1).
189         * @param secondParallelLatitude
190         *            the latitiude (in radians) of the second parallel. (Snyder phi_2).
191         * @param geographicCRS
192         * @param falseNorthing
193         * @param falseEasting
194         * @param naturalOrigin
195         * @param units
196         * @param scale
197         * @param identifier
198         * @param name
199         * @param version
200         * @param description
201         * @param areaOfUse
202         */
203        public LambertConformalConic( double firstParallelLatitude, double secondParallelLatitude,
204                                                GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
205                                                Point2d naturalOrigin, Unit units, double scale, String identifier,
206                                                String name, String version, String description, String areaOfUse ) {
207            this( firstParallelLatitude,
208                   secondParallelLatitude,
209                   geographicCRS,
210                   falseNorthing,
211                   falseEasting,
212                   naturalOrigin,
213                   units,
214                   scale,
215                   new String[]{identifier},
216                   new String[]{name},
217                   new String[]{version},
218                   new String[]{description},
219                   new String[]{areaOfUse} );
220        }
221        
222        /**
223         * Creates a Lambert Conformal projection with a intersecting cone at the given parallel latitudes.
224         * 
225         * @param firstParallelLatitude
226         *            the latitiude (in radians) of the first parallel. (Snyder phi_1).
227         * @param secondParallelLatitude
228         *            the latitiude (in radians) of the second parallel. (Snyder phi_2).
229         * @param geographicCRS
230         * @param falseNorthing
231         * @param falseEasting
232         * @param naturalOrigin
233         * @param units
234         * @param scale
235         * @param identifiers
236         */
237        public LambertConformalConic( double firstParallelLatitude, double secondParallelLatitude,
238                                                GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
239                                                Point2d naturalOrigin, Unit units, double scale, String[] identifiers ) {
240            this( firstParallelLatitude,
241                  secondParallelLatitude,
242                  geographicCRS,
243                  falseNorthing,
244                  falseEasting,
245                  naturalOrigin,
246                  units,
247                  scale,
248                  identifiers, null,null,null,null );
249        }
250    
251        /**
252         * Creates a Lambert Conformal projection with a intersecting cone at the given parallel latitudes.
253         * 
254         * @param firstParallelLatitude
255         *            the latitiude (in radians) of the first parallel. (Snyder phi_1).
256         * @param secondParallelLatitude
257         *            the latitiude (in radians) of the second parallel. (Snyder phi_2).
258         * @param geographicCRS
259         * @param falseNorthing
260         * @param falseEasting
261         * @param naturalOrigin
262         * @param units
263         * @param scale
264         * @param identifier
265         */
266        public LambertConformalConic( double firstParallelLatitude, double secondParallelLatitude,
267                                                GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
268                                                Point2d naturalOrigin, Unit units, double scale, String identifier ) {
269            this( firstParallelLatitude,
270                  secondParallelLatitude,
271                  geographicCRS,
272                  falseNorthing,
273                  falseEasting,
274                  naturalOrigin,
275                  units,
276                  scale,
277                  new String[]{identifier} );
278        }
279        
280        /**
281         * Creates a Lambert Conformal projection with a tangential cone at the naturalOrigin.y's latitude.
282         * 
283         * @param geographicCRS
284         * @param falseNorthing
285         * @param falseEasting
286         * @param naturalOrigin
287         * @param units
288         * @param scale
289         * @param identifiers
290         * @param name
291         */
292        public LambertConformalConic( GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
293                                                Point2d naturalOrigin, Unit units, double scale, String[] identifiers  ) {
294            this( Double.NaN,
295                  Double.NaN,
296                  geographicCRS,
297                  falseNorthing,
298                  falseEasting,
299                  naturalOrigin,
300                  units,
301                  scale,
302                  identifiers );
303        }
304    
305        /**
306         * Creates a Lambert Conformal projection with a tangential cone at the naturalOrigin.y's latitude.
307         * 
308         * @param geographicCRS
309         * @param falseNorthing
310         * @param falseEasting
311         * @param naturalOrigin
312         * @param units
313         * @param scale
314         * @param identifier
315         * @param name
316         */
317        public LambertConformalConic( GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
318                                                Point2d naturalOrigin, Unit units, double scale, String identifier  ) {
319            this( Double.NaN,
320                  Double.NaN,
321                  geographicCRS,
322                  falseNorthing,
323                  falseEasting,
324                  naturalOrigin,
325                  units,
326                  scale,
327                  identifier );
328        }
329        
330        /**
331         * Creates a Lambert Conformal projection with a intersecting cone at the given parallel latitudes. and a scale of
332         * 1.
333         * 
334         * @param firstParallelLatitude
335         *            the latitiude (in radians) of the first parallel. (Snyder phi_1).
336         * @param secondParallelLatitude
337         *            the latitiude (in radians) of the second parallel. (Snyder phi_2).
338         * @param geographicCRS
339         * @param falseNorthing
340         * @param falseEasting
341         * @param naturalOrigin
342         * @param units
343         * @param identifiers
344         * @param name
345         */
346        public LambertConformalConic( double firstParallelLatitude, double secondParallelLatitude,
347                                                GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
348                                                Point2d naturalOrigin, Unit units, String[] identifiers ) {
349            this( firstParallelLatitude,
350                  secondParallelLatitude,
351                  geographicCRS,
352                  falseNorthing,
353                  falseEasting,
354                  naturalOrigin,
355                  units,
356                  1,
357                  identifiers );
358    
359        }
360    
361        /**
362         * Creates a Lambert Conformal projection with a intersecting cone at the given parallel latitudes. and a scale of
363         * 1.
364         * 
365         * @param firstParallelLatitude
366         *            the latitiude (in radians) of the first parallel. (Snyder phi_1).
367         * @param secondParallelLatitude
368         *            the latitiude (in radians) of the second parallel. (Snyder phi_2).
369         * @param geographicCRS
370         * @param falseNorthing
371         * @param falseEasting
372         * @param naturalOrigin
373         * @param units
374         * @param identifier
375         * @param name
376         */
377        public LambertConformalConic( double firstParallelLatitude, double secondParallelLatitude,
378                                                GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
379                                                Point2d naturalOrigin, Unit units, String identifier ) {
380            this( firstParallelLatitude,
381                  secondParallelLatitude,
382                  geographicCRS,
383                  falseNorthing,
384                  falseEasting,
385                  naturalOrigin,
386                  units,
387                  1,
388                  identifier);
389    
390        }
391        
392        /**
393         * Creates a Lambert Conformal projection with a tangential cone at the naturalOrigin.y's latitude. And a scale of
394         * 1.
395         * 
396         * @param geographicCRS
397         * @param falseNorthing
398         * @param falseEasting
399         * @param naturalOrigin
400         * @param units
401         * @param identifiers
402         * @param name
403         */
404        public LambertConformalConic( GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
405                                                Point2d naturalOrigin, Unit units, String[] identifiers) {
406            this( Double.NaN,
407                  Double.NaN,
408                  geographicCRS,
409                  falseNorthing,
410                  falseEasting,
411                  naturalOrigin,
412                  units,
413                  1,
414                  identifiers );
415    
416        }
417    
418        /**
419         * Creates a Lambert Conformal projection with a tangential cone at the naturalOrigin.y's latitude. And a scale of
420         * 1.
421         * 
422         * @param geographicCRS
423         * @param falseNorthing
424         * @param falseEasting
425         * @param naturalOrigin
426         * @param units
427         * @param identifier
428         * @param name
429         */
430        public LambertConformalConic( GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
431                                                Point2d naturalOrigin, Unit units, String identifier ) {
432            this( Double.NaN,
433                  Double.NaN,
434                  geographicCRS,
435                  falseNorthing,
436                  falseEasting,
437                  naturalOrigin,
438                  units,
439                  1,
440                  identifier );
441    
442        }
443    
444        /**
445         * 
446         * @see org.deegree.crs.projections.Projection#doInverseProjection(double, double)
447         */
448        @Override
449        public Point2d doInverseProjection( double x, double y ) {
450            Point2d out = new Point2d( 0, 0 );
451            x -= getFalseEasting();
452            y -= getFalseNorthing();
453            // why divide by the scale????
454            x /= getScaleFactor();
455            y = rho0 - (y / getScaleFactor());
456            double rho = ProjectionUtils.length( x, y );
457            if ( rho > ProjectionUtils.EPS11 ) {
458                if ( n < 0.0 ) {
459                    // if using the atan2 the values must be inverted.
460                    rho = -rho;
461                    x = -x;
462                    y = -y;
463                }
464                if ( isSpherical() ) {
465                    // Snyder (p.107 15-5).
466                    out.y = ( 2.0 * Math.atan( Math.pow( largeF / rho, 1.0 / n ) ) ) - ProjectionUtils.HALFPI;
467                } else {
468                    // out.y = MapUtils.phi2( Math.pow( rho / largeF, 1.0 / n ), getEccentricity() );
469                    double t = Math.pow( rho / largeF, 1.0 / n );
470                    double chi = ProjectionUtils.HALFPI - ( 2 * Math.atan( t ) );
471                    out.y = ProjectionUtils.calcPhiFromConformalLatitude( chi, preCalcedPhiSeries );
472                }
473                // Combine Snyder (P.107/109 14-9) with (p.107/109 14-11), please pay attention to the remark of snyder on
474                // the atan2 at p.107!!!
475                out.x = Math.atan2( x, y ) / n;
476            } else {
477                out.x = 0;
478                out.y = ( n > 0.0 ) ? ProjectionUtils.HALFPI : -ProjectionUtils.HALFPI;
479            }
480            out.x += getProjectionLongitude();
481            return out;
482        }
483    
484        /**
485         * 
486         * @see org.deegree.crs.projections.Projection#doProjection(double, double)
487         */
488        @Override
489        public Point2d doProjection( double lambda, double phi ) {
490            lambda -= getProjectionLongitude();
491            double rho = 0;
492            if ( Math.abs( Math.abs( phi ) - ProjectionUtils.HALFPI ) > ProjectionUtils.EPS10 ) {
493                // For spherical see Snyder (p.106 15-1) for ellipitical Snyder (p.108 15-7), pay attention to the '-n'
494                rho = largeF * ( isSpherical() ? Math.pow( Math.tan( ProjectionUtils.QUARTERPI + ( .5 * phi ) ), -n )
495                                              : Math.pow( ProjectionUtils.tanHalfCoLatitude( phi,
496                                                                                      Math.sin( phi ),
497                                                                                      getEccentricity() ), n ) );
498            }
499            // calc theta Snyder (p.106/108 14-4) multiply lambda with the 'n' constant.
500            double theta = lambda * n;
501            
502            Point2d out = new Point2d( 0, 0 );
503            out.x = getScaleFactor() * ( rho * Math.sin( theta ) ) + getFalseEasting();
504            out.y = getScaleFactor() * ( rho0 - ( rho * Math.cos( theta ) ) ) + getFalseNorthing();
505            return out;
506        }
507    
508        @Override
509        public String getDeegreeSpecificName() {
510            return "lambertConformalConic";
511        }
512    
513    }