    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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: lbuesching $
    085     *
    086     * @version $Revision: 23199 $, $Date: 2010-03-23 08:40:50 +0100 (Di, 23. Mär 2010) $
    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
    1343         * negative angles, d 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
    1362         * negative angles, d 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[]> getSortedAvailableCRSIds()
    1380                                throws CRSConfigurationException {
    1381            Set<String> keys = getResolver().getAvailableIDs();
    1382            List<String[]> result = new LinkedList<String[]>();
    1383            for ( String key : keys ) {
    1384                result.add( new String[] { key } );
    1385            }
    1386            return result;
    1387        }
    1388
    1389        public List<String> getAvailableCRSIds()
    1390                                throws CRSConfigurationException {
    1391            Set<String> keys = getResolver().getAvailableIDs();
    1392            List<String> result = new LinkedList<String>();
    1393            result.addAll( keys );
    1394            return result;
    1395        }
    1396
    1397        public Identifiable getIdentifiable( String id )
    1398                                throws CRSConfigurationException {
    1399            Identifiable result = getCachedIdentifiable( id );
    1400            if ( result == null ) {
    1401                throw new UnsupportedOperationException(
    1402                                                         "The retrieval of an arbitrary Identifiable Object is currently not supported by the proj 4 provider." );
    1403            }
    1404            return result;
    1405        }
    1406
    1407        @Override
    1408        protected CoordinateSystem parseCoordinateSystem( Map<String, String> crsDefinition )
    1409                                throws CRSConfigurationException {
    1410
    1411            String crsType = crsDefinition.remove( "proj" );
    1412            if ( crsType == null || "".equals( crsType.trim() ) ) {
    1413                LOG.logDebug( "The given params contain: " + crsDefinition );
    1414                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJ4_NO_PROJ_PARAM",
    1415                                                                          crsDefinition.get( EPSG_PRE + "identifier" ) ) );
    1416            }
    1417            crsType = crsType.trim();
    1418            if ( "longlat".equals( crsType ) ) {
    1419                // the geo-crs should find it's own id and has no parent projected crs (null, null).
    1420                return createGeographicCRS( null, null, crsDefinition );
    1421            }
    1422            return createProjectedCRS( crsType, crsDefinition );
    1423        }
    1424
    1425        @Override
    1426        public Transformation parseTransformation( Map<String, String> transformationDefinition )
    1427                                throws CRSConfigurationException {
    1428            throw new UnsupportedOperationException(
    1429                                                     "Parsing of transformation parameters is not applicable for proj4 configuration files yet." );
    1430        }
    1431
    1432        public Transformation getTransformation( CoordinateSystem sourceCRS, CoordinateSystem targetCRS )
    1433                                throws CRSConfigurationException {
    1434            return getResolver().getTransformation( sourceCRS, targetCRS );
    1435        }
    1436    }