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