001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/crs/configuration/proj4/PROJ4CRSProvider.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005       Department of Geography, University of Bonn
006     and
007       lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035    ----------------------------------------------------------------------------*/
036    
037    package org.deegree.crs.configuration.proj4;
038    
039    import java.util.Calendar;
040    import java.util.GregorianCalendar;
041    import java.util.HashMap;
042    import java.util.LinkedList;
043    import java.util.List;
044    import java.util.Map;
045    import java.util.Properties;
046    import java.util.Set;
047    
048    import javax.vecmath.Point2d;
049    
050    import org.deegree.crs.Identifiable;
051    import org.deegree.crs.components.Axis;
052    import org.deegree.crs.components.Ellipsoid;
053    import org.deegree.crs.components.GeodeticDatum;
054    import org.deegree.crs.components.PrimeMeridian;
055    import org.deegree.crs.components.Unit;
056    import org.deegree.crs.configuration.AbstractCRSProvider;
057    import org.deegree.crs.coordinatesystems.CoordinateSystem;
058    import org.deegree.crs.coordinatesystems.GeographicCRS;
059    import org.deegree.crs.coordinatesystems.ProjectedCRS;
060    import org.deegree.crs.exceptions.CRSConfigurationException;
061    import org.deegree.crs.projections.Projection;
062    import org.deegree.crs.projections.azimuthal.LambertAzimuthalEqualArea;
063    import org.deegree.crs.projections.azimuthal.StereographicAlternative;
064    import org.deegree.crs.projections.azimuthal.StereographicAzimuthal;
065    import org.deegree.crs.projections.conic.LambertConformalConic;
066    import org.deegree.crs.projections.cylindric.TransverseMercator;
067    import org.deegree.crs.transformations.Transformation;
068    import org.deegree.crs.transformations.helmert.Helmert;
069    import org.deegree.framework.log.ILogger;
070    import org.deegree.framework.log.LoggerFactory;
071    import org.deegree.i18n.Messages;
072    
073    /**
074     * The <code>PROJ4CRSProvider</code> class is capable of parsing the nad/epsg file and use it as a backend for crs's.
075     * This class also adds following identifiers to the coordinatesystems.
076     * <ul>
077     * <li>http://www.opengis.net/gml/srs/epsg.xml#4326</li>
078     * <li>URN:OPENGIS:DEF:CRS:EPSG::</li>
079     * <li>URN:OGC:DEF:CRS:EPSG::</li>
080     * </ul>
081     *
082     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
083     *
084     * @author last edited by: $Author: mschneider $
085     *
086     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
087     *
088     */
089    
090    public class PROJ4CRSProvider extends AbstractCRSProvider<Map<String, String>> {
091    
092        private static ILogger LOG = LoggerFactory.getLogger( PROJ4CRSProvider.class );
093    
094        private static int ellipsCount = 0;
095    
096        private static int datumCount = 0;
097    
098        private static int geographicCRSCount = 0;
099    
100        private static int primeMeridianCount = 0;
101    
102        private final static String EPSG_PRE = "EPSG:";
103    
104        private final static String OPENGIS_URL = "HTTP://WWW.OPENGIS.NET/GML/SRS/EPSG.XML#";
105    
106        private final static String OPENGIS_URN = "URN:OPENGIS:DEF:CRS:EPSG::";
107    
108        private static final String OGC_URN = "URN:OGC:DEF:CRS:EPSG::";
109    
110        private Map<String, CoordinateSystem> coordinateSystems = new HashMap<String, CoordinateSystem>( 10000 );
111    
112        private String version = null;
113    
114        private String[] versions = null;
115    
116        private String areaOfUse = "Unknown";
117    
118        private String[] areasOfUse = new String[] { "Unknown" };
119    
120        /**
121         * Export constructor, sets the version to current date..
122         */
123        public PROJ4CRSProvider() {
124            super( new Properties(), null, null );
125            // dummy constructor for exporting only.
126            GregorianCalendar cal = (GregorianCalendar) Calendar.getInstance();
127            version = cal.get( Calendar.YEAR ) + "-" + ( cal.get( Calendar.MONTH ) + 1 ) + "-"
128                      + cal.get( Calendar.DAY_OF_MONTH ) + "T" + cal.get( Calendar.HOUR_OF_DAY ) + ":"
129                      + cal.get( Calendar.MINUTE );
130            versions = new String[] { version };
131        }
132    
133        /**
134         * Opens a reader on the file and parses all parameters with id, without instantiating any CoordinateSystems.
135         *
136         * @param properties
137         *            containing a crs.configuration property referencing a file location.
138         */
139        public PROJ4CRSProvider( Properties properties ) {
140            super( properties, ProjFileResource.class, null );
141            if ( getResolver() == null ) {
142                setResolver( new ProjFileResource( this, properties ) );
143            }
144        }
145    
146        @Override
147        protected ProjFileResource getResolver() {
148            return (ProjFileResource) super.getResolver();
149        }
150    
151        public List<CoordinateSystem> getAvailableCRSs()
152                                throws CRSConfigurationException {
153            Set<String> keys = getResolver().getAvailableIDs();
154            if ( LOG.isDebug() ) {
155                LOG.logDebug( "Found following keys: " + keys );
156            }
157            List<CoordinateSystem> allSystems = new LinkedList<CoordinateSystem>();
158            for ( String key : keys ) {
159                try {
160                    CoordinateSystem result = getCRSByID( key );
161                    if ( result != null ) {
162                        allSystems.add( result );
163                    }
164                } catch ( CRSConfigurationException e ) {
165                    LOG.logInfo( Messages.getMessage( "CRS_CONFIG_PROJ4_NOT_ADDING_CRS", key, e.getMessage() ) );
166                }
167            }
168            // get all already created crs's
169            keys = coordinateSystems.keySet();
170            for ( String key : keys ) {
171                CoordinateSystem result = coordinateSystems.get( key );
172                if ( result != null ) {
173                    allSystems.add( result );
174                }
175            }
176            return allSystems;
177        }
178    
179        public boolean canExport() {
180            return false;
181        }
182    
183        public void export( StringBuilder sb, List<CoordinateSystem> crsToExport ) {
184            throw new UnsupportedOperationException( "Exporting to PROJ4 configuration is not suppored yet." );
185    
186        }
187    
188        /**
189         * Creates a projected crs from given params. Because proj4 doesn't define some axisorder xy is always assumed.
190         *
191         * @param projectionName
192         *            to give the geographic crs (+e.g. proj=tmerc) or <code>null</code>
193         * @param params
194         *            containing datum info.
195         * @return a geographic crs
196         * @throws CRSConfigurationException
197         *             if an exception occurs while creating the datum.
198         */
199        private ProjectedCRS createProjectedCRS( String projectionName, Map<String, String> params )
200                                throws CRSConfigurationException {
201            String[] names = new String[] { params.remove( "comment" ) };
202    
203            String[] descriptions = null;
204            String id = params.get( "identifier" );
205            String[] ids = getPredefinedIDs( id );
206            String geoID = "GEO_CRS_" + geographicCRSCount;
207            if ( "3068".equals( id ) || "31466".equals( id ) || "31467".equals( id ) || "31468".equals( id )
208                 || "31469".equals( id ) ) {
209                geoID = EPSG_PRE + "4314";
210            } else {
211                geographicCRSCount++;
212            }
213            GeographicCRS underLyingCRS = createGeographicCRS( geoID, id, params );
214            Projection projection = createProjection( projectionName, underLyingCRS, params );
215    
216            return new ProjectedCRS( projection, new Axis[] { new Axis( projection.getUnits(), "x", Axis.AO_EAST ),
217                                                             new Axis( projection.getUnits(), "y", Axis.AO_NORTH ) }, ids,
218                                     names, versions, descriptions, areasOfUse );
219        }
220    
221        /**
222         * Creates a geographic crs with given identifier, if the identifier is empty or <code>null</code> the
223         * params.getIdentifier will be used. Because proj4 doesn't define some axisorder xy is always assumed.
224         *
225         * @param identifier
226         *            to give the geographic crs (+proj=longlat) or <code>null</code>
227         * @param params
228         *            containing datum info.
229         * @param projectedID
230         *            of the projected crs (only if identifier is not null).
231         * @return a geographic crs
232         * @throws CRSConfigurationException
233         *             if an exception occurs while creating the datum.
234         */
235        private GeographicCRS createGeographicCRS( String identifier, String projectedID, Map<String, String> params )
236                                throws CRSConfigurationException {
237            String name = "Proj4 defined Geographic CRS";
238            String tmp = params.remove( "comment" );
239            if ( tmp != null && !"".equals( tmp.trim() ) ) {
240                name = tmp;
241            }
242            String[] names = new String[] { name };
243            String description = "Handmade proj4 geographic crs definition (parsed from nad/epsg).";
244            String ids[] = new String[] { identifier };
245            String tmpIdentifier = identifier;
246            String tmpProjectedID = projectedID;
247            if ( tmpIdentifier == null || "".equals( tmpIdentifier.trim() ) ) {
248                tmpIdentifier = params.get( "identifier" );
249                ids = getPredefinedIDs( tmpIdentifier );
250                // if the id was not set, we create a geocrs, which means that no projectedID has been
251                // set, we want to build the datum with the id though!
252                tmpProjectedID = tmpIdentifier;
253            } else {
254                description += " Used by projected crs with id: " + projectedID;
255            }
256            String[] descriptions = new String[] { description };
257            // projectedID will also hold the id of the geo-crs if it is a top level one.
258            GeodeticDatum datum = createDatum( params, tmpProjectedID );
259            GeographicCRS result = new GeographicCRS( datum,
260                                                      new Axis[] { new Axis( Unit.RADIAN, "longitude", Axis.AO_EAST ),
261                                                                  new Axis( Unit.RADIAN, "latitude", Axis.AO_NORTH ) },
262                                                      ids, names, versions, descriptions, areasOfUse );
263            return result;
264        }
265    
266        /**
267         * Create a datum by resolving the 'datum' param or creating it from the given ellipsoid/primemeridan parameters.
268         *
269         * @param params
270         *            to create the datum from
271         * @return a datum
272         * @throws CRSConfigurationException
273         *             if one of the datums necessities( ellipsoid, primemeridan) could not be created.
274         */
275        private GeodeticDatum createDatum( Map<String, String> params, String identifier )
276                                throws CRSConfigurationException {
277    
278            GeodeticDatum result = null;
279            String tmpValue = params.remove( "datum" );
280            if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
281                // removing the defined ellipsoid.
282                result = getPredefinedDatum( tmpValue, params.remove( "ellps" ), identifier );
283                if ( result == null ) {
284                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_UNKNOWN_DATUM",
285                                                                              params.get( EPSG_PRE + "identifier" ),
286                                                                              tmpValue ) );
287                }
288            } else {
289                Ellipsoid ellipsoid = createEllipsoid( params );
290                if ( ellipsoid == null ) {
291                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_DATUM_WITHOUT_ELLIPSOID",
292                                                                              params.get( EPSG_PRE + "identifier" ) ) );
293                }
294                String id = "DATUM_" + datumCount++;
295                String name = "Proj4 defined datum";
296    
297                String description = "Handmade proj4 datum definition (parsed from nad/epsg) used by crs with id: "
298                                     + ( EPSG_PRE + identifier );
299                PrimeMeridian pm = createPrimeMeridian( params );
300                result = new GeodeticDatum( ellipsoid, pm, null, id, name, version, description, areaOfUse );
301            }
302            return result;
303        }
304    
305        /**
306         * Create a prime meridian according to predefined proj4 definitions.
307         *
308         * @param params
309         *            to get the 'pm' parameter from.
310         * @return a mapped primemeridian or the Greenwich if no pm parameter was found.
311         * @throws CRSConfigurationException
312         *             if the pm-parameter could not be mapped.
313         */
314        private PrimeMeridian createPrimeMeridian( Map<String, String> params )
315                                throws CRSConfigurationException {
316            String tmpValue = params.remove( "pm" );
317            PrimeMeridian result = PrimeMeridian.GREENWICH;
318            String id = "pm_" + primeMeridianCount++;
319            String[] names = null;
320            String[] meridianVersions = null;
321            String[] descs = null;
322            String[] aous = null;
323            double longitude = Double.NaN;
324            if ( tmpValue != null && !"".equals( tmpValue.trim() ) && !"greenwich".equals( tmpValue.trim() ) ) {
325                if ( "athens".equals( tmpValue ) ) {
326                    longitude = parseAngleFormat( "23d42'58.815\"E", false );
327                    id = "8912";
328                    names = new String[] { "Athens" };
329                    meridianVersions = new String[] { "1995-06-02" };
330                    descs = new String[] { "Used in Greece for older mapping based on Hatt projection." };
331                } else if ( "bern".equals( tmpValue ) ) {
332                    longitude = parseAngleFormat( "7d26'22.5\"E", false );
333                    id = "8907";
334                    names = new String[] { "Bern" };
335                    meridianVersions = new String[] { "1995-06-02" };
336                    descs = new String[] { "1895 value. Newer value of 7 deg 26 min 22.335 sec E determined in 1938." };
337                } else if ( "bogota".equals( tmpValue ) ) {
338                    longitude = parseAngleFormat( "74d04'51.3\"W", false );
339                    id = "8904";
340                    names = new String[] { "Bogota" };
341                    meridianVersions = new String[] { "1995-06-02" };
342                    descs = new String[] { "Instituto Geografico 'Augustin Cadazzi' (IGAC); Bogota" };
343                } else if ( "brussels".equals( tmpValue ) ) {
344                    longitude = parseAngleFormat( "4d22'4.71\"E", false );
345                    id = "8910";
346                    names = new String[] { "Brussel" };
347                    meridianVersions = new String[] { "1995-06-02" };
348                } else if ( "ferro".equals( tmpValue ) ) {
349                    longitude = parseAngleFormat( "17d40'W", false );
350                    id = "8909";
351                    names = new String[] { "Ferro" };
352                    meridianVersions = new String[] { "1995-06-02" };
353                    descs = new String[] { "Used in Austria and former Czechoslovakia. " };
354                } else if ( "jakarta".equals( tmpValue ) ) {
355                    longitude = parseAngleFormat( "106d48'27.79\"E", false );
356                    id = "8908";
357                    names = new String[] { "Jakarta" };
358                    meridianVersions = new String[] { "1995-06-02" };
359                } else if ( "lisbon".equals( tmpValue ) ) {
360                    longitude = parseAngleFormat( "9d07'54.862\"W", false );
361                    id = "8902";
362                    names = new String[] { "lisbon" };
363                    meridianVersions = new String[] { "1995-06-02" };
364                    descs = new String[] { "Information Source: Instituto Geografico e Cadastral; Lisbon " };
365                } else if ( "madrid".equals( tmpValue ) ) {
366                    longitude = parseAngleFormat( "3d41'16.58\"W", false );
367                    id = "8905";
368                    names = new String[] { "Madrid" };
369                    meridianVersions = new String[] { "1995-06-02" };
370                    descs = new String[] { "Value adopted by IGN (Paris) in 1936. Equivalent to 2 deg 20min 14.025sec. Preferred by EPSG to earlier value of 2deg 20min 13.95sec (2.596898 grads) used by RGS London." };
371                } else if ( "oslo".equals( tmpValue ) ) {
372                    longitude = parseAngleFormat( "10d43'22.5\"E", false );
373                    id = "8913";
374                    names = new String[] { "Oslo" };
375                    meridianVersions = new String[] { "1995-06-02" };
376                    descs = new String[] { "ormerly known as Kristiania or Christiania." };
377                } else if ( "paris".equals( tmpValue ) ) {
378                    longitude = parseAngleFormat( "2d20'14.025\"E", false );
379                    id = "8903";
380                    names = new String[] { "Paris" };
381                    meridianVersions = new String[] { "1995-06-02" };
382                    descs = new String[] { "Value adopted by IGN (Paris) in 1936. Equivalent to 2 deg 20min 14.025sec. Preferred by EPSG to earlier value of 2deg 20min 13.95sec (2.596898 grads) used by RGS London." };
383                } else if ( "rome".equals( tmpValue ) ) {
384                    longitude = parseAngleFormat( "12d27'8.4\"E", false );
385                    id = "8906";
386                    names = new String[] { "Rome" };
387                    meridianVersions = new String[] { "1995-06-02" };
388                } else if ( "stockholm".equals( tmpValue ) ) {
389                    longitude = parseAngleFormat( "18d3'29.8\"E", false );
390                    id = "8911";
391                    names = new String[] { "Stockholm" };
392                    meridianVersions = new String[] { "1995-06-02" };
393                } else {
394                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_UNKNOWN_PM",
395                                                                              params.get( EPSG_PRE + "identifier" ),
396                                                                              tmpValue ) );
397                }
398            }
399            if ( !Double.isNaN( longitude ) ) {
400                String[] ids = new String[] { id };
401                if ( !id.startsWith( "pm_" ) ) {
402                    ids = getPredefinedIDs( id );
403                }
404                result = new PrimeMeridian( Unit.RADIAN, longitude, ids, names, meridianVersions, descs, aous );
405            }
406            return result;
407        }
408    
409        /**
410         * Tries to create an ellips from a predefined mapping, or from axis, eccentricities etc. if defined.
411         *
412         * @param params
413         *            to create the ellipsoid from
414         * @return an (mapped) ellipsoid.
415         * @throws CRSConfigurationException
416         *             if no mapping was found or the semimajor axis was not defined.
417         */
418        private Ellipsoid createEllipsoid( Map<String, String> params )
419                                throws CRSConfigurationException {
420    
421            Ellipsoid result = null;
422            double semiMajorAxis = Double.NaN;
423            double eccentricitySquared = Double.NaN;
424            double eccentricity = Double.NaN;
425            double inverseFlattening = Double.NaN;
426            double semiMinorAxis = Double.NaN;
427    
428            // Get the ellipsoid
429            String tmpValue = params.remove( "ellps" );
430            if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
431                LOG.logDebug( "Creating predefined ellipsoid: " + tmpValue );
432                result = getPredefinedEllipsoid( tmpValue );
433            } else {
434                // if no ellipsoid is defined maybe a sphere
435                tmpValue = params.remove( "R" );
436                if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
437                    LOG.logDebug( "Found a Radius instead of an ellipsoid, the projection uses a sphere!" );
438                    semiMajorAxis = Double.parseDouble( tmpValue );
439                } else {// an ellipsoid instead of a sphere.
440                    tmpValue = params.remove( "a" );
441                    if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
442                        semiMajorAxis = Double.parseDouble( tmpValue );
443                    }
444                    tmpValue = params.remove( "es" );
445                    if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
446                        eccentricitySquared = Double.parseDouble( tmpValue );
447                    } else {
448                        tmpValue = params.remove( "e" );
449                        if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
450                            eccentricity = Double.parseDouble( tmpValue );
451                        } else {
452                            tmpValue = params.remove( "rf" );
453                            if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
454                                inverseFlattening = Double.parseDouble( tmpValue );
455                            } else {
456                                tmpValue = params.remove( "f" );
457                                if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
458                                    double flattening = Double.parseDouble( tmpValue );
459                                    if ( Math.abs( flattening ) > 0.000001 ) {
460                                        inverseFlattening = 1 / flattening;
461                                    } else {
462                                        LOG.logDebug( "The given flattening: " + flattening
463                                                      + " can not be inverted (divide by zero) using a sphere as ellipsoid" );
464                                    }
465                                } else {
466                                    tmpValue = params.remove( "b" );
467                                    if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
468                                        semiMinorAxis = Double.parseDouble( tmpValue );
469                                    }
470                                }
471                            }
472                        }
473                    }
474                }
475    
476                /**
477                 * These parameters (R_A, R_V, R_a, R_g, R_h, R_lat_a, R_lat_g) were not used in the epsg file, maybe they
478                 * are of future value?. <code>
479                 *     private final static double SIXTH = .1666666666666666667; // 1/6
480                 *     private final static double RA4 = .04722222222222222222; // 17/360
481                 *     private final static double RA6 = .02215608465608465608; // 67/3024
482                 *     private final static double RV4 = .06944444444444444444; // 5/72
483                 *     private final static double RV6 = .04243827160493827160; // 55/1296
484                 * </code>
485                 */
486                // String tmpValue = params.get( "R_A" );
487                // if ( tmpValue != null && Boolean.getBoolean( tmpValue ) ) {
488                // semiMajorAxis *= 1. - eccentricitySquared
489                // * ( SIXTH + eccentricitySquared * ( RA4 + eccentricitySquared * RA6 ) );
490                // } else {
491                // tmpValue = params.get( "R_V" );
492                // if ( tmpValue != null && Boolean.getBoolean( tmpValue ) ) {
493                // semiMajorAxis *= 1. - eccentricitySquared
494                // * ( SIXTH + eccentricitySquared * ( RV4 + eccentricitySquared * RV6 ) );
495                // } else {
496                // tmpValue = params.get( "R_a" );
497                // if ( tmpValue != null && Boolean.getBoolean( tmpValue ) ) {
498                // semiMajorAxis = .5 * ( semiMajorAxis + semiMinorAxis );
499                // } else {
500                // tmpValue = params.get( "R_g" );
501                // if ( tmpValue != null && Boolean.getBoolean( tmpValue ) ) {
502                // semiMajorAxis = Math.sqrt( semiMajorAxis * semiMinorAxis );
503                // } else {
504                // tmpValue = params.get( "R_h" );
505                // if ( tmpValue != null && Boolean.getBoolean( tmpValue ) ) {
506                // semiMajorAxis = 2. * semiMajorAxis * semiMinorAxis / ( semiMajorAxis + semiMinorAxis
507                // );
508                // eccentricitySquared = 0.;
509                // } else {
510                // tmpValue = params.get( "R_lat_a" );
511                // if ( tmpValue != null ) {
512                // double tmp = Math.sin( parseAngle( tmpValue ) );
513                // if ( Math.abs( tmp ) > HALFPI )
514                // throw new CRSConfigurationException( "-11" );
515                // tmp = 1. - eccentricitySquared * tmp * tmp;
516                // semiMajorAxis *= .5 * ( 1. - eccentricitySquared + tmp ) / ( tmp * Math.sqrt( tmp )
517                // );
518                // eccentricitySquared = 0.;
519                // } else {
520                // tmpValue = params.get( "R_lat_g" );
521                // if ( tmpValue != null ) {
522                // double tmp = Math.sin( parseAngle( tmpValue ) );
523                // if ( Math.abs( tmp ) > HALFPI )
524                // throw new CRSConfigurationException( "-11" );
525                // tmp = 1. - eccentricitySquared * tmp * tmp;
526                // semiMajorAxis *= Math.sqrt( 1. - eccentricitySquared ) / tmp;
527                // eccentricitySquared = 0.;
528                // }
529                // }
530                // }
531                // }
532                // }
533                // }
534                // }
535                if ( Double.isNaN( semiMajorAxis ) ) {
536                    throw new CRSConfigurationException(
537                                                         Messages.getMessage(
538                                                                              "CRS_CONFIG_PROJ4_ELLIPSOID_WITHOUT_SEMIMAJOR",
539                                                                              params.get( EPSG_PRE + "identifier" ) ) );
540                }
541                String id = "ELLIPSOID_" + ellipsCount++;
542                String description = "Handmade proj4 ellipsoid definition (parsed from nad/epsg) used by crs with id: "
543                                     + params.get( "identifier" );
544                String name = "Proj4 defined ellipsoid";
545    
546                if ( !Double.isNaN( eccentricitySquared ) ) {
547                    result = new Ellipsoid( semiMajorAxis, Math.sqrt( eccentricitySquared ), Unit.METRE, id, name, version,
548                                            description, areaOfUse );
549                } else if ( !Double.isNaN( eccentricity ) ) {
550                    result = new Ellipsoid( semiMajorAxis, eccentricity, Unit.METRE, id, name, version, description,
551                                            areaOfUse );
552                } else if ( !Double.isNaN( inverseFlattening ) ) {
553                    result = new Ellipsoid( semiMajorAxis, Unit.METRE, inverseFlattening, id, name, version, description,
554                                            areaOfUse );
555                } else if ( !Double.isNaN( semiMinorAxis ) ) {
556                    result = new Ellipsoid( Unit.METRE, semiMajorAxis, semiMinorAxis, id, name, version, description,
557                                            areaOfUse );
558                } else {
559                    LOG.logDebug( "Only a semimajor defined, assuming a sphere (instead of an ellipsoid) is to be created." );
560                    result = new Ellipsoid( Unit.METRE, semiMajorAxis, semiMajorAxis, id, name, version, description,
561                                            areaOfUse );
562                }
563            }
564            return result;
565        }
566    
567        /**
568         * @param datumName
569         *            of the datum to map.
570         * @param definedEllipsoid
571         *            the ellipsoid to use (gotten from 'ellps' param) or <code>null</code> if a predefined ellipsoid
572         *            should be used.
573         * @return a Geodetic datum or <code>null</code> if the given name was null or empty.
574         * @throws CRSConfigurationException
575         */
576        private GeodeticDatum getPredefinedDatum( String datumName, String definedEllipsoid, String crsID )
577                                throws CRSConfigurationException {
578            if ( datumName != null && !"".equals( datumName.trim() ) ) {
579                datumName = datumName.trim();
580                String[] datumIDs = null;
581                String[] datumNames = null;
582                String[] datumDescriptions = null;
583                String[] datumVersions = null;
584                String[] datumAOU = null;
585                Helmert confInfo = new Helmert( GeographicCRS.WGS84, GeographicCRS.WGS84, "Created by proj4 CRSProvider" );
586                Ellipsoid ellipsoid = null;
587                if ( "GGRS87".equalsIgnoreCase( datumName ) ) {
588                    confInfo = new Helmert( -199.87, 74.79, 246.62, 0, 0, 0, 0, GeographicCRS.WGS84, GeographicCRS.WGS84,
589                                            getPredefinedIDs( "1272" ), new String[] {}, null, null, null );
590    
591                    datumIDs = getPredefinedIDs( "6121" );
592                    datumNames = new String[] { "Greek_Geodetic_Reference_System_1987" };
593                    if ( definedEllipsoid == null || "".equals( definedEllipsoid )
594                         || "GRS80".equals( definedEllipsoid.trim() ) ) {
595                        ellipsoid = getPredefinedEllipsoid( "GRS80" );
596                    } else {
597                        ellipsoid = getPredefinedEllipsoid( definedEllipsoid );
598                    }
599                } else if ( "NAD27".equalsIgnoreCase( datumName ) ) {
600                    confInfo = new Helmert( -8, 160, 176, 0, 0, 0, 0, GeographicCRS.WGS84, GeographicCRS.WGS84,
601                                            getPredefinedIDs( "1173" ), new String[] { "North_American_Datum_1983" }, null,
602                                            null, null );
603    
604                    datumIDs = getPredefinedIDs( "6267" );
605                    datumNames = new String[] { "North_American_Datum_1927" };
606                    // don't no how to do this.
607                    // "nadgrids=@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat";
608                    if ( definedEllipsoid == null || "".equals( definedEllipsoid )
609                         || "clrk66".equals( definedEllipsoid.trim() ) ) {
610                        ellipsoid = getPredefinedEllipsoid( "clrk66" );
611                    } else {
612                        ellipsoid = getPredefinedEllipsoid( definedEllipsoid );
613                    }
614                } else if ( "NAD83".equalsIgnoreCase( datumName ) ) {
615                    confInfo = new Helmert( GeographicCRS.WGS84, GeographicCRS.WGS84, getPredefinedIDs( "1188" ), null,
616                                            null, new String[] { "Derived at 312 stations." },
617                                            new String[] { "North America - all Canada and USA subunits" } );
618                    datumIDs = getPredefinedIDs( "6269" );
619                    datumNames = new String[] { "North_American_Datum_1983" };
620                    if ( definedEllipsoid == null || "".equals( definedEllipsoid )
621                         || "GRS80".equals( definedEllipsoid.trim() ) ) {
622                        ellipsoid = getPredefinedEllipsoid( "GRS80" );
623                    } else {
624                        ellipsoid = getPredefinedEllipsoid( definedEllipsoid );
625                    }
626                } else if ( "OSGB36".equalsIgnoreCase( datumName ) ) {
627                    confInfo = new Helmert(
628                                            446.448,
629                                            -125.157,
630                                            542.060,
631                                            0.1502,
632                                            0.2470,
633                                            0.8421,
634                                            -20.4894,
635                                            GeographicCRS.WGS84,
636                                            GeographicCRS.WGS84,
637                                            getPredefinedIDs( "1314" ),
638                                            null,
639                                            null,
640                                            new String[] { "For a more accurate transformation see OSGB 1936 / British National Grid to ETRS89 (2) (code 1039): contact the Ordnance Survey of Great Britain (http://www.gps.gov.uk/gpssurveying.asp) for details." },
641                                            new String[] { "United Kingdom (UK) - Great Britain and UKCS" } );
642                    datumIDs = getPredefinedIDs( "6001" );
643                    datumNames = new String[] { "Airy 1830" };
644                    if ( definedEllipsoid == null || "".equals( definedEllipsoid )
645                         || "airy".equals( definedEllipsoid.trim() ) ) {
646                        ellipsoid = getPredefinedEllipsoid( "airy" );
647                    } else {
648                        ellipsoid = getPredefinedEllipsoid( definedEllipsoid );
649                    }
650                } else if ( "WGS84".equalsIgnoreCase( datumName ) ) {
651                    return GeodeticDatum.WGS84;
652                } else if ( "carthage".equalsIgnoreCase( datumName ) ) {
653                    confInfo = new Helmert( -263.0, 6.0, 431.0, 0, 0, 0, 0, GeographicCRS.WGS84, GeographicCRS.WGS84,
654                                            getPredefinedIDs( "1130" ), null, null,
655                                            new String[] { "Derived at 5 stations." }, new String[] { "Tunisia" } );
656                    datumIDs = getPredefinedIDs( "6816" );
657                    datumNames = new String[] { "Carthage 1934 Tunisia" };
658                    if ( definedEllipsoid == null || "".equals( definedEllipsoid )
659                         || "clark80".equals( definedEllipsoid.trim() ) ) {
660                        ellipsoid = getPredefinedEllipsoid( "clark80" );
661                    } else {
662                        ellipsoid = getPredefinedEllipsoid( definedEllipsoid );
663                    }
664                } else if ( "hermannskogel".equalsIgnoreCase( datumName ) ) {
665                    confInfo = new Helmert( 653.0, -212.0, 449.0, 0, 0, 0, 0, GeographicCRS.WGS84, GeographicCRS.WGS84,
666                                            new String[] { "kogel", EPSG_PRE + "1306" }, null, null,
667                                            new String[] { "No epsg code was found." }, null );
668                    datumIDs = new String[] { "Hermannskogel" };
669                    datumNames = new String[] { "some undefined proj4 datum" };
670                    if ( definedEllipsoid == null || "".equals( definedEllipsoid )
671                         || "bessel".equals( definedEllipsoid.trim() ) ) {
672                        ellipsoid = getPredefinedEllipsoid( "bessel" );
673                    } else {
674                        ellipsoid = getPredefinedEllipsoid( definedEllipsoid );
675                    }
676                } else if ( "ire65".equalsIgnoreCase( datumName ) ) {
677                    confInfo = new Helmert( 482.530, -130.596, 564.557, -1.042, -0.214, -0.631, 8.15, GeographicCRS.WGS84,
678                                            GeographicCRS.WGS84, new String[] { "ire65_conversion" }, null, null,
679                                            new String[] { "no epsg code was found" }, null );
680                    datumIDs = new String[] { "Ireland 1965" };
681                    datumNames = new String[] { "no epsg code was found." };
682                    if ( definedEllipsoid == null || "".equals( definedEllipsoid )
683                         || "mod_airy".equals( definedEllipsoid.trim() ) ) {
684                        ellipsoid = getPredefinedEllipsoid( "mod_airy" );
685                    } else {
686                        ellipsoid = getPredefinedEllipsoid( definedEllipsoid );
687                    }
688                } else if ( "nzgd49".equalsIgnoreCase( datumName ) ) {
689                    confInfo = new Helmert(
690                                            59.47,
691                                            -5.04,
692                                            187.44,
693                                            0.47,
694                                            -0.1,
695                                            1.024,
696                                            -4.5993,
697                                            GeographicCRS.WGS84,
698                                            GeographicCRS.WGS84,
699                                            getPredefinedIDs( "1564" ),
700                                            new String[] { "NZGD49 to WGS 84 (2)" },
701                                            new String[] { "OSG-Nzl 4m" },
702                                            new String[] { "hese parameter values are taken from NZGD49 to NZGD2000 (4) (code 1701) and assume that NZGD2000 and WGS 84 are coincident to within the accuracy of the transformation. For improved accuracy use NZGD49 to WGS 84 (4) (code 1670)." },
703                                            new String[] { "New Zealand" } );
704                    datumIDs = getPredefinedIDs( "6272" );
705                    datumNames = new String[] { "New Zealand Geodetic Datum 1949" };
706                    if ( definedEllipsoid == null || "".equals( definedEllipsoid )
707                         || "intl".equals( definedEllipsoid.trim() ) ) {
708                        ellipsoid = getPredefinedEllipsoid( "intl" );
709                    } else {
710                        ellipsoid = getPredefinedEllipsoid( definedEllipsoid );
711                    }
712                } else if ( "potsdam".equalsIgnoreCase( datumName ) ) {
713                    if ( ( crsID != null && !"".equals( crsID ) ) && "3068".equals( crsID ) || "4314".equals( crsID )
714                         || "31466".equals( crsID ) || "31467".equals( crsID ) || "31468".equals( crsID )
715                         || "31469".equals( crsID ) ) {
716                        confInfo = new Helmert(
717                                                598.1,
718                                                73.7,
719                                                418.2,
720                                                0.202,
721                                                0.045,
722                                                -2.455,
723                                                6.7,
724                                                GeographicCRS.WGS84,
725                                                GeographicCRS.WGS84,
726                                                getPredefinedIDs( "1777" ),
727                                                new String[] { "DHDN to WGS 84" },
728                                                new String[] { "EPSG-Deu W 3m" },
729                                                new String[] { "Parameter values from DHDN to ETRS89 (2) (code 1776) assuming that ETRS89 is equivalent to WGS 84 within the accuracy of the transformation. Replaces DHDN to WGS 84 (1) (tfm code 1673)." },
730                                                new String[] { "Germany - states of former West Germany - Baden-Wurtemberg, Bayern, Hessen, Niedersachsen, Nordrhein-Westfalen, Rheinland-Pfalz, Saarland, Schleswig-Holstein." } );
731                        datumIDs = getPredefinedIDs( "6314" );
732                        datumNames = new String[] { "Deutsches Hauptdreiecksnetz" };
733                        datumVersions = new String[] { "2006-06-12" };
734                        datumDescriptions = new String[] { "Fundamental point: Rauenberg. Latitude: 52 deg 27 min 12.021 sec N; Longitude: 13 deg 22 min 04.928 sec E (of Greenwich). This station was destroyed in 1910 and the station at Potsdam substituted as the fundamental point." };
735                        datumAOU = new String[] { "Germany - states of former West Germany - Baden-Wurtemberg, Bayern, Hessen, Niedersachsen, Nordrhein-Westfalen, Rheinland-Pfalz, Saarland, Schleswig-Holstein." };
736                    } else {
737                        confInfo = new Helmert(
738                                                606.0,
739                                                23.0,
740                                                413.0,
741                                                0,
742                                                0,
743                                                0,
744                                                0,
745                                                GeographicCRS.WGS84,
746                                                GeographicCRS.WGS84,
747                                                getPredefinedIDs( "15955" ),
748                                                new String[] { "RD/83 to WGS 84 (1)" },
749                                                new String[] { "OGP-Deu BeTA2007" },
750                                                new String[] { "These parameter values are taken from DHDN to ETRS89 (8) (code 15948) as RD/83 and ETRS89 may be considered equivalent to DHDN and WGS 84 respectively within the accuracy of the transformation." },
751                                                new String[] { "Germany-Sachsen" } );
752                        datumIDs = getPredefinedIDs( "6746" );
753                        datumNames = new String[] { "Potsdam Rauenberg 1950 DHDN" };
754                    }
755    
756                    if ( definedEllipsoid == null || "".equals( definedEllipsoid )
757                         || "bessel".equals( definedEllipsoid.trim() ) ) {
758                        ellipsoid = getPredefinedEllipsoid( "bessel" );
759                    } else {
760                        ellipsoid = getPredefinedEllipsoid( definedEllipsoid );
761                    }
762                } else {
763                    return null;
764                }
765    
766                return new GeodeticDatum( ellipsoid, PrimeMeridian.GREENWICH, confInfo, datumIDs, datumNames,
767                                          datumVersions, datumDescriptions, datumAOU );
768            }
769            return null;
770        }
771    
772        private String[] getPredefinedIDs( String idNumber ) {
773            return new String[] { EPSG_PRE + idNumber, OGC_URN + idNumber, OPENGIS_URL + idNumber, OPENGIS_URN + idNumber };
774        }
775    
776        /**
777         * This method was adopted from the com.jhlabs.map.proj.Projection Factory#initizalize method.
778         *
779         * @param projName
780         *            name of the projection
781         * @return the deegree understandable String or <code>null</code> if the projName could not be mapped.
782         * @throws CRSConfigurationException
783         *             if the projName could not be mapped.
784         */
785        private Projection createProjection( String projName, GeographicCRS geoCRS, Map<String, String> params )
786                                throws CRSConfigurationException {
787            Projection result = null;
788            // in degrees
789            double projectionLatitude = 0;
790            double projectionLongitude = 0;
791            double firstParallelLatitude = Double.NaN;
792            double secondParallelLatitude = Double.NaN;
793            double trueScaleLatitude = Double.NaN;
794    
795            // meter
796            double falseNorthing = 0;
797            double falseEasting = 0;
798            double scale = 1;
799    
800            String s = params.remove( "lat_0" );
801            if ( s != null && !"".equals( s.trim() ) ) {
802                projectionLatitude = parseAngleFormat( s, false );
803            }
804    
805            s = params.remove( "lon_0" );
806            if ( s != null && !"".equals( s.trim() ) ) {
807                projectionLongitude = parseAngleFormat( s, false );
808            }
809            Point2d naturalOrigin = new Point2d( projectionLongitude, projectionLatitude );
810    
811            s = params.remove( "lat_1" );
812            if ( s != null && !"".equals( s.trim() ) ) {
813                firstParallelLatitude = parseAngleFormat( s, false );
814            }
815    
816            s = params.remove( "lat_2" );
817            if ( s != null && !"".equals( s.trim() ) ) {
818                secondParallelLatitude = parseAngleFormat( s, false );
819            }
820    
821            s = params.remove( "lat_ts" );
822            if ( s != null && !"".equals( s.trim() ) ) {
823                trueScaleLatitude = parseAngleFormat( s, false );
824            }
825    
826            s = params.remove( "x_0" );
827            if ( s != null && !"".equals( s.trim() ) ) {
828                falseEasting = Double.parseDouble( s );
829            }
830            s = params.remove( "y_0" );
831            if ( s != null && !"".equals( s.trim() ) ) {
832                falseNorthing = Double.parseDouble( s );
833            }
834    
835            s = params.remove( "k_0" );
836            if ( s == null ) {
837                s = params.remove( "k" );
838            }
839            if ( s != null && !"".equals( s.trim() ) ) {
840                scale = Double.parseDouble( s );
841            }
842    
843            Unit units = createUnit( params );
844    
845            if ( projName != null && !"".equals( projName ) ) {
846                projName = projName.trim();
847                if ( "aea".equals( projName ) ) {// "Albers Equal Area"
848                } else if ( "aeqd".equals( projName ) ) {// "Azimuthal Equidistant"
849                } else if ( "airy".equals( projName ) ) {// "Airy"
850                } else if ( "aitoff".equals( projName ) ) {// "Aitoff"
851                } else if ( "alsk".equals( projName ) ) {// "Mod. Stereographics of Alaska"
852                } else if ( "apian".equals( projName ) ) {// "Apian Globular I"
853                } else if ( "august".equals( projName ) ) {// "August Epicycloidal"
854                } else if ( "bacon".equals( projName ) ) {// "Bacon Globular"
855                } else if ( "bipc".equals( projName ) ) {// "Bipolar conic of western hemisphere"
856                } else if ( "boggs".equals( projName ) ) {// "Boggs Eumorphic"
857                } else if ( "bonne".equals( projName ) ) {// "Bonne (Werner lat_1=90)"
858                } else if ( "cass".equals( projName ) ) {// "Cassini"
859                } else if ( "cc".equals( projName ) ) {// "Central Cylindrical"
860                } else if ( "cea".equals( projName ) ) {// "Equal Area Cylindrical"
861                } else if ( "chamb".equals( projName ) ) {// "Chamberlin Trimetric"
862                } else if ( "collg".equals( projName ) ) {// "Collignon"
863                } else if ( "crast".equals( projName ) ) {// "Craster Parabolic (Putnins P4)"
864                } else if ( "denoy".equals( projName ) ) {// "Denoyer Semi-Elliptical"
865                } else if ( "eck1".equals( projName ) ) {// "Eckert I"
866                } else if ( "eck2".equals( projName ) ) {// "Eckert II"
867                } else if ( "eck3".equals( projName ) ) {// "Eckert III"
868                } else if ( "eck4".equals( projName ) ) {// "Eckert IV"
869                } else if ( "eck5".equals( projName ) ) {// "Eckert V"
870                } else if ( "eck6".equals( projName ) ) {// "Eckert VI"
871                } else if ( "eqc".equals( projName ) ) {// "Equidistant Cylindrical (Plate Caree)"
872                } else if ( "eqdc".equals( projName ) ) {// "Equidistant Conic"
873                } else if ( "euler".equals( projName ) ) {// "Euler"
874                } else if ( "fahey".equals( projName ) ) {// "Fahey"
875                } else if ( "fouc".equals( projName ) ) {// "Foucaut"
876                } else if ( "fouc_s".equals( projName ) ) {// "Foucaut Sinusoidal"
877                } else if ( "gall".equals( projName ) ) {// "Gall (Gall Stereographic)"
878                } else if ( "gins8".equals( projName ) ) {// "Ginsburg VIII (TsNIIGAiK)"
879                } else if ( "gn_sinu".equals( projName ) ) {// "General Sinusoidal Series"
880                } else if ( "gnom".equals( projName ) ) {// "Gnomonic"
881                } else if ( "goode".equals( projName ) ) {// "Goode Homolosine"
882                } else if ( "gs48".equals( projName ) ) {// "Mod. Stererographics of 48 U.S."
883                } else if ( "gs50".equals( projName ) ) {// "Mod. Stererographics of 50 U.S."
884                } else if ( "hammer".equals( projName ) ) {// "Hammer & Eckert-Greifendorff"
885                } else if ( "hatano".equals( projName ) ) {// "Hatano Asymmetrical Equal Area"
886                } else if ( "imw_p".equals( projName ) ) {// "Internation Map of the World Polyconic"
887                } else if ( "kav5".equals( projName ) ) {// "Kavraisky V"
888                } else if ( "kav7".equals( projName ) ) {// "Kavraisky VII"
889                } else if ( "labrd".equals( projName ) ) {// "Laborde"
890                } else if ( "laea".equals( projName ) ) {// "Lambert Azimuthal Equal Area"
891                    result = new LambertAzimuthalEqualArea( geoCRS, falseNorthing, falseEasting, naturalOrigin, units,
892                                                            scale );
893                } else if ( "lagrng".equals( projName ) ) {// "Lagrange"
894                } else if ( "larr".equals( projName ) ) {// "Larrivee"
895                } else if ( "lask".equals( projName ) ) {// "Laskowski"
896                } else if ( "latlong".equals( projName ) ) {// "Lat/Long"
897                } else if ( "lcc".equals( projName ) ) {// "Lambert Conformal Conic"
898                    result = new LambertConformalConic( firstParallelLatitude, secondParallelLatitude, geoCRS,
899                                                        falseNorthing, falseEasting, naturalOrigin, units, scale );
900                } else if ( "leac".equals( projName ) ) {// "Lambert Equal Area Conic"
901                } else if ( "lee_os".equals( projName ) ) {// "Lee Oblated Stereographic"
902                } else if ( "loxim".equals( projName ) ) {// "Loximuthal"
903                } else if ( "lsat".equals( projName ) ) {// "Space oblique for LANDSAT"
904                } else if ( "mbt_s".equals( projName ) ) {// "McBryde-Thomas Flat-Polar Sine"
905                } else if ( "mbt_fps".equals( projName ) ) {// "McBryde-Thomas Flat-Pole Sine (No. 2)"
906                } else if ( "mbtfpp".equals( projName ) ) {// "McBride-Thomas Flat-Polar Parabolic"
907                } else if ( "mbtfpq".equals( projName ) ) {// "McBryde-Thomas Flat-Polar Quartic"
908                } else if ( "mbtfps".equals( projName ) ) {// "McBryde-Thomas Flat-Polar Sinusoidal"
909                } else if ( "merc".equals( projName ) ) {// "Mercator"
910                } else if ( "mil_os".equals( projName ) ) {// "Miller Oblated Stereographic"
911                } else if ( "mill".equals( projName ) ) {// "Miller Cylindrical"
912                } else if ( "mpoly".equals( projName ) ) {// "Modified Polyconic"
913                } else if ( "moll".equals( projName ) ) {// "Mollweide"
914                } else if ( "murd1".equals( projName ) ) {// "Murdoch I"
915                } else if ( "murd2".equals( projName ) ) {// "Murdoch II"
916                } else if ( "murd3".equals( projName ) ) {// "Murdoch III"
917                } else if ( "nell".equals( projName ) ) {// "Nell"
918                } else if ( "nell_h".equals( projName ) ) {// "Nell-Hammer"
919                } else if ( "nicol".equals( projName ) ) {// "Nicolosi Globular"
920                } else if ( "nsper".equals( projName ) ) {// "Near-sided perspective"
921                } else if ( "nzmg".equals( projName ) ) {// "New Zealand Map Grid"
922                } else if ( "ob_tran".equals( projName ) ) {// "General Oblique Transformation"
923                } else if ( "ocea".equals( projName ) ) {// "Oblique Cylindrical Equal Area"
924                } else if ( "oea".equals( projName ) ) {// "Oblated Equal Area"
925                } else if ( "omerc".equals( projName ) ) {// "Oblique Mercator"
926                } else if ( "ortel".equals( projName ) ) {// "Ortelius Oval"
927                } else if ( "ortho".equals( projName ) ) {// "Orthographic"
928                } else if ( "pconic".equals( projName ) ) {// "Perspective Conic"
929                } else if ( "poly".equals( projName ) ) {// "Polyconic (American)"
930                } else if ( "putp1".equals( projName ) ) {// "Putnins P1"
931                } else if ( "putp2".equals( projName ) ) {// "Putnins P2"
932                } else if ( "putp3".equals( projName ) ) {// "Putnins P3"
933                } else if ( "putp3p".equals( projName ) ) {// "Putnins P3'"
934                } else if ( "putp4p".equals( projName ) ) {// "Putnins P4'"
935                } else if ( "putp5".equals( projName ) ) {// "Putnins P5"
936                } else if ( "putp5p".equals( projName ) ) {// "Putnins P5'"
937                } else if ( "putp6".equals( projName ) ) {// "Putnins P6"
938                } else if ( "putp6p".equals( projName ) ) {// "Putnins P6'"
939                } else if ( "qua_aut".equals( projName ) ) {// "Quartic Authalic"
940                } else if ( "robin".equals( projName ) ) {// "Robinson"
941                } else if ( "rpoly".equals( projName ) ) {// "Rectangular Polyconic"
942                } else if ( "sinu".equals( projName ) ) {// "Sinusoidal (Sanson-Flamsteed)"
943                } else if ( "somerc".equals( projName ) ) {// "Swiss. Obl. Mercator"
944                } else if ( "stere".equals( projName ) ) {// "Oblique Stereographic Alternative"
945                    result = new StereographicAzimuthal( trueScaleLatitude, geoCRS, falseNorthing, falseEasting,
946                                                         naturalOrigin, units, scale );
947                } else if ( "sterea".equals( projName ) ) {
948                    result = new StereographicAlternative( geoCRS, falseNorthing, falseEasting, naturalOrigin, units, scale );
949                } else if ( "tcc".equals( projName ) ) {// "Transverse Central Cylindrical"
950                } else if ( "tcea".equals( projName ) ) {// "Transverse Cylindrical Equal Area"
951                } else if ( "tissot".equals( projName ) ) {// "Tissot Conic"
952                } else if ( "tmerc".equals( projName ) || "utm".equals( projName ) ) {// "Transverse
953                    // Mercator"
954                    s = params.remove( "south" );
955                    boolean north = ( s == null || "".equals( s.trim() ) );
956                    s = params.remove( "zone" );
957                    if ( s != null && !"".equals( s.trim() ) ) {
958                        int zone = Integer.parseInt( s );
959                        result = new TransverseMercator( zone, north, geoCRS, units );
960                    } else {
961                        result = new TransverseMercator( north, geoCRS, falseNorthing, falseEasting, naturalOrigin, units,
962                                                         scale );
963                    }
964                } else if ( "tpeqd".equals( projName ) ) {// "Two Point Equidistant"
965                } else if ( "tpers".equals( projName ) ) {// "Tilted perspective"
966                } else if ( "ups".equals( projName ) ) {// "Universal Polar Stereographic"
967                } else if ( "urm5".equals( projName ) ) {// "Urmaev V"
968                } else if ( "urmfps".equals( projName ) ) {// "Urmaev Flat-Polar Sinusoidal"
969                } else if ( "utm".equals( projName ) ) {// "Universal Transverse Mercator (UTM)"
970                } else if ( "vandg".equals( projName ) ) {// "van der Grinten (I)"
971                } else if ( "vandg2".equals( projName ) ) {// "van der Grinten II"
972                } else if ( "vandg3".equals( projName ) ) {// "van der Grinten III"
973                } else if ( "vandg4".equals( projName ) ) {// "van der Grinten IV"
974                } else if ( "vitk1".equals( projName ) ) {// "Vitkovsky I"
975                } else if ( "wag1".equals( projName ) ) {// "Wagner I (Kavraisky VI)"
976                } else if ( "wag2".equals( projName ) ) {// "Wagner II"
977                } else if ( "wag3".equals( projName ) ) {// "Wagner III"
978                } else if ( "wag4".equals( projName ) ) {// "Wagner IV"
979                } else if ( "wag5".equals( projName ) ) {// "Wagner V"
980                } else if ( "wag6".equals( projName ) ) {// "Wagner VI"
981                } else if ( "wag7".equals( projName ) ) {// "Wagner VII"
982                } else if ( "weren".equals( projName ) ) {// "Werenskiold I"
983                } else if ( "wink1".equals( projName ) ) {// "Winkel I"
984                } else if ( "wink2".equals( projName ) ) {// "Winkel II"
985                } else if ( "wintri".equals( projName ) ) {// "Winkel Tripel"
986                }
987                if ( result == null ) {
988                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_UNKNOWN_PROJECTION",
989                                                                              projName ) );
990                }
991            }
992            return result;
993        }
994    
995        /**
996         * @param params
997         *            the values to get the units or to_meter from.
998         * @return a unit create from the +unit parameter or Unit.METRE if not found.
999         * @throws CRSConfigurationException
1000         *             if the given unit parameter could not be mapped to a valid deegree-crs unit.
1001         */
1002        private Unit createUnit( Map<String, String> params )
1003                                throws CRSConfigurationException {
1004            Unit result = Unit.METRE;
1005            String tmpValue = params.remove( "units" );
1006            if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
1007                result = Unit.createUnitFromString( tmpValue );
1008                if ( result == null ) {
1009                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_UNKNOWN_UNIT",
1010                                                                              params.get( EPSG_PRE + "identifier" ),
1011                                                                              tmpValue ) );
1012                }
1013            } else {
1014                tmpValue = params.remove( "to_meter" );
1015                if ( tmpValue != null && !"".equals( tmpValue.trim() ) ) {
1016                    result = new Unit( "Unknown", "unknown", Double.parseDouble( tmpValue ), Unit.METRE );
1017                }
1018            }
1019            return result;
1020        }
1021    
1022        /**
1023         * Maps the given proj4 name to an id (if any) and creates an ellipsoid accordingly.
1024         *
1025         * @param ellipsoidName
1026         *            defined in the proj lib
1027         * @return an ellipsoid with an id and name or <code>null</code> if the ellipsoidName was null or empty.
1028         * @throws CRSConfigurationException
1029         *             if the given name could not be mapped.
1030         */
1031        private Ellipsoid getPredefinedEllipsoid( String ellipsoidName )
1032                                throws CRSConfigurationException {
1033            if ( ellipsoidName != null && !"".equals( ellipsoidName.trim() ) ) {
1034                ellipsoidName = ellipsoidName.trim();
1035                double semiMajorAxis = 0;
1036                double semiMinorAxis = Double.NaN;
1037                double inverseFlattening = 1;
1038                String id = ellipsoidName;
1039                String name = "";
1040                if ( "APL4.9".equalsIgnoreCase( ellipsoidName ) ) {
1041                    semiMajorAxis = 6378137.0;
1042                    inverseFlattening = 298.25;
1043                    name = "Appl. Physics. 1965";
1044                    // no epsg
1045                } else if ( "CPM".equalsIgnoreCase( ellipsoidName ) ) {
1046                    semiMajorAxis = 6375738.7;
1047                    inverseFlattening = 334.29;
1048                    name = "Comm. des Poids et Mesures 1799";
1049                    // no epsg
1050                } else if ( "GRS67".equalsIgnoreCase( ellipsoidName ) ) {
1051                    semiMajorAxis = 6378160.0;
1052                    inverseFlattening = 298.2471674270;
1053                    name = "GRS 67(IUGG 1967)";
1054                    id = "7036";
1055                } else if ( "GRS80".equalsIgnoreCase( ellipsoidName ) ) {
1056                    semiMajorAxis = 6378137.0;
1057                    inverseFlattening = 298.257222101;
1058                    name = "GRS 1980(IUGG, 1980)";
1059                    id = "7019";
1060                } else if ( "IAU76".equalsIgnoreCase( ellipsoidName ) ) {
1061                    semiMajorAxis = 6378140.0;
1062                    inverseFlattening = 298.257;
1063                    name = "IAU 1976";
1064                    // probably clarke
1065                    // no epsg
1066                } else if ( "MERIT".equalsIgnoreCase( ellipsoidName ) ) {
1067                    semiMajorAxis = 6378137.0;
1068                    inverseFlattening = 298.257;
1069                    name = "MERIT 1983";
1070                    // no epsg
1071                } else if ( "NWL9D".equalsIgnoreCase( ellipsoidName ) ) {
1072                    semiMajorAxis = 6378145.0;
1073                    inverseFlattening = 298.25;
1074                    name = "Naval Weapons Lab., 1965";
1075                    // no epsg
1076                } else if ( "SEasia".equalsIgnoreCase( ellipsoidName ) ) {
1077                    semiMajorAxis = 6378155.0;
1078                    semiMinorAxis = 6356773.3205;
1079                    name = "Southeast Asia";
1080                    // no epsg
1081                } else if ( "SGS85".equalsIgnoreCase( ellipsoidName ) ) {
1082                    semiMajorAxis = 6378136.0;
1083                    inverseFlattening = 298.257;
1084                    name = "Soviet Geodetic System 85";
1085                    // no epsg
1086                } else if ( "WGS60".equalsIgnoreCase( ellipsoidName ) ) {
1087                    semiMajorAxis = 6378165.0;
1088                    inverseFlattening = 298.3;
1089                    name = "WGS 60";
1090                    // no epsg
1091                } else if ( "WGS66".equalsIgnoreCase( ellipsoidName ) ) {
1092                    semiMajorAxis = 6378145.0;
1093                    inverseFlattening = 298.25;
1094                    name = "WGS 66";
1095                    // no epsg
1096                } else if ( "WGS72".equalsIgnoreCase( ellipsoidName ) ) {
1097                    semiMajorAxis = 6378135.0;
1098                    inverseFlattening = 298.26;
1099                    name = "WGS 72";
1100                    id = "7043";
1101                } else if ( "WGS84".equalsIgnoreCase( ellipsoidName ) ) {
1102                    semiMajorAxis = 6378137.0;
1103                    inverseFlattening = 298.257223563;
1104                    name = "WGS 84";
1105                    id = "7030";
1106                } else if ( "airy".equalsIgnoreCase( ellipsoidName ) ) {
1107                    semiMajorAxis = 6377563.396;
1108                    semiMinorAxis = 6356256.910;
1109                    name = "Airy 1830";
1110                    id = "7001";
1111                } else if ( "andrae".equalsIgnoreCase( ellipsoidName ) ) {
1112                    semiMajorAxis = 6377104.43;
1113                    inverseFlattening = 300.0;
1114                    name = "Andrae 1876 (Den., Iclnd.)";
1115                    // no epsg
1116                } else if ( "aust_SA".equalsIgnoreCase( ellipsoidName ) ) {
1117                    semiMajorAxis = 6378160.0;
1118                    inverseFlattening = 298.25;
1119                    name = "Australian Natl & S. Amer. 1969";
1120                    id = "7050";
1121                } else if ( "bess_nam".equalsIgnoreCase( ellipsoidName ) ) {
1122                    semiMajorAxis = 6377483.865;
1123                    inverseFlattening = 299.1528128;
1124                    name = "Bessel 1841 (Namibia)";
1125                    id = "7046";
1126                } else if ( "bessel".equalsIgnoreCase( ellipsoidName ) ) {
1127                    semiMajorAxis = 6377397.155;
1128                    inverseFlattening = 299.1528128;
1129                    name = "Bessel 1841";
1130                    id = "7004";
1131                } else if ( "clrk66".equalsIgnoreCase( ellipsoidName ) ) {
1132                    semiMajorAxis = 6378206.4;
1133                    semiMinorAxis = 6356583.8;
1134                    name = "Clarke 1866";
1135                    id = "7008";
1136                } else if ( "clrk80".equalsIgnoreCase( ellipsoidName ) ) {
1137                    semiMajorAxis = 6378249.145;
1138                    inverseFlattening = 293.4663;
1139                    name = "Clarke 1880 mod.";
1140                    id = "7034";
1141                } else if ( "delmbr".equalsIgnoreCase( ellipsoidName ) ) {
1142                    semiMajorAxis = 6376428.;
1143                    inverseFlattening = 311.5;
1144                    name = "Delambre 1810 (Belgium)";
1145                    // epsg closed
1146                } else if ( "engelis".equalsIgnoreCase( ellipsoidName ) ) {
1147                    semiMajorAxis = 6378136.05;
1148                    inverseFlattening = 298.2566;
1149                    name = "Engelis 1985";
1150                    // espg closed
1151                } else if ( "evrst30".equalsIgnoreCase( ellipsoidName ) ) {
1152                    semiMajorAxis = 6377276.345;
1153                    inverseFlattening = 300.8017;
1154                    name = "Everest 1830";
1155                    id = "7042";
1156                } else if ( "evrst48".equalsIgnoreCase( ellipsoidName ) ) {
1157                    semiMajorAxis = 6377304.063;
1158                    inverseFlattening = 300.8017;
1159                    name = "Everest 1948";
1160                    id = "7018";
1161                } else if ( "evrst56".equalsIgnoreCase( ellipsoidName ) ) {
1162                    semiMajorAxis = 6377301.243;
1163                    inverseFlattening = 300.8017;
1164                    name = "Everest 1956";
1165                    id = "7044";
1166                } else if ( "evrst69".equalsIgnoreCase( ellipsoidName ) ) {
1167                    semiMajorAxis = 6377295.664;
1168                    inverseFlattening = 300.8017;
1169                    name = "Everest 1969";
1170                    id = "7056";
1171                } else if ( "evrstSS".equalsIgnoreCase( ellipsoidName ) ) {
1172                    semiMajorAxis = 6377298.556;
1173                    inverseFlattening = 300.8017;
1174                    name = "Everest (Sabah & Sarawak)";
1175                    id = "7016";
1176                } else if ( "fschr60".equalsIgnoreCase( ellipsoidName ) ) {
1177                    semiMajorAxis = 6378166.;
1178                    inverseFlattening = 298.3;
1179                    name = "Fischer (Mercury Datum) 1960";
1180                    // epsg closed
1181                } else if ( "fschr60m".equalsIgnoreCase( ellipsoidName ) ) {
1182                    semiMajorAxis = 6378155.;
1183                    inverseFlattening = 298.3;
1184                    name = "Modified Fischer 1960";
1185                    // epsg closed
1186                } else if ( "fschr68".equalsIgnoreCase( ellipsoidName ) ) {
1187                    semiMajorAxis = 6378150.;
1188                    inverseFlattening = 298.3;
1189                    name = "Fischer 1968";
1190                    // epsg closed
1191                } else if ( "helmert".equalsIgnoreCase( ellipsoidName ) ) {
1192                    semiMajorAxis = 6378200.;
1193                    inverseFlattening = 298.3;
1194                    name = "Helmert 1906";
1195                    id = "7020";
1196                } else if ( "hough".equalsIgnoreCase( ellipsoidName ) ) {
1197                    semiMajorAxis = 6378270.0;
1198                    inverseFlattening = 297.;
1199                    name = "Hough";
1200                    id = "7053";
1201                } else if ( "intl".equalsIgnoreCase( ellipsoidName ) ) {
1202                    semiMajorAxis = 6378388.0;
1203                    inverseFlattening = 297.;
1204                    name = "International 1909 (Hayford)";
1205                    id = "7022";
1206                } else if ( "kaula".equalsIgnoreCase( ellipsoidName ) ) {
1207                    semiMajorAxis = 6378163.;
1208                    inverseFlattening = 298.24;
1209                    name = "Kaula 1961";
1210                    // no epsg
1211                } else if ( "krass".equalsIgnoreCase( ellipsoidName ) ) {
1212                    semiMajorAxis = 6378245.0;
1213                    inverseFlattening = 298.3;
1214                    name = "Krassowsky, 1942";
1215                    id = "7024";
1216                } else if ( "lerch".equalsIgnoreCase( ellipsoidName ) ) {
1217                    semiMajorAxis = 6378139.;
1218                    inverseFlattening = 298.257;
1219                    name = "Lerch 1979";
1220                    // no epsg
1221                } else if ( "mod_airy".equalsIgnoreCase( ellipsoidName ) ) {
1222                    semiMajorAxis = 6377340.189;
1223                    semiMinorAxis = 6356034.446;
1224                    name = "Modified Airy";
1225                    id = "7002";
1226                } else if ( "mprts".equalsIgnoreCase( ellipsoidName ) ) {
1227                    semiMajorAxis = 6397300.;
1228                    inverseFlattening = 191.;
1229                    name = "Maupertius 1738";
1230                    // no epsg
1231                } else if ( "new_intl".equalsIgnoreCase( ellipsoidName ) ) {
1232                    semiMajorAxis = 6378157.5;
1233                    semiMinorAxis = 6356772.2;
1234                    name = "New International 1967";
1235                    id = "7036";
1236                } else if ( "plessis".equalsIgnoreCase( ellipsoidName ) ) {
1237                    semiMajorAxis = 6376523.;
1238                    semiMinorAxis = 6355863.;
1239                    name = "Plessis 1817 (France)";
1240                    id = "7027";
1241                } else if ( "sphere".equalsIgnoreCase( ellipsoidName ) ) {
1242                    semiMajorAxis = 6370997.0;
1243                    semiMinorAxis = 6370997.0;
1244                    name = "Normal Sphere (r=6370997)";
1245                } else if ( "walbeck".equalsIgnoreCase( ellipsoidName ) ) {
1246                    semiMajorAxis = 6376896.0;
1247                    semiMinorAxis = 6355834.8467;
1248                    name = "Walbeck";
1249                    // epsg closed
1250                } else {
1251                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_UNKNOWN_ELLIPSOID",
1252                                                                              ellipsoidName ) );
1253                }
1254                String[] ids = new String[] { id };
1255                if ( !ellipsoidName.equals( id ) ) {
1256                    ids = new String[] { EPSG_PRE + id, OGC_URN + id, OPENGIS_URL + id, OPENGIS_URN + id };
1257                }
1258                Ellipsoid ellips = null;
1259                if ( Double.isNaN( semiMinorAxis ) ) {
1260                    ellips = new Ellipsoid( semiMajorAxis, Unit.METRE, inverseFlattening, ids, new String[] { name }, null,
1261                                            null, null );
1262                } else {// semiMinorAxis was given.
1263                    ellips = new Ellipsoid( Unit.METRE, semiMajorAxis, semiMinorAxis, ids, new String[] { name }, null,
1264                                            null, null );
1265                }
1266                return ellips;
1267            }
1268            return null;
1269        }
1270    
1271        /**
1272         * Helper method to parse day, month second formats. With a little help from com.jhlabs.map.AngleFormat
1273         *
1274         * @param text
1275         *            to pe parsed into degrees (or radians).
1276         * @param toDegrees
1277         *            if the given text is in degrees
1278         * @return a
1279         */
1280        private double parseAngleFormat( String text, boolean toDegrees ) {
1281            double d = 0, m = 0, s = 0;
1282            double result;
1283            boolean negate = false;
1284            int length = text.length();
1285            if ( length > 0 ) {
1286                char c = Character.toUpperCase( text.charAt( length - 1 ) );
1287                switch ( c ) {
1288                case 'W':
1289                case 'S':
1290                    negate = true;
1291                case 'E':
1292                case 'N':
1293                    text = text.substring( 0, length - 1 );
1294                    break;
1295                }
1296            }
1297            int i = text.indexOf( 'd' );
1298            if ( i == -1 )
1299                i = text.indexOf( '\u00b0' );
1300            if ( i != -1 ) {
1301                String dd = text.substring( 0, i );
1302                String mmss = text.substring( i + 1 );
1303                d = Double.valueOf( dd ).doubleValue();
1304                i = mmss.indexOf( 'm' );
1305                if ( i == -1 )
1306                    i = mmss.indexOf( '\'' );
1307                if ( i != -1 ) {
1308                    if ( i != 0 ) {
1309                        String mm = mmss.substring( 0, i );
1310                        m = Double.valueOf( mm ).doubleValue();
1311                    }
1312                    if ( mmss.endsWith( "s" ) || mmss.endsWith( "\"" ) )
1313                        mmss = mmss.substring( 0, mmss.length() - 1 );
1314                    if ( i != mmss.length() - 1 ) {
1315                        String ss = mmss.substring( i + 1 );
1316                        s = Double.valueOf( ss ).doubleValue();
1317                    }
1318                    if ( m < 0 || m > 59 )
1319                        throw new NumberFormatException( "Minutes must be between 0 and 59" );
1320                    if ( s < 0 || s >= 60 )
1321                        throw new NumberFormatException( "Seconds must be between 0 and 59" );
1322                } else if ( i != 0 ) {
1323                    m = Double.valueOf( mmss ).doubleValue();
1324                }
1325                if ( toDegrees ) {
1326                    result = dmsToDeg( d, m, s );
1327                } else {
1328                    result = dmsToRad( d, m, s );
1329                }
1330            } else {
1331                result = Double.parseDouble( text );
1332                if ( !toDegrees )
1333                    result = Math.toRadians( result );
1334            }
1335            if ( negate ) {// South
1336                result = -result;
1337            }
1338            return result;
1339        }
1340    
1341        /**
1342         * Converts angle information to radians. With a little help from com.jhlabs.map.MapMath. For negative angles, d
1343         * should be negative, m & s positive.
1344         *
1345         * @param d
1346         *            days
1347         * @param m
1348         *            months
1349         * @param s
1350         *            seconds.
1351         * @return the converted value in radians.
1352         */
1353        private double dmsToRad( double d, double m, double s ) {
1354            if ( d >= 0 ) {
1355                return ( d + m / 60 + s / 3600 ) * Math.PI / 180.0;
1356            }
1357            return ( d - m / 60 - s / 3600 ) * Math.PI / 180.0;
1358        }
1359    
1360        /**
1361         * Converts angle information to degrees. With a little help from com.jhlabs.map.MapMath. For negative angles, d
1362         * should be negative, m & s positive.
1363         *
1364         * @param d
1365         *            days
1366         * @param m
1367         *            minutes
1368         * @param s
1369         *            seconds.
1370         * @return the converted value in degrees.
1371         */
1372        private double dmsToDeg( double d, double m, double s ) {
1373            if ( d >= 0 ) {
1374                return ( d + m / 60 + s / 3600 );
1375            }
1376            return ( d - m / 60 - s / 3600 );
1377        }
1378    
1379        public List<String> getAvailableCRSIds()
1380                                throws CRSConfigurationException {
1381            Set<String> keys = getResolver().getAvailableIDs();
1382            List<String> result = new LinkedList<String>();
1383            result.addAll( keys );
1384            return result;
1385        }
1386    
1387        public Identifiable getIdentifiable( String id )
1388                                throws CRSConfigurationException {
1389            Identifiable result = getCachedIdentifiable( id );
1390            if ( result == null ) {
1391                throw new UnsupportedOperationException(
1392                                                         "The retrieval of an arbitrary Identifiable Object is currently not supported by the proj 4 provider." );
1393            }
1394            return result;
1395        }
1396    
1397        @Override
1398        protected CoordinateSystem parseCoordinateSystem( Map<String, String> crsDefinition )
1399                                throws CRSConfigurationException {
1400    
1401            String crsType = crsDefinition.remove( "proj" );
1402            if ( crsType == null || "".equals( crsType.trim() ) ) {
1403                LOG.logDebug( "The given params contain: " + crsDefinition );
1404                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_NO_PROJ_PARAM",
1405                                                                          crsDefinition.get( EPSG_PRE + "identifier" ) ) );
1406            }
1407            crsType = crsType.trim();
1408            if ( "longlat".equals( crsType ) ) {
1409                // the geo-crs should find it's own id and has no parent projected crs (null, null).
1410                return createGeographicCRS( null, null, crsDefinition );
1411            }
1412            return createProjectedCRS( crsType, crsDefinition );
1413        }
1414    
1415        @Override
1416        public Transformation parseTransformation( Map<String, String> transformationDefinition )
1417                                throws CRSConfigurationException {
1418            throw new UnsupportedOperationException(
1419                                                     "Parsing of transformation parameters is not applicable for proj4 configuration files yet." );
1420        }
1421    
1422        public Transformation getTransformation( CoordinateSystem sourceCRS, CoordinateSystem targetCRS )
1423                                throws CRSConfigurationException {
1424            return getResolver().getTransformation( sourceCRS, targetCRS );
1425        }
1426    }