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.FileInputStream;
044    import java.io.FileNotFoundException;
045    import java.io.IOException;
046    import java.io.InputStream;
047    import java.io.InputStreamReader;
048    import java.io.Reader;
049    import java.net.MalformedURLException;
050    import java.util.ArrayList;
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.Identifiable;
059    import org.deegree.crs.components.Axis;
060    import org.deegree.crs.components.Ellipsoid;
061    import org.deegree.crs.components.GeodeticDatum;
062    import org.deegree.crs.components.PrimeMeridian;
063    import org.deegree.crs.components.Unit;
064    import org.deegree.crs.coordinatesystems.CoordinateSystem;
065    import org.deegree.crs.coordinatesystems.GeocentricCRS;
066    import org.deegree.crs.coordinatesystems.GeographicCRS;
067    import org.deegree.crs.coordinatesystems.ProjectedCRS;
068    import org.deegree.crs.exceptions.CRSConfigurationException;
069    import org.deegree.crs.projections.Projection;
070    import org.deegree.crs.projections.ProjectionUtils;
071    import org.deegree.crs.projections.azimuthal.LambertAzimuthalEqualArea;
072    import org.deegree.crs.projections.azimuthal.StereographicAzimuthal;
073    import org.deegree.crs.projections.conic.LambertConformalConic;
074    import org.deegree.crs.projections.cylindric.TransverseMercator;
075    import org.deegree.crs.transformations.WGS84ConversionInfo;
076    import org.deegree.datatypes.QualifiedName;
077    import org.deegree.framework.log.ILogger;
078    import org.deegree.framework.log.LoggerFactory;
079    import org.deegree.framework.xml.NamespaceContext;
080    import org.deegree.framework.xml.XMLFragment;
081    import org.deegree.framework.xml.XMLParsingException;
082    import org.deegree.framework.xml.XMLTools;
083    import org.deegree.i18n.Messages;
084    import org.deegree.ogcbase.CommonNamespaces;
085    import org.w3c.dom.Document;
086    import org.w3c.dom.Element;
087    import org.xml.sax.SAXException;
088    
089    /**
090     * The <code>DeegreeCRSProvider</code> reads the deegree crs-config (based on it's own xml-schema) and creates the
091     * CRS's (and their datums, conversion info's, ellipsoids and projections) if requested.
092     * <p>
093     * Attention, although urn's are case-sensitive, the deegreeCRSProvider is not. All incoming id's are toLowerCased!
094     * </p>
095     * 
096     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
097     * 
098     * @author last edited by: $Author:$
099     * 
100     * @version $Revision:$, $Date:$
101     * 
102     */
103    
104    public class DeegreeCRSProvider implements CRSProvider {
105    
106        private static ILogger LOG = LoggerFactory.getLogger( DeegreeCRSProvider.class );
107    
108        /**
109         * The standard configuration file, points to deegree-crs-configuration.xml.
110         */
111        private static final String STANDARD_CONFIG = "deegree-crs-configuration.xml";
112    
113        /**
114         * The mamespaces used in deegree.
115         */
116        private static NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
117    
118        /**
119         * The prefix to use.
120         */
121        private final static String PRE = CommonNamespaces.CRS_PREFIX + ":";
122    
123        /**
124         * The namespace to use.
125         */
126        private final static String CRS_URI = CommonNamespaces.CRSNS.toASCIIString();
127    
128        /**
129         * The EPSG-Database defines only 48 different ellipsoids, set to 60, will --probably-- result in no collisions.
130         */
131        private final Map<String, Ellipsoid> ellipsoids = new HashMap<String, Ellipsoid>( 60 );
132    
133        /**
134         * The EPSG-Database defines over 400 different Geodetic Datums, set to 450, will --probably-- result in no
135         * collisions.
136         */
137        private final Map<String, GeodeticDatum> datums = new HashMap<String, GeodeticDatum>( 450 );
138    
139        /**
140         * The EPSG-Database defines over 1100 different CoordinateTransformations, set to 1200, will --probably-- result in
141         * no collisions.
142         */
143        private final Map<String, WGS84ConversionInfo> conversionInfos = new HashMap<String, WGS84ConversionInfo>( 1200 );
144    
145        /**
146         * Theoretically infinite prime meridians could be defined, let's set it to a more practical number of 42.
147         */
148        private final Map<String, PrimeMeridian> primeMeridians = new HashMap<String, PrimeMeridian>( 42 );
149    
150        /**
151         * The EPSG-Database defines over 2960 different ProjectedCRS's, set to 3500, will --probably-- result in no
152         * collisions.
153         */
154        private final Map<String, Projection> projections = new HashMap<String, Projection>( 3500 );
155    
156        /**
157         * The EPSG-Database defines over 2960 different ProjectedCRS's, set to 3500, will --probably-- result in no
158         * collisions.
159         */
160        private final Map<String, ProjectedCRS> projectedCRSs = new HashMap<String, ProjectedCRS>( 3500 );
161    
162        /**
163         * The EPSG-Database defines over 490 different GeographicCRS's (geodetic), set to 600, will --probably-- result in
164         * no collisions.
165         */
166        private final Map<String, GeographicCRS> geographicCRSs = new HashMap<String, GeographicCRS>( 600 );
167    
168        /**
169         * The EPSG-Database doesn't define GeocentricCRS's, set to 30, will --probably-- result in no collisions.
170         */
171        private final Map<String, GeocentricCRS> geocentricCRSs = new HashMap<String, GeocentricCRS>( 30 );
172    
173        private final List<GeographicCRS> cachedGeoCRSs = new ArrayList<GeographicCRS>( 3000 );
174    
175        private final Map<String, String> doubleGeos = new HashMap<String, String>( 3000 );
176    
177        private final List<GeodeticDatum> cachedDatums = new ArrayList<GeodeticDatum>( 3000 );
178    
179        private final Map<String, String> doubleDatums = new HashMap<String, String>( 3000 );
180    
181        private final List<WGS84ConversionInfo> cachedToWGS = new ArrayList<WGS84ConversionInfo>( 3000 );
182    
183        private final Map<String, String> doubleToWGS = new HashMap<String, String>( 3000 );
184    
185        private final List<Ellipsoid> cachedEllipsoids = new ArrayList<Ellipsoid>( 3000 );
186    
187        private final Map<String, String> doubleEllipsoids = new HashMap<String, String>( 3000 );
188    
189        private final List<PrimeMeridian> cachedMeridans = new ArrayList<PrimeMeridian>( 3000 );
190    
191        private final Map<String, String> doubleMeridians = new HashMap<String, String>( 3000 );
192    
193        private final List<Projection> cachedProjections = new ArrayList<Projection>( 3000 );
194    
195        private final Map<String, String> doubleProjections = new HashMap<String, String>( 3000 );
196    
197        /**
198         * The root element of the deegree - crs - configuration.
199         */
200        private Element rootElement;
201    
202        private boolean checkForDoubleDefinition = false;
203    
204        /**
205         * Empty constructor may only be used for exporting, other usage will result in undefined behavior.
206         */
207        public DeegreeCRSProvider() {
208            Document doc = XMLTools.create();
209            rootElement = doc.createElementNS( CommonNamespaces.CRSNS.toASCIIString(), PRE + "definitions" );
210            rootElement = (Element) doc.importNode( rootElement, false );
211            rootElement = (Element) doc.appendChild( rootElement );
212        }
213    
214        /**
215         * @param f
216         *            to load coordinate system definitions from if null, the standard configuration file will be used, by
217         *            searching '/' first and if unsuccessful in org.deegree.crs.configuration.
218         * @throws CRSConfigurationException
219         *             if the give file or the default-crs-configuration.xml file could not be loaded.
220         */
221        public DeegreeCRSProvider( File f ) throws CRSConfigurationException {
222            InputStream is = null;
223            if ( f == null ) {
224                LOG.logDebug( "No configuration file given, trying to load standard-crs-configurtiion.xml" );
225                is = DeegreeCRSProvider.class.getResourceAsStream( "/" + STANDARD_CONFIG );
226                if ( is == null ) {
227                    is = DeegreeCRSProvider.class.getResourceAsStream( STANDARD_CONFIG );
228                }
229                if ( is == null ) {
230                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_DEFAULT_CONFIG_FOUND" ) );
231                }
232            } else {
233                LOG.logDebug( "Trying to load configuration from file: " + f.getAbsoluteFile() );
234                try {
235                    is = new FileInputStream( f );
236                } catch ( FileNotFoundException e ) {
237                    throw new CRSConfigurationException( e );
238                }
239            }
240            if ( is == null ) {
241                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_CONFIG_FOUND" ) );
242            }
243            Reader read = new BufferedReader( new InputStreamReader( is ) );
244            try {
245                XMLFragment configDocument = new XMLFragment( read, XMLFragment.DEFAULT_URL );
246                rootElement = configDocument.getRootElement();
247            } catch ( MalformedURLException e ) {
248                throw new CRSConfigurationException( e );
249            } catch ( IOException e ) {
250                throw new CRSConfigurationException( e );
251            } catch ( SAXException e ) {
252                throw new CRSConfigurationException( e );
253            } finally {
254                try {
255                    read.close();
256                } catch ( IOException e ) {
257                    // could not close the stream, just leave it as it is.
258                }
259            }
260        }
261    
262        public boolean canExport() {
263            return true;
264        }
265    
266        public synchronized CoordinateSystem getCRSByID( String crsId )
267                                                                       throws CRSConfigurationException {
268            if ( crsId != null && !"".equals( crsId.trim() ) ) {
269    
270                crsId = crsId.toUpperCase().trim();
271                LOG.logDebug( "Trying to load crs with id: " + crsId + " from cache." );
272                if ( geographicCRSs.containsKey( crsId ) && geographicCRSs.get( crsId ) != null ) {
273                    return geographicCRSs.get( crsId );
274                } else if ( projectedCRSs.containsKey( crsId ) && projectedCRSs.get( crsId ) != null ) {
275                    return projectedCRSs.get( crsId );
276                } else if ( geocentricCRSs.containsKey( crsId ) && geocentricCRSs.get( crsId ) != null ) {
277                    return geocentricCRSs.get( crsId );
278                }
279                LOG.logDebug( "No crs with id: " + crsId + " found in cache." );
280                if ( rootElement == null ) {
281                    this.notifyAll();
282                    return null;
283                }
284                Element crsElement = getTopElementFromID( crsId );
285                if ( crsElement == null ) {
286                    LOG.logDebug( "The requested crs id: " + crsId
287                                  + " could not be mapped to a configured CoordinateSystem." );
288                    this.notifyAll();
289                    return null;
290                }
291                String crsType = crsElement.getLocalName();
292                if ( crsType == null || "".equals( crsType.trim() ) ) {
293                    LOG.logDebug( "The requested crs id: " + crsId
294                                  + " could not be mapped to a configured CoordinateSystem." );
295                    this.notifyAll();
296                    return null;
297                }
298                if ( "geographicCRS".equalsIgnoreCase( crsType ) ) {
299                    return parseGeographicCRS( crsElement );
300                } else if ( "projectedCRS".equalsIgnoreCase( crsType ) ) {
301                    return parseProjectedCRS( crsElement );
302                } else if ( "geocentricCRS".equalsIgnoreCase( crsType ) ) {
303                    return parseGeocentricCRS( crsElement );
304                }
305            }
306            LOG.logDebug( "The id: " + crsId
307                          + " could not be mapped to a valid deegreec-crs, currently projectedCRS, geographicCRS and geocentricCRS are supported." );
308            return null;
309        }
310    
311        public void export( StringBuilder sb, List<CoordinateSystem> crsToExport ) {
312            if ( crsToExport != null ) {
313                if ( crsToExport.size() != 0 ) {
314                    LOG.logDebug( "Trying to export: " + crsToExport.size() + " coordinate systems." );
315                    XMLFragment frag = new XMLFragment( new QualifiedName( "crs", "definitions", CommonNamespaces.CRSNS ) );
316                    Element root = frag.getRootElement();
317                    ArrayList<String> exportedIDs = new ArrayList<String>( crsToExport.size() );
318                    for ( CoordinateSystem crs : crsToExport ) {
319                        if ( crs.getType() == CoordinateSystem.GEOCENTRIC_CRS ) {
320                            export( (GeocentricCRS) crs, root, exportedIDs );
321                        } else if ( crs.getType() == CoordinateSystem.GEOGRAPHIC_CRS ) {
322                            export( (GeographicCRS) crs, root, exportedIDs );
323                        } else if ( crs.getType() == CoordinateSystem.PROJECTED_CRS ) {
324                            export( (ProjectedCRS) crs, root, exportedIDs );
325                        }
326                    }
327                    root.normalize();
328                    Document validDoc = createValidDocument( root );
329                    try {
330                        XMLFragment frag2 = new XMLFragment( validDoc, "http://www.deegree.org/crs" );
331                        sb.append( frag2.getAsPrettyString() );
332                    } catch ( MalformedURLException e ) {
333                        LOG.logError( "Could not export crs definitions because: " + e.getMessage(), e );
334                    }
335                } else {
336                    LOG.logWarning( "No coordinate system were given (list.size() == 0)." );
337                }
338            } else {
339                LOG.logError( "No coordinate system were given (list == null)." );
340            }
341        }
342    
343        public List<CoordinateSystem> getAvailableCRSs()
344                                                        throws CRSConfigurationException {
345            List<CoordinateSystem> allSystems = new ArrayList<CoordinateSystem>( 10000 );
346            if ( rootElement != null ) {
347                List<Element> allCRSIDs = new ArrayList<Element>( 10000 );
348    
349                try {
350                    allCRSIDs.addAll( XMLTools.getElements( rootElement,
351                                                            "//" + PRE + "geographicCRS/" + PRE + "id[1]",
352                                                            nsContext ) );
353                    allCRSIDs.addAll( XMLTools.getElements( rootElement,
354                                                            "//" + PRE + "projectedCRS/" + PRE + "id[1]",
355                                                            nsContext ) );
356                    allCRSIDs.addAll( XMLTools.getElements( rootElement,
357                                                            "//" + PRE + "geocentricCRS/" + PRE + "id[1]",
358                                                            nsContext ) );
359                } catch ( XMLParsingException e ) {
360                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_GET_ALL_ELEMENT_IDS",
361                                                                              e.getMessage() ), e );
362                }
363                for ( Element crsID : allCRSIDs ) {
364                    if ( crsID != null ) {
365                        String id = crsID.getTextContent();
366                        if ( id != null && !"".equals( id.trim() ) ) {
367                            CoordinateSystem crs = getCRSByID( id );
368                            if ( crs != null ) {
369                                allSystems.add( crs );
370                            }
371                        }
372    
373                    }
374                }
375                allSystems.addAll( geocentricCRSs.values() );
376                allSystems.addAll( geographicCRSs.values() );
377                allSystems.addAll( projectedCRSs.values() );
378                if ( checkForDoubleDefinition ) {
379                    if ( !doubleGeos.isEmpty() ) {
380                        Set<String> keys = doubleGeos.keySet();
381                        LOG.logInfo( "Following geographic crs's could probably be mapped on eachother" );
382                        for ( String key : keys ) {
383                            LOG.logInfo( key + " : " + doubleGeos.get( key ) );
384                        }
385                    }
386    
387                    if ( !doubleDatums.isEmpty() ) {
388                        Set<String> keys = doubleDatums.keySet();
389                        LOG.logInfo( "Following datums could probably be mapped on eachother" );
390                        for ( String key : keys ) {
391                            LOG.logInfo( key + " : " + doubleDatums.get( key ) );
392                        }
393                    }
394                    if ( !doubleToWGS.isEmpty() ) {
395                        Set<String> keys = doubleToWGS.keySet();
396                        LOG.logInfo( "Following wgs conversion infos could probably be mapped on eachother" );
397                        for ( String key : keys ) {
398                            LOG.logInfo( key + " : " + doubleToWGS.get( key ) );
399                        }
400                    }
401                    if ( !doubleEllipsoids.isEmpty() ) {
402                        Set<String> keys = doubleEllipsoids.keySet();
403                        LOG.logInfo( "Following ellipsoids could probably be mapped on eachother" );
404                        for ( String key : keys ) {
405                            LOG.logInfo( key + " : " + doubleEllipsoids.get( key ) );
406                        }
407                    }
408                    if ( !doubleMeridians.isEmpty() ) {
409                        Set<String> keys = doubleEllipsoids.keySet();
410                        LOG.logInfo( "Following prime meridians could probably be mapped on eachother" );
411                        for ( String key : keys ) {
412                            LOG.logInfo( key + " : " + doubleMeridians.get( key ) );
413                        }
414                    }
415                    if ( !doubleProjections.isEmpty() ) {
416                        Set<String> keys = doubleProjections.keySet();
417                        LOG.logInfo( "Following projections could probably be mapped on eachother" );
418                        for ( String key : keys ) {
419                            LOG.logInfo( key + " : " + doubleProjections.get( key ) );
420                        }
421                    }
422                }
423            } else {
424                LOG.logDebug( "The root element is null, is this correct behaviour?" );
425            }
426            return allSystems;
427        }
428    
429        private Document createValidDocument( Element root ) {
430            // List<Element> lastInput = new ArrayList<Element>( 100 );
431            try {
432                List<Element> valid = XMLTools.getElements( root, PRE + "ellipsoid", nsContext );
433                valid.addAll( XMLTools.getElements( root, PRE + "geodeticDatum", nsContext ) );
434                valid.addAll( XMLTools.getElements( root, PRE + "lambertAzimuthalEqualArea", nsContext ) );
435                valid.addAll( XMLTools.getElements( root, PRE + "lambertConformalConic", nsContext ) );
436                valid.addAll( XMLTools.getElements( root, PRE + "stereographicAzimuthal", nsContext ) );
437                valid.addAll( XMLTools.getElements( root, PRE + "transverseMercator", nsContext ) );
438                valid.addAll( XMLTools.getElements( root, PRE + "projectedCRS", nsContext ) );
439                valid.addAll( XMLTools.getElements( root, PRE + "geographicCRS", nsContext ) );
440                valid.addAll( XMLTools.getElements( root, PRE + "geocentricCRS", nsContext ) );
441                valid.addAll( XMLTools.getElements( root, PRE + "primeMeridian", nsContext ) );
442                valid.addAll( XMLTools.getElements( root, PRE + "wgs84Transformation", nsContext ) );
443                Document doc = XMLTools.create();
444                Element newRoot = doc.createElementNS( CommonNamespaces.CRSNS.toASCIIString(), PRE + "definitions" );
445                newRoot = (Element) doc.importNode( newRoot, false );
446                newRoot = (Element) doc.appendChild( newRoot );
447                for ( int i = 0; i < valid.size(); ++i ) {
448                    Element el = valid.get( i );
449                    el = (Element) doc.importNode( el, true );
450                    newRoot.appendChild( el );
451                }
452                XMLTools.appendNSBinding( newRoot, CommonNamespaces.XSI_PREFIX, CommonNamespaces.XSINS );
453                newRoot.setAttributeNS( CommonNamespaces.XSINS.toASCIIString(),
454                                        "xsi:schemaLocation",
455                                        "http://www.deegree.org/crs c:/windows/profiles/rutger/EIGE~VO5/eclipse-projekte/coordinate_systems/resources/schema/crsdefinition.xsd" );
456                return doc;
457            } catch ( XMLParsingException xmle ) {
458                xmle.printStackTrace();
459            }
460            return root.getOwnerDocument();
461        }
462    
463        /**
464         * Export the projected CRS to it's appropriate deegree-crs-definitions form.
465         * 
466         * @param projectedCRS
467         *            to be exported
468         * @param rootNode
469         *            to export the projected CRS to.
470         * @param exportedIds
471         *            a list of id's already exported.
472         */
473        private void export( ProjectedCRS projectedCRS, Element rootNode, List<String> exportedIds ) {
474            if ( !exportedIds.contains( projectedCRS.getIdentifier() ) ) {
475                Element crsElement = XMLTools.appendElement( rootNode, CommonNamespaces.CRSNS, PRE + "projectedCRS" );
476                exportAbstractCRS( projectedCRS, crsElement );
477                GeographicCRS underLyingCRS = projectedCRS.getGeographicCRS();
478                export( underLyingCRS, rootNode, exportedIds );
479                export( projectedCRS.getUnits(), crsElement );
480                // Add a reference from the geographicCRS element to the projectedCRS element.
481                XMLTools.appendElement( crsElement,
482                                        CommonNamespaces.CRSNS,
483                                        PRE + "usedGeographicCRS",
484                                        underLyingCRS.getIdentifier() );
485    
486                Projection projection = projectedCRS.getProjection();
487                export( projection, rootNode, exportedIds );
488                // Add a reference from the projection element to the projectedCRS element.
489                XMLTools.appendElement( crsElement,
490                                        CommonNamespaces.CRSNS,
491                                        PRE + "usedProjection",
492                                        projection.getIdentifier() );
493    
494                // Add the ids to the exportedID list.
495                for ( String eID : projectedCRS.getIdentifiers() ) {
496                    exportedIds.add( eID );
497                }
498                // finally add the crs node to the rootnode.
499                rootNode.appendChild( crsElement );
500            }
501        }
502    
503        /**
504         * Export the geocentric/geographic CRS to it's appropriate deegree-crs-definitions form.
505         * 
506         * @param geographicCRS
507         *            to be exported
508         * @param rootNode
509         *            to export the geographic CRS to.
510         * @param exportedIds
511         *            a list of id's already exported.
512         */
513        private void export( GeographicCRS geographicCRS, Element rootNode, List<String> exportedIds ) {
514            if ( !exportedIds.contains( geographicCRS.getIdentifier() ) ) {
515                Element crsElement = XMLTools.appendElement( rootNode, CommonNamespaces.CRSNS, PRE + "geographicCRS" );
516                exportAbstractCRS( geographicCRS, crsElement );
517    
518                // export the datum.
519                GeodeticDatum datum = geographicCRS.getGeodeticDatum();
520                if ( datum != null ) {
521                    export( datum, rootNode, exportedIds );
522                    // Add a reference from the datum element to the geographic element.
523                    XMLTools.appendElement( crsElement, CommonNamespaces.CRSNS, PRE + "usedDatum", datum.getIdentifier() );
524                }
525                // Add the ids to the exportedID list.
526                for ( String eID : geographicCRS.getIdentifiers() ) {
527                    exportedIds.add( eID );
528                }
529                // finally add the crs node to the rootnode.
530                rootNode.appendChild( crsElement );
531            }
532        }
533    
534        /**
535         * Export the projection to it's appropriate deegree-crs-definitions form.
536         * 
537         * @param projection
538         *            to be exported
539         * @param rootNode
540         *            to export the projection to.
541         * @param exportedIds
542         *            a list of id's already exported.
543         */
544        private void export( Projection projection, Element rootNode, List<String> exportedIds ) {
545            if ( !exportedIds.contains( projection.getIdentifier() ) ) {
546                String elementName = projection.getDeegreeSpecificName();
547                Element projectionElement = XMLTools.appendElement( rootNode, CommonNamespaces.CRSNS, PRE + elementName );
548                exportIdentifiable( projection, projectionElement );
549                Element tmp = XMLTools.appendElement( projectionElement,
550                                                      CommonNamespaces.CRSNS,
551                                                      PRE + "latitudeOfNaturalOrigin",
552                                                      Double.toString( Math.toDegrees( projection.getProjectionLatitude() ) ) );
553                tmp.setAttribute( "inDegrees", "true" );
554                tmp = XMLTools.appendElement( projectionElement,
555                                              CommonNamespaces.CRSNS,
556                                              PRE + "longitudeOfNaturalOrigin",
557                                              Double.toString( Math.toDegrees( projection.getProjectionLongitude() ) ) );
558                tmp.setAttribute( "inDegrees", "true" );
559    
560                XMLTools.appendElement( projectionElement,
561                                        CommonNamespaces.CRSNS,
562                                        PRE + "scaleFactor",
563                                        Double.toString( projection.getScale() ) );
564                XMLTools.appendElement( projectionElement,
565                                        CommonNamespaces.CRSNS,
566                                        PRE + "falseEasting",
567                                        Double.toString( projection.getFalseEasting() ) );
568                XMLTools.appendElement( projectionElement,
569                                        CommonNamespaces.CRSNS,
570                                        PRE + "falseNorthing",
571                                        Double.toString( projection.getFalseNorthing() ) );
572                if ( "transverseMercator".equalsIgnoreCase( elementName ) ) {
573                    XMLTools.appendElement( projectionElement,
574                                            CommonNamespaces.CRSNS,
575                                            PRE + "northernHemisphere",
576                                            Boolean.toString( ( (TransverseMercator) projection ).getHemisphere() ) );
577                } else if ( "lambertConformalConic".equalsIgnoreCase( elementName ) ) {
578                    double paralellLatitude = ( (LambertConformalConic) projection ).getFirstParallelLatitude();
579                    if ( !Double.isNaN( paralellLatitude ) && Math.abs( paralellLatitude ) > ProjectionUtils.EPS11 ) {
580                        paralellLatitude = Math.toDegrees( paralellLatitude );
581                        tmp = XMLTools.appendElement( projectionElement,
582                                                      CommonNamespaces.CRSNS,
583                                                      PRE + "firstParallelLatitude",
584                                                      Double.toString( paralellLatitude ) );
585                        tmp.setAttribute( "inDegrees", "true" );
586                    }
587                    paralellLatitude = ( (LambertConformalConic) projection ).getSecondParallelLatitude();
588                    if ( !Double.isNaN( paralellLatitude ) && Math.abs( paralellLatitude ) > ProjectionUtils.EPS11 ) {
589                        paralellLatitude = Math.toDegrees( paralellLatitude );
590                        tmp = XMLTools.appendElement( projectionElement,
591                                                      CommonNamespaces.CRSNS,
592                                                      PRE + "secondParallelLatitude",
593                                                      Double.toString( paralellLatitude ) );
594                        tmp.setAttribute( "inDegrees", "true" );
595                    }
596                } else if ( "stereographicAzimuthal".equalsIgnoreCase( elementName ) ) {
597                    tmp = XMLTools.appendElement( projectionElement,
598                                                  CommonNamespaces.CRSNS,
599                                                  PRE + "trueScaleLatitude",
600                                                  Double.toString( ( (StereographicAzimuthal) projection ).getTrueScaleLatitude() ) );
601                    tmp.setAttribute( "inDegrees", "true" );
602                }
603            }
604        }
605    
606        /**
607         * Export the confInvo to it's appropriate deegree-crs-definitions form.
608         * 
609         * @param confInvo
610         *            to be exported
611         * @param rootNode
612         *            to export the confInvo to.
613         * @param exportedIds
614         *            a list of id's already exported.
615         */
616        private void export( WGS84ConversionInfo confInvo, Element rootNode, final List<String> exportedIds ) {
617            if ( !exportedIds.contains( confInvo.getIdentifier() ) ) {
618                Element convElement = XMLTools.appendElement( rootNode, CommonNamespaces.CRSNS, PRE + "wgs84Transformation" );
619                exportIdentifiable( confInvo, convElement );
620    
621                XMLTools.appendElement( convElement,
622                                        CommonNamespaces.CRSNS,
623                                        PRE + "xAxisTranslation",
624                                        Double.toString( confInvo.dx ) );
625                XMLTools.appendElement( convElement,
626                                        CommonNamespaces.CRSNS,
627                                        PRE + "yAxisTranslation",
628                                        Double.toString( confInvo.dy ) );
629                XMLTools.appendElement( convElement,
630                                        CommonNamespaces.CRSNS,
631                                        PRE + "zAxisTranslation",
632                                        Double.toString( confInvo.dz ) );
633                XMLTools.appendElement( convElement,
634                                        CommonNamespaces.CRSNS,
635                                        PRE + "xAxisRotation",
636                                        Double.toString( confInvo.ex ) );
637                XMLTools.appendElement( convElement,
638                                        CommonNamespaces.CRSNS,
639                                        PRE + "yAxisRotation",
640                                        Double.toString( confInvo.ey ) );
641                XMLTools.appendElement( convElement,
642                                        CommonNamespaces.CRSNS,
643                                        PRE + "zAxisRotation",
644                                        Double.toString( confInvo.ez ) );
645                XMLTools.appendElement( convElement,
646                                        CommonNamespaces.CRSNS,
647                                        PRE + "scaleDifference",
648                                        Double.toString( confInvo.ppm ) );
649    
650                // Add the ids to the exportedID list.
651                for ( String eID : confInvo.getIdentifiers() ) {
652                    exportedIds.add( eID );
653                }
654    
655                // finally add the WGS84-Transformation node to the rootnode.
656                rootNode.appendChild( convElement );
657            }
658    
659        }
660    
661        /**
662         * Export the PrimeMeridian to it's appropriate deegree-crs-definitions form.
663         * 
664         * @param pMeridian
665         *            to be exported
666         * @param rootNode
667         *            to export the pMeridian to.
668         * @param exportedIds
669         *            a list of id's already exported.
670         */
671        private void export( PrimeMeridian pMeridian, Element rootNode, final List<String> exportedIds ) {
672            if ( !exportedIds.contains( pMeridian.getIdentifier() ) ) {
673                Element meridianElement = XMLTools.appendElement( rootNode, CommonNamespaces.CRSNS, PRE + "primeMeridian" );
674                exportIdentifiable( pMeridian, meridianElement );
675                export( pMeridian.getAngularUnit(), meridianElement );
676                XMLTools.appendElement( meridianElement,
677                                        CommonNamespaces.CRSNS,
678                                        PRE + "longitude",
679                                        Double.toString( pMeridian.getLongitude() ) );
680    
681                // Add the ids to the exportedID list.
682                for ( String eID : pMeridian.getIdentifiers() ) {
683                    exportedIds.add( eID );
684                }
685    
686                // finally add the prime meridian node to the rootnode.
687                rootNode.appendChild( meridianElement );
688            }
689        }
690    
691        /**
692         * Export the ellipsoid to it's appropriate deegree-crs-definitions form.
693         * 
694         * @param ellipsoid
695         *            to be exported
696         * @param rootNode
697         *            to export the ellipsoid to.
698         * @param exportedIds
699         *            a list of id's already exported.
700         */
701        private void export( Ellipsoid ellipsoid, Element rootNode, final List<String> exportedIds ) {
702            if ( !exportedIds.contains( ellipsoid.getIdentifier() ) ) {
703                Element ellipsoidElement = XMLTools.appendElement( rootNode, CommonNamespaces.CRSNS, PRE + "ellipsoid" );
704                exportIdentifiable( ellipsoid, ellipsoidElement );
705                XMLTools.appendElement( ellipsoidElement,
706                                        CommonNamespaces.CRSNS,
707                                        PRE + "semiMajorAxis",
708                                        Double.toString( ellipsoid.getSemiMajorAxis() ) );
709                XMLTools.appendElement( ellipsoidElement,
710                                        CommonNamespaces.CRSNS,
711                                        PRE + "inverseFlatting",
712                                        Double.toString( ellipsoid.getInverseFlattening() ) );
713                export( ellipsoid.getUnits(), ellipsoidElement );
714    
715                // Add the ids to the exportedID list.
716                for ( String eID : ellipsoid.getIdentifiers() ) {
717                    exportedIds.add( eID );
718                }
719                // finally add the ellipsoid node to the rootnode.
720                rootNode.appendChild( ellipsoidElement );
721            }
722        }
723    
724        /**
725         * Export the datum to it's appropriate deegree-crs-definitions form.
726         * 
727         * @param datum
728         *            to be exported
729         * @param rootNode
730         *            to export the datum to.
731         * @param exportedIds
732         *            a list of id's already exported.
733         */
734        private void export( GeodeticDatum datum, Element rootNode, List<String> exportedIds ) {
735            if ( !exportedIds.contains( datum.getIdentifier() ) ) {
736                Element datumElement = XMLTools.appendElement( rootNode, CommonNamespaces.CRSNS, PRE + "geodeticDatum" );
737                exportIdentifiable( datum, datumElement );
738                /**
739                 * EXPORT the ELLIPSOID
740                 */
741                Ellipsoid ellipsoid = datum.getEllipsoid();
742                if ( ellipsoid != null ) {
743                    export( ellipsoid, rootNode, exportedIds );
744                    // Add a reference from the ellipsoid element to the datum element.
745                    XMLTools.appendElement( datumElement,
746                                            CommonNamespaces.CRSNS,
747                                            PRE + "usedEllipsoid",
748                                            ellipsoid.getIdentifier() );
749                }
750    
751                /**
752                 * EXPORT the PRIME_MERIDIAN
753                 */
754                PrimeMeridian pMeridian = datum.getPrimeMeridian();
755                if ( pMeridian != null ) {
756                    export( pMeridian, rootNode, exportedIds );
757                    // Add a reference from the prime meridian element to the datum element.
758                    XMLTools.appendElement( datumElement,
759                                            CommonNamespaces.CRSNS,
760                                            PRE + "usedPrimeMeridian",
761                                            pMeridian.getIdentifier() );
762                }
763    
764                /**
765                 * EXPORT the WGS-84-Conversion INFO
766                 */
767                WGS84ConversionInfo confInvo = datum.getWGS84Conversion();
768                if ( confInvo != null ) {
769                    export( confInvo, rootNode, exportedIds );
770                    // Add a reference from the prime meridian element to the datum element.
771                    XMLTools.appendElement( datumElement,
772                                            CommonNamespaces.CRSNS,
773                                            PRE + "usedWGS84ConversionInfo",
774                                            confInvo.getIdentifier() );
775                }
776    
777                // Add the ids to the exportedID list.
778                for ( String eID : datum.getIdentifiers() ) {
779                    exportedIds.add( eID );
780                }
781                // finally add the datum node to the rootnode.
782                rootNode.appendChild( datumElement );
783            }
784        }
785    
786        /**
787         * Export toplevel crs features.
788         * 
789         * @param crs
790         *            to be exported
791         * @param crsElement
792         *            to export to
793         */
794        private void exportAbstractCRS( CoordinateSystem crs, Element crsElement ) {
795            exportIdentifiable( crs, crsElement );
796            Axis[] axis = crs.getAxis();
797            StringBuilder axisOrder = new StringBuilder( 200 );
798            for ( int i = 0; i < axis.length; ++i ) {
799                Axis a = axis[i];
800                export( a, crsElement );
801                axisOrder.append( a.getName() );
802                if ( ( i + 1 ) < axis.length ) {
803                    axisOrder.append( ", " );
804                }
805            }
806            XMLTools.appendElement( crsElement, CommonNamespaces.CRSNS, PRE + "axisOrder", axisOrder.toString() );
807    
808        }
809    
810        /**
811         * Export the geocentric CRS to it's appropriate deegree-crs-definitions form.
812         * 
813         * @param geocentricCRS
814         *            to be exported
815         * @param rootNode
816         *            to export the geocentric CRS to.
817         * @param exportedIds
818         *            a list of id's already exported.
819         */
820        private void export( GeocentricCRS geocentricCRS, Element rootNode, List<String> exportedIds ) {
821            if ( !exportedIds.contains( geocentricCRS.getIdentifier() ) ) {
822                Element crsElement = XMLTools.appendElement( rootNode, CommonNamespaces.CRSNS, PRE + "geocentricCRS" );
823                exportAbstractCRS( geocentricCRS, crsElement );
824                // export the datum.
825                GeodeticDatum datum = geocentricCRS.getGeodeticDatum();
826                if ( datum != null ) {
827                    export( datum, rootNode, exportedIds );
828                    // Add a reference from the datum element to the geocentric element.
829                    XMLTools.appendElement( crsElement, CommonNamespaces.CRSNS, PRE + "usedDatum", datum.getIdentifier() );
830                }
831                // Add the ids to the exportedID list.
832                for ( String eID : geocentricCRS.getIdentifiers() ) {
833                    exportedIds.add( eID );
834                }
835                // finally add the crs node to the rootnode.
836                rootNode.appendChild( crsElement );
837            }
838        }
839    
840        /**
841         * Creates the basic nodes of the identifiable object.
842         * 
843         * @param id
844         *            object to be exported.
845         * @param currentNode
846         *            to expand
847         */
848        private void exportIdentifiable( Identifiable id, Element currentNode ) {
849            for ( String i : id.getIdentifiers() ) {
850                if ( i != null ) {
851                    XMLTools.appendElement( currentNode, CommonNamespaces.CRSNS, PRE + "id", i );
852                }
853    
854            }
855            if ( id.getNames() != null && id.getNames().length > 0 ) {
856                for ( String i : id.getNames() ) {
857                    if ( i != null ) {
858                        XMLTools.appendElement( currentNode, CommonNamespaces.CRSNS, PRE + "name", i );
859                    }
860                }
861            }
862            if ( id.getVersions() != null && id.getVersions().length > 0 ) {
863                for ( String i : id.getVersions() ) {
864                    if ( i != null ) {
865                        XMLTools.appendElement( currentNode, CommonNamespaces.CRSNS, PRE + "version", i );
866                    }
867                }
868            }
869            if ( id.getDescriptions() != null && id.getDescriptions().length > 0 ) {
870                for ( String i : id.getDescriptions() ) {
871                    if ( i != null ) {
872                        XMLTools.appendElement( currentNode, CommonNamespaces.CRSNS, PRE + "description", i );
873                    }
874                }
875            }
876            if ( id.getAreasOfUse() != null && id.getAreasOfUse().length > 0 ) {
877                for ( String i : id.getAreasOfUse() ) {
878                    if ( i != null ) {
879                        XMLTools.appendElement( currentNode, CommonNamespaces.CRSNS, PRE + "areaOfUse", i );
880                    }
881                }
882            }
883        }
884    
885        /**
886         * Export an axis to xml in the crs-definitions schema layout.
887         * 
888         * @param axis
889         *            to be exported.
890         * @param currentNode
891         *            to export to.
892         */
893        private void export( Axis axis, Element currentNode ) {
894            Document doc = currentNode.getOwnerDocument();
895            Element axisElement = doc.createElementNS( CRS_URI, PRE + "Axis" );
896            // The name.
897            XMLTools.appendElement( axisElement, CommonNamespaces.CRSNS, PRE + "name", axis.getName() );
898    
899            // the units.
900            Unit units = axis.getUnits();
901            export( units, axisElement );
902    
903            XMLTools.appendElement( axisElement,
904                                    CommonNamespaces.CRSNS,
905                                    PRE + "axisOrientation",
906                                    axis.getOrientationAsString() );
907            currentNode.appendChild( axisElement );
908        }
909    
910        /**
911         * Export a unit to xml in the crs-definitions schema layout. If the given units are radians, the output will be
912         * transformed to degrees (because of the export-to-degree policy).
913         * 
914         * @param units
915         *            to be exported.
916         * @param currentNode
917         *            to export to.
918         */
919        private void export( Unit units, Element currentNode ) {
920            if ( units != null && currentNode != null ) {
921                String tmp = units.getName();
922                if ( units.equals( Unit.RADIAN ) ) {
923                    tmp = Unit.DEGREE.getName();
924                }
925                XMLTools.appendElement( currentNode, CommonNamespaces.CRSNS, PRE + "units", tmp );
926            }
927        }
928    
929        /**
930         * @param crsElement
931         *            from which the crs is to be created (using cached datums, conversioninfos and projections).
932         * 
933         * @return a geographic coordinatesystem based on the given xml-element.
934         * @throws CRSConfigurationException
935         *             if a required element could not be found, or an xmlParsingException occurred.
936         */
937        private CoordinateSystem parseGeographicCRS( Element crsElement )
938                                                                         throws CRSConfigurationException {
939            Identifiable id = parseIdentifiable( crsElement );
940            Axis[] axis = parseAxisOrder( crsElement );
941    
942            // get the datum
943            GeodeticDatum usedDatum = parseReferencedGeodeticDatum( crsElement, id.getIdentifier() );
944            GeographicCRS result = new GeographicCRS( usedDatum, axis, id );
945            for ( String s : id.getIdentifiers() ) {
946                LOG.logDebug( "Adding id: " + s + "to geographic crs cache." );
947                geographicCRSs.put( s, result );
948            }
949            if ( checkForDoubleDefinition ) {
950                checkForUniqueness( cachedGeoCRSs, doubleGeos, result );
951            }
952            // remove the child, thus resulting in a smaller dom tree.
953            rootElement.removeChild( crsElement );
954            return result;
955        }
956    
957        /**
958         * Reads an element from the configuration with xpath *[crs:id='givenID'] and returns the found element.
959         * 
960         * @param id
961         *            to search for
962         * @return the element or <code>null</code> if no such element was found.
963         */
964        private Element getTopElementFromID( String id ) {
965            if ( rootElement == null ) {
966                LOG.logDebug( "The Root element is null, hence no crs's are available" );
967                return null;
968            }
969            Element crsElement = null;
970            // String xPath ="//*[crs:id='EPSG:31466']";
971            String xPath = "*[" + PRE + "id='" + id + "']";
972            try {
973                crsElement = XMLTools.getElement( rootElement, xPath, nsContext );
974            } catch ( XMLParsingException e ) {
975                LOG.logError( Messages.getMessage( "CRS_CONFIG_NO_RESULT_FOR_ID", id, e.getMessage() ), e );
976            }
977            LOG.logDebug( "Trying to find elements with xpath: " + xPath
978                          + ( ( crsElement == null ) ? " [failed]" : " [success]" ) );
979            return crsElement;
980        }
981    
982        /**
983         * @param datumID
984         * @return the
985         * @throws CRSConfigurationException
986         */
987        private GeodeticDatum getGeodeticDatumFromID( String datumID )
988                                                                      throws CRSConfigurationException {
989            if ( datumID != null && !"".equals( datumID.trim() ) ) {
990                datumID = datumID.trim();
991                if ( datums.containsKey( datumID ) && datums.get( datumID ) != null ) {
992                    return datums.get( datumID );
993                }
994                Element datumElement = getTopElementFromID( datumID );
995                if ( datumElement == null ) {
996                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_ELEMENT", "datum", datumID ) );
997                }
998                // get the identifiable.
999                Identifiable id = parseIdentifiable( datumElement );
1000    
1001                // get the ellipsoid.
1002                Ellipsoid ellipsoid = null;
1003                try {
1004                    String ellipsID = XMLTools.getRequiredNodeAsString( datumElement, PRE + "usedEllipsoid", nsContext );
1005                    if ( ellipsID != null && !"".equals( ellipsID.trim() ) ) {
1006                        ellipsoid = getEllipsoidFromID( ellipsID );
1007                    }
1008                } catch ( XMLParsingException e ) {
1009                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1010                                                                              "usedEllipsoid",
1011                                                                              ( ( datumElement == null ) ? "null"
1012                                                                                                        : datumElement.getLocalName() ),
1013                                                                              e.getMessage() ),
1014                                                         e );
1015                }
1016    
1017                // get the primemeridian if any.
1018                PrimeMeridian pMeridian = null;
1019                try {
1020                    String pMeridianID = XMLTools.getNodeAsString( datumElement, PRE + "usedPrimeMeridian", nsContext, null );
1021                    if ( pMeridianID != null && !"".equals( pMeridianID.trim() ) ) {
1022                        pMeridian = getPrimeMeridianFromID( pMeridianID );
1023                    }
1024                    if ( pMeridian == null ) {
1025                        pMeridian = PrimeMeridian.GREENWICH;
1026                    }
1027                } catch ( XMLParsingException e ) {
1028                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1029                                                                              "usedPrimeMeridian",
1030                                                                              ( ( datumElement == null ) ? "null"
1031                                                                                                        : datumElement.getLocalName() ),
1032                                                                              e.getMessage() ),
1033                                                         e );
1034                }
1035    
1036                // get the WGS84 if any.
1037                WGS84ConversionInfo cInfo = null;
1038                try {
1039                    String infoID = XMLTools.getNodeAsString( datumElement,
1040                                                              PRE + "usedWGS84ConversionInfo",
1041                                                              nsContext,
1042                                                              null );
1043                    if ( infoID != null && !"".equals( infoID.trim() ) ) {
1044                        cInfo = getConversionInfoFromID( infoID );
1045                    }
1046                    if ( cInfo == null ) {
1047                        cInfo = new WGS84ConversionInfo( "Created by DeegreeCRSProvider" );
1048                    }
1049                } catch ( XMLParsingException e ) {
1050                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1051                                                                              "wgs84ConversionInfo",
1052                                                                              ( ( datumElement == null ) ? "null"
1053                                                                                                        : datumElement.getLocalName() ),
1054                                                                              e.getMessage() ),
1055                                                         e );
1056                }
1057                if ( ellipsoid == null ) {
1058                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_DATUM_HAS_NO_ELLIPSOID", datumID ) );
1059                }
1060                GeodeticDatum result = new GeodeticDatum( ellipsoid,
1061                                                          pMeridian,
1062                                                          cInfo,
1063                                                          id.getIdentifiers(),
1064                                                          id.getNames(),
1065                                                          id.getVersions(),
1066                                                          id.getDescriptions(),
1067                                                          id.getAreasOfUse() );
1068                for ( String s : id.getIdentifiers() ) {
1069                    datums.put( s, result );
1070                }
1071                if ( checkForDoubleDefinition ) {
1072                    checkForUniqueness( cachedDatums, doubleDatums, result );
1073                }
1074    
1075                // remove the datum from the xml-tree.
1076                rootElement.removeChild( datumElement );
1077                return result;
1078            }
1079    
1080            return null;
1081        }
1082    
1083        /**
1084         * @param meridianID
1085         *            the id to search for.
1086         * @return the primeMeridian with given id or <code>null</code>
1087         * @throws CRSConfigurationException
1088         *             if the longitude was not set or the units could not be parsed.
1089         */
1090        private PrimeMeridian getPrimeMeridianFromID( String meridianID )
1091                                                                         throws CRSConfigurationException {
1092            if ( meridianID != null && !"".equals( meridianID.trim() ) ) {
1093                if ( primeMeridians.containsKey( meridianID ) && primeMeridians.get( meridianID ) != null ) {
1094                    return primeMeridians.get( meridianID );
1095                }
1096                Element meridianElement = getTopElementFromID( meridianID );
1097                if ( meridianElement == null ) {
1098                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_ELEMENT",
1099                                                                              "primeMeridian",
1100                                                                              meridianID ) );
1101                }
1102                Identifiable id = parseIdentifiable( meridianElement );
1103                Unit units = parseUnit( meridianElement );
1104                double longitude = 0;
1105                try {
1106                    longitude = XMLTools.getRequiredNodeAsDouble( meridianElement, PRE + "longitude", nsContext );
1107                    boolean inDegrees = XMLTools.getNodeAsBoolean( meridianElement,
1108                                                                   PRE + "longitude/@inDegrees",
1109                                                                   nsContext,
1110                                                                   true );
1111                    longitude = ( longitude != 0 && inDegrees ) ? Math.toRadians( longitude ) : longitude;
1112                } catch ( XMLParsingException e ) {
1113                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1114                                                                              "longitude",
1115                                                                              ( ( meridianElement == null ) ? "null"
1116                                                                                                           : meridianElement.getLocalName() ),
1117                                                                              e.getMessage() ),
1118                                                         e );
1119                }
1120                PrimeMeridian result = new PrimeMeridian( units,
1121                                                          longitude,
1122                                                          id.getIdentifiers(),
1123                                                          id.getNames(),
1124                                                          id.getVersions(),
1125                                                          id.getDescriptions(),
1126                                                          id.getAreasOfUse() );
1127                for ( String s : id.getIdentifiers() ) {
1128                    primeMeridians.put( s, result );
1129                }
1130                if ( checkForDoubleDefinition ) {
1131                    checkForUniqueness( cachedMeridans, doubleMeridians, result );
1132                }
1133                // remove the prime meridian, thus resulting in a smaller xml-tree.
1134                rootElement.removeChild( meridianElement );
1135                return result;
1136            }
1137            return null;
1138        }
1139    
1140        /**
1141         * Tries to find a cached ellipsoid, if not found, the config will be checked.
1142         * 
1143         * @param ellipsoidID
1144         * @return an ellipsoid or <code>null</code> if no ellipsoid with given id was found, or the id was
1145         *         <code>null</code> or empty.
1146         * @throws CRSConfigurationException
1147         *             if something went wrong.
1148         */
1149        private Ellipsoid getEllipsoidFromID( String ellipsoidID )
1150                                                                  throws CRSConfigurationException {
1151            if ( ellipsoidID != null && !"".equals( ellipsoidID.trim() ) ) {
1152                if ( ellipsoids.containsKey( ellipsoidID ) && ellipsoids.get( ellipsoidID ) != null ) {
1153                    return ellipsoids.get( ellipsoidID );
1154                }
1155                Element ellipsoidElement = getTopElementFromID( ellipsoidID );
1156                if ( ellipsoidElement == null ) {
1157                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_ELEMENT",
1158                                                                              "ellipsoid",
1159                                                                              ellipsoidID ) );
1160                }
1161                Identifiable id = parseIdentifiable( ellipsoidElement );
1162                try {
1163                    double semiMajor = XMLTools.getRequiredNodeAsDouble( ellipsoidElement, PRE + "semiMajorAxis", nsContext );
1164    
1165                    Unit units = parseUnit( ellipsoidElement );
1166                    double inverseFlattening = XMLTools.getNodeAsDouble( ellipsoidElement,
1167                                                                         PRE + "inverseFlatting",
1168                                                                         nsContext,
1169                                                                         Double.NaN );
1170                    double eccentricity = XMLTools.getNodeAsDouble( ellipsoidElement,
1171                                                                    PRE + "eccentricity",
1172                                                                    nsContext,
1173                                                                    Double.NaN );
1174                    double semiMinorAxis = XMLTools.getNodeAsDouble( ellipsoidElement,
1175                                                                     PRE + "semiMinorAxis",
1176                                                                     nsContext,
1177                                                                     Double.NaN );
1178                    if ( Double.isNaN( inverseFlattening ) && Double.isNaN( eccentricity ) && Double.isNaN( semiMinorAxis ) ) {
1179                        throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_ELLIPSOID_MISSES_PARAM",
1180                                                                                  ellipsoidID ) );
1181                    }
1182    
1183                    Ellipsoid result = null;
1184                    if ( !Double.isNaN( inverseFlattening ) ) {
1185                        result = new Ellipsoid( semiMajor,
1186                                                units,
1187                                                inverseFlattening,
1188                                                id.getIdentifiers(),
1189                                                id.getNames(),
1190                                                id.getVersions(),
1191                                                id.getDescriptions(),
1192                                                id.getAreasOfUse() );
1193                    } else if ( !Double.isNaN( eccentricity ) ) {
1194                        result = new Ellipsoid( semiMajor,
1195                                                eccentricity,
1196                                                units,
1197                                                id.getIdentifiers(),
1198                                                id.getNames(),
1199                                                id.getVersions(),
1200                                                id.getDescriptions(),
1201                                                id.getAreasOfUse() );
1202                    } else {
1203                        result = new Ellipsoid( units,
1204                                                semiMajor,
1205                                                semiMinorAxis,
1206                                                id.getIdentifiers(),
1207                                                id.getNames(),
1208                                                id.getVersions(),
1209                                                id.getDescriptions(),
1210                                                id.getAreasOfUse() );
1211                    }
1212                    for ( String s : id.getIdentifiers() ) {
1213                        ellipsoids.put( s, result );
1214                    }
1215                    if ( checkForDoubleDefinition ) {
1216                        checkForUniqueness( cachedEllipsoids, doubleEllipsoids, result );
1217                    }
1218                    // remove the ellipsoid from the xml-tree.
1219                    rootElement.removeChild( ellipsoidElement );
1220                    return result;
1221                } catch ( XMLParsingException e ) {
1222                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1223                                                                              "ellipsoid",
1224                                                                              ( ( ellipsoidElement == null ) ? "null"
1225                                                                                                            : ellipsoidElement.getLocalName() ),
1226                                                                              e.getMessage() ),
1227                                                         e );
1228                }
1229            }
1230    
1231            return null;
1232        }
1233    
1234        /**
1235         * @param info
1236         * @return the configured wgs84 conversion info parameters.
1237         * @throws CRSConfigurationException
1238         */
1239        private WGS84ConversionInfo getConversionInfoFromID( String infoID )
1240                                                                            throws CRSConfigurationException {
1241            if ( infoID != null && !"".equals( infoID.trim() ) ) {
1242                if ( conversionInfos.containsKey( infoID ) && conversionInfos.get( infoID ) != null ) {
1243                    return conversionInfos.get( infoID );
1244                }
1245                Element cInfoElement = getTopElementFromID( infoID );
1246                if ( cInfoElement == null ) {
1247                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_ELEMENT",
1248                                                                              "wgs84ConversionInfo",
1249                                                                              infoID ) );
1250                }
1251                Identifiable identifiable = parseIdentifiable( cInfoElement );
1252                double xT = 0, yT = 0, zT = 0, xR = 0, yR = 0, zR = 0, scale = 0;
1253                try {
1254                    xT = XMLTools.getNodeAsDouble( cInfoElement, PRE + "xAxisTranslation", nsContext, 0 );
1255                    yT = XMLTools.getNodeAsDouble( cInfoElement, PRE + "yAxisTranslation", nsContext, 0 );
1256                    zT = XMLTools.getNodeAsDouble( cInfoElement, PRE + "zAxisTranslation", nsContext, 0 );
1257                    xR = XMLTools.getNodeAsDouble( cInfoElement, PRE + "xAxisRotation", nsContext, 0 );
1258                    yR = XMLTools.getNodeAsDouble( cInfoElement, PRE + "yAxisRotation", nsContext, 0 );
1259                    zR = XMLTools.getNodeAsDouble( cInfoElement, PRE + "zAxisRotation", nsContext, 0 );
1260                    scale = XMLTools.getNodeAsDouble( cInfoElement, PRE + "scaleDifference", nsContext, 0 );
1261                } catch ( XMLParsingException e ) {
1262                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1263                                                                              "conversionInfo",
1264                                                                              "definitions",
1265                                                                              e.getMessage() ), e );
1266                }
1267                WGS84ConversionInfo result = new WGS84ConversionInfo( xT, yT, zT, xR, yR, zR, scale, identifiable );
1268                for ( String id : identifiable.getIdentifiers() ) {
1269                    conversionInfos.put( id, result );
1270                }
1271                if ( checkForDoubleDefinition ) {
1272                    checkForUniqueness( cachedToWGS, doubleToWGS, result );
1273                }
1274                // remove the child, thus resulting in a smaller xml-tree.
1275                rootElement.removeChild( cInfoElement );
1276                return result;
1277    
1278            }
1279            return null;
1280        }
1281    
1282        /**
1283         * @param crsElement
1284         *            from which the crs is to be created (using chached datums, conversioninfos and projections).
1285         * @return a projected coordinatesystem based on the given xml-element.
1286         * @throws CRSConfigurationException
1287         *             if a required element could not be found, or an xmlParsingException occurred.
1288         */
1289        private CoordinateSystem parseProjectedCRS( Element crsElement )
1290                                                                        throws CRSConfigurationException {
1291            Identifiable id = parseIdentifiable( crsElement );
1292            Axis[] axis = parseAxisOrder( crsElement );
1293            Unit units = parseUnit( crsElement );
1294    
1295            String usedProjection = null;
1296            String usedGeographicCRS = null;
1297            try {
1298                usedProjection = XMLTools.getRequiredNodeAsString( crsElement, PRE + "usedProjection", nsContext );
1299                usedGeographicCRS = XMLTools.getRequiredNodeAsString( crsElement, PRE + "usedGeographicCRS", nsContext );
1300            } catch ( XMLParsingException e ) {
1301                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1302                                                                          "projectiontType or usedGeographicCRS",
1303                                                                          ( ( crsElement == null ) ? "null"
1304                                                                                                  : crsElement.getLocalName() ),
1305                                                                          e.getMessage() ),
1306                                                     e );
1307    
1308            }
1309            // first create the datum.
1310            if ( usedGeographicCRS == null || "".equals( usedGeographicCRS.trim() ) ) {
1311                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_REFERENCE_ID_IS_EMPTY",
1312                                                                          "usedGeographicCRS",
1313                                                                          id.getIdentifier() ) );
1314            }
1315            CoordinateSystem geoCRS = getCRSByID( usedGeographicCRS );
1316            if ( geoCRS == null || geoCRS.getType() != CoordinateSystem.GEOGRAPHIC_CRS ) {
1317                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJECTEDCRS_FALSE_CRSREF",
1318                                                                          id.getIdentifier(),
1319                                                                          usedGeographicCRS ) );
1320            }
1321    
1322            // then the projection.
1323            if ( usedProjection == null || "".equals( usedProjection.trim() ) ) {
1324                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_REFERENCE_ID_IS_EMPTY",
1325                                                                          "projectionType",
1326                                                                          id.getIdentifier() ) );
1327            }
1328            Projection projection = getProjectionByID( usedProjection, (GeographicCRS) geoCRS, units );
1329            if ( projection == null ) {
1330                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJECTEDCRS_FALSE_PROJREF",
1331                                                                          id.getIdentifier(),
1332                                                                          usedProjection ) );
1333            }
1334            ProjectedCRS result = new ProjectedCRS( projection, axis, id );
1335            for ( String s : id.getIdentifiers() ) {
1336                LOG.logDebug( "Adding id: " + s + "to projected crs cache." );
1337                projectedCRSs.put( s, result );
1338            }
1339            // remove the child, thus resulting in a smaller xml-tree.
1340            rootElement.removeChild( crsElement );
1341            return result;
1342        }
1343    
1344        /**
1345         * Find or parses the projection with given id.
1346         * 
1347         * @param usedProjection
1348         * @return the configured projection or <code>null</code> if not defined or found.
1349         * @throws CRSConfigurationException
1350         */
1351        private Projection getProjectionByID( String usedProjection, GeographicCRS underlyingCRS, Unit units )
1352                                                                                                              throws CRSConfigurationException {
1353            if ( usedProjection != null && !"".equals( usedProjection.trim() ) ) {
1354                usedProjection = usedProjection.trim();
1355                if ( projections.containsKey( usedProjection ) && projections.get( usedProjection ) != null ) {
1356                    return projections.get( usedProjection );
1357                }
1358                Element projectionElement = getTopElementFromID( usedProjection );
1359                if ( projectionElement == null ) {
1360                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_ELEMENT",
1361                                                                              "projection",
1362                                                                              usedProjection ) );
1363                }
1364                // get the identifiable.
1365                Identifiable id = parseIdentifiable( projectionElement );
1366                Projection result = null;
1367    
1368                try {
1369                    double latitudeOfNaturalOrigin = XMLTools.getNodeAsDouble( projectionElement,
1370                                                                               PRE + "latitudeOfNaturalOrigin",
1371                                                                               nsContext,
1372                                                                               0 );
1373                    boolean inDegrees = XMLTools.getNodeAsBoolean( projectionElement,
1374                                                                   PRE + "latitudeOfNaturalOrigin/@inDegrees",
1375                                                                   nsContext,
1376                                                                   true );
1377                    latitudeOfNaturalOrigin = ( latitudeOfNaturalOrigin != 0 && inDegrees ) ? Math.toRadians( latitudeOfNaturalOrigin )
1378                                                                                           : latitudeOfNaturalOrigin;
1379    
1380                    double longitudeOfNaturalOrigin = XMLTools.getNodeAsDouble( projectionElement,
1381                                                                                PRE + "longitudeOfNaturalOrigin",
1382                                                                                nsContext,
1383                                                                                0 );
1384                    inDegrees = XMLTools.getNodeAsBoolean( projectionElement,
1385                                                           PRE + "longitudeOfNaturalOrigin/@inDegrees",
1386                                                           nsContext,
1387                                                           true );
1388                    longitudeOfNaturalOrigin = ( longitudeOfNaturalOrigin != 0 && inDegrees ) ? Math.toRadians( longitudeOfNaturalOrigin )
1389                                                                                             : longitudeOfNaturalOrigin;
1390    
1391                    double scaleFactor = XMLTools.getNodeAsDouble( projectionElement, PRE + "scaleFactor", nsContext, 0 );
1392                    double falseEasting = XMLTools.getNodeAsDouble( projectionElement, PRE + "falseEasting", nsContext, 0 );
1393                    double falseNorthing = XMLTools.getNodeAsDouble( projectionElement, PRE + "falseNorthing", nsContext, 0 );
1394    
1395                    String projectionName = projectionElement.getLocalName().trim();
1396                    Point2d naturalOrigin = new Point2d( longitudeOfNaturalOrigin, latitudeOfNaturalOrigin );
1397    
1398                    if ( "transverseMercator".equalsIgnoreCase( projectionName ) ) {
1399                        // change schema to let projection be identifiable. fix method geodetic
1400                        boolean northernHemi = XMLTools.getNodeAsBoolean( projectionElement,
1401                                                                          PRE + "northernHemisphere",
1402                                                                          nsContext,
1403                                                                          true );
1404                        result = new TransverseMercator( northernHemi,
1405                                                         underlyingCRS,
1406                                                         falseNorthing,
1407                                                         falseEasting,
1408                                                         naturalOrigin,
1409                                                         units,
1410                                                         scaleFactor,
1411                                                         id.getIdentifiers(),
1412                                                         id.getNames(),
1413                                                         id.getVersions(),
1414                                                         id.getDescriptions(),
1415                                                         id.getAreasOfUse() );
1416                    } else if ( "lambertAzimuthalEqualArea".equalsIgnoreCase( projectionName ) ) {
1417                        result = new LambertAzimuthalEqualArea( underlyingCRS,
1418                                                                falseNorthing,
1419                                                                falseEasting,
1420                                                                naturalOrigin,
1421                                                                units,
1422                                                                scaleFactor,
1423                                                                id.getIdentifiers(),
1424                                                                id.getNames(),
1425                                                                id.getVersions(),
1426                                                                id.getDescriptions(),
1427                                                                id.getAreasOfUse() );
1428                    } else if ( "lambertConformalConic".equalsIgnoreCase( projectionName ) ) {
1429                        double firstP = XMLTools.getNodeAsDouble( projectionElement,
1430                                                                  PRE + "firstParallelLatitude",
1431                                                                  nsContext,
1432                                                                  Double.NaN );
1433                        inDegrees = XMLTools.getNodeAsBoolean( projectionElement,
1434                                                               PRE + "firstParallelLatitude/@inDegrees",
1435                                                               nsContext,
1436                                                               true );
1437                        firstP = ( !Double.isNaN( firstP ) && inDegrees ) ? Math.toRadians( firstP ) : firstP;
1438    
1439                        double secondP = XMLTools.getNodeAsDouble( projectionElement,
1440                                                                   PRE + "secondParallelLatitude",
1441                                                                   nsContext,
1442                                                                   Double.NaN );
1443                        inDegrees = XMLTools.getNodeAsBoolean( projectionElement,
1444                                                               PRE + "secondParallelLatitude/@inDegrees",
1445                                                               nsContext,
1446                                                               true );
1447                        secondP = ( !Double.isNaN( secondP ) && inDegrees ) ? Math.toRadians( secondP ) : secondP;
1448                        result = new LambertConformalConic( firstP,
1449                                                            secondP,
1450                                                            underlyingCRS,
1451                                                            falseNorthing,
1452                                                            falseEasting,
1453                                                            naturalOrigin,
1454                                                            units,
1455                                                            scaleFactor,
1456                                                            id.getIdentifiers(),
1457                                                            id.getNames(),
1458                                                            id.getVersions(),
1459                                                            id.getDescriptions(),
1460                                                            id.getAreasOfUse() );
1461                    } else if ( "stereographicAzimuthal".equalsIgnoreCase( projectionName ) ) {
1462                        double trueScaleL = XMLTools.getNodeAsDouble( projectionElement,
1463                                                                      PRE + "trueScaleLatitude",
1464                                                                      nsContext,
1465                                                                      Double.NaN );
1466                        inDegrees = XMLTools.getNodeAsBoolean( projectionElement,
1467                                                               PRE + "trueScaleLatitude/@inDegrees",
1468                                                               nsContext,
1469                                                               true );
1470                        trueScaleL = ( !Double.isNaN( trueScaleL ) && inDegrees ) ? Math.toRadians( trueScaleL )
1471                                                                                 : trueScaleL;
1472    
1473                        result = new StereographicAzimuthal( trueScaleL,
1474                                                             underlyingCRS,
1475                                                             falseNorthing,
1476                                                             falseEasting,
1477                                                             naturalOrigin,
1478                                                             units,
1479                                                             scaleFactor,
1480                                                             id.getIdentifiers(),
1481                                                             id.getNames(),
1482                                                             id.getVersions(),
1483                                                             id.getDescriptions(),
1484                                                             id.getAreasOfUse() );
1485                    } else {
1486                        throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJECTEDCRS_INVALID_PROJECTION",
1487                                                                                  id.getIdentifier(),
1488                                                                                  usedProjection ) );
1489    
1490                    }
1491                    for ( String identifier : id.getIdentifiers() ) {
1492                        projections.put( identifier, result );
1493                    }
1494                    if ( checkForDoubleDefinition ) {
1495                        checkForUniqueness(  cachedProjections, doubleProjections, result );
1496                    }
1497                    // remove the child, thus resulting in a smaller xml-tree.
1498                    rootElement.removeChild( projectionElement );
1499                    return result;
1500                } catch ( XMLParsingException e ) {
1501                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1502                                                                              "projection parameters",
1503                                                                              ( ( projectionElement == null ) ? "null"
1504                                                                                                             : projectionElement.getLocalName() ),
1505                                                                              e.getMessage() ),
1506                                                         e );
1507    
1508                }
1509            }
1510    
1511            return null;
1512        }
1513        
1514        /**
1515         * @param crsElement
1516         *            from which the crs is to be created (using cached datums, conversioninfos and projections).
1517         * @return a geocentric coordinatesystem based on the given xml-element.
1518         * @throws CRSConfigurationException
1519         *             if a required element could not be found, or an xmlParsingException occurred.
1520         */
1521        private CoordinateSystem parseGeocentricCRS( Element crsElement )
1522                                                                         throws CRSConfigurationException {
1523            Identifiable id = parseIdentifiable( crsElement );
1524            Axis[] axis = parseAxisOrder( crsElement );
1525            GeodeticDatum usedDatum = parseReferencedGeodeticDatum( crsElement, id.getIdentifier() );
1526            GeocentricCRS result = new GeocentricCRS( usedDatum,
1527                                                      axis,
1528                                                      id.getIdentifiers(),
1529                                                      id.getNames(),
1530                                                      id.getVersions(),
1531                                                      id.getDescriptions(),
1532                                                      id.getAreasOfUse() );
1533            for ( String identifier : id.getIdentifiers() ) {
1534                geocentricCRSs.put( identifier, result );
1535            }
1536            rootElement.removeChild( crsElement );
1537            return result;
1538        }
1539    
1540        /**
1541         * Parses the required usedDatum element from the given parentElement (probably a crs element).
1542         * 
1543         * @param parentElement
1544         *            to parse the required usedDatum element from.
1545         * @param parentID
1546         *            optional for an appropriate error message.
1547         * @return the Datum.
1548         * @throws CRSConfigurationException
1549         *             if a parsing error occurred, the node was not defined or an illegal id reference (not found) was
1550         *             given.
1551         */
1552        private GeodeticDatum parseReferencedGeodeticDatum( Element parentElement, String parentID )
1553                                                                                                    throws CRSConfigurationException {
1554            String datumID = null;
1555            try {
1556                datumID = XMLTools.getRequiredNodeAsString( parentElement, PRE + "usedDatum", nsContext );
1557            } catch ( XMLParsingException e ) {
1558                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1559                                                                          "datumID",
1560                                                                          ( ( parentElement == null ) ? "null"
1561                                                                                                     : parentElement.getLocalName() ),
1562                                                                          e.getMessage() ),
1563                                                     e );
1564            }
1565            if ( datumID == null || "".equals( datumID.trim() ) ) {
1566                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_REFERENCE_ID_IS_EMPTY",
1567                                                                          "usedDatum",
1568                                                                          parentID ) );
1569            }
1570            GeodeticDatum usedDatum = getGeodeticDatumFromID( datumID );
1571            if ( usedDatum == null ) {
1572                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_USEDDATUM_IS_NULL", datumID, parentID ) );
1573            }
1574            return usedDatum;
1575        }
1576    
1577        /**
1578         * Parses all elements of the identifiable object.
1579         * 
1580         * @param element
1581         *            the xml-representation of the id-object
1582         * @return the identifiable object or <code>null</code> if no id was given.
1583         * @throws CRSConfigurationException
1584         */
1585        private Identifiable parseIdentifiable( Element element )
1586                                                                 throws CRSConfigurationException {
1587            try {
1588                String[] identifiers = XMLTools.getNodesAsStrings( element, PRE + "id", nsContext );
1589                if ( identifiers == null || identifiers.length == 0 ) {
1590                    String msg = Messages.getMessage( "CRS_CONFIG_NO_ID", ( ( element == null ) ? "null"
1591                                                                                               : element.getLocalName() ) );
1592                    throw new CRSConfigurationException( msg );
1593                }
1594                for ( int i = 0; i < identifiers.length; ++i ) {
1595                    if ( identifiers[i] != null ) {
1596                        identifiers[i] = identifiers[i].toUpperCase().trim();
1597                    }
1598                }
1599                String[] names = XMLTools.getNodesAsStrings( element, PRE + "name", nsContext );
1600                String[] versions = XMLTools.getNodesAsStrings( element, PRE + "version", nsContext );
1601                String[] descriptions = XMLTools.getNodesAsStrings( element, PRE + "description", nsContext );
1602                String[] areasOfUse = XMLTools.getNodesAsStrings( element, PRE + "areaOfuse", nsContext );
1603                return new Identifiable( identifiers, names, versions, descriptions, areasOfUse );
1604            } catch ( XMLParsingException e ) {
1605                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1606                                                                          "Identifiable",
1607                                                                          ( ( element == null ) ? "null"
1608                                                                                               : element.getLocalName() ),
1609                                                                          e.getMessage() ), e );
1610            }
1611        }
1612    
1613        /**
1614         * Creates an axis array for the given crs element.
1615         * 
1616         * @param crsElement
1617         *            to be parsed
1618         * @return an Array of axis defining their order.
1619         * @throws CRSConfigurationException
1620         *             if a required element could not be found, or an xmlParsingException occurred, or the axisorder uses
1621         *             names which were not defined in the axis elements.
1622         */
1623        private Axis[] parseAxisOrder( Element crsElement )
1624                                                           throws CRSConfigurationException {
1625            String axisOrder = null;
1626            try {
1627                axisOrder = XMLTools.getRequiredNodeAsString( crsElement, PRE + "axisOrder", nsContext );
1628            } catch ( XMLParsingException e ) {
1629                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1630                                                                          "AxisOrder",
1631                                                                          ( ( crsElement == null ) ? "null"
1632                                                                                                  : crsElement.getLocalName() ),
1633                                                                          e.getMessage() ),
1634                                                     e );
1635            }
1636            if ( axisOrder == null || "".equals( axisOrder.trim() ) ) {
1637                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1638                                                                          "AxisOrder",
1639                                                                          ( ( crsElement == null ) ? "null"
1640                                                                                                  : crsElement.getLocalName() ),
1641                                                                          " axisOrder element may not be empty" ) );
1642            }
1643            axisOrder = axisOrder.trim();
1644            String[] order = axisOrder.trim().split( "," );
1645            Axis[] axis = new Axis[order.length];
1646            String XPATH = PRE + "Axis[" + PRE + "name = '";
1647            for ( int i = 0; i < order.length; ++i ) {
1648                String t = order[i];
1649                if ( t != null && !"".equals( t.trim() ) ) {
1650                    t = t.trim();
1651                    try {
1652                        Element axisElement = XMLTools.getRequiredElement( crsElement, XPATH + t + "']", nsContext );
1653                        String axisOrientation = XMLTools.getRequiredNodeAsString( axisElement,
1654                                                                                   PRE + "axisOrientation",
1655                                                                                   nsContext );
1656                        Unit unit = parseUnit( axisElement );
1657                        axis[i] = new Axis( unit, t, axisOrientation );
1658                    } catch ( XMLParsingException e ) {
1659                        throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1660                                                                                  "Axis",
1661                                                                                  ( ( crsElement == null ) ? "null"
1662                                                                                                          : crsElement.getLocalName() ),
1663                                                                                  e.getMessage() ),
1664                                                             e );
1665                    }
1666                }
1667            }
1668            return axis;
1669        }
1670    
1671        /**
1672         * Parses a unit from the given xml-parent, if the unit is degree, it will be mapped to radians.
1673         * 
1674         * @param parent
1675         *            xml-node to parse the unit from.
1676         * @return the unit object.
1677         * @throws CRSConfigurationException
1678         *             if the unit object could not be created.
1679         */
1680        private Unit parseUnit( Element parent )
1681                                                throws CRSConfigurationException {
1682            String units = null;
1683            try {
1684                units = XMLTools.getNodeAsString( parent, PRE + "units", nsContext, null );
1685            } catch ( XMLParsingException e ) {
1686                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1687                                                                          "units",
1688                                                                          ( ( parent == null ) ? "null"
1689                                                                                              : parent.getLocalName() ),
1690                                                                          e.getMessage() ), e );
1691            }
1692            if ( Unit.DEGREE.getName().equals( units ) ) {
1693                units = Unit.RADIAN.getName();
1694            }
1695            Unit unit = Unit.createUnitFromString( units );
1696            if ( unit == null ) {
1697                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1698                                                                          "units",
1699                                                                          ( ( parent == null ) ? "null"
1700                                                                                              : parent.getLocalName() ),
1701                                                                          "unknown unit: " + units ) );
1702            }
1703            return unit;
1704        }
1705        
1706        /**
1707         * @param <T> should be at least of Type Identifiable.
1708         * @param uniqueList to check against
1709         * @param mapping to added the id of T to if it is found duplicate.
1710         * @param toBeChecked to check.
1711         */
1712        private <T extends Identifiable> void checkForUniqueness( List<T> uniqueList, Map<String, String> mapping, T toBeChecked){
1713            if ( uniqueList.contains( toBeChecked ) ) {
1714                int index = uniqueList.indexOf( toBeChecked );
1715                LOG.logInfo( "The Identifiable with id: " + toBeChecked.getIdentifier()
1716                             + " was found to be equal with: "
1717                             + uniqueList.get( index ).getIdentifier() );
1718                String key = uniqueList.get( index ).getIdentifier();
1719                if ( key != null && !"".equals( key.trim() ) ) {
1720                    String value = mapping.get( key );
1721                    //it would be nicest to get the epsg code if any.
1722                    if ( !key.startsWith( "EPSG:" ) && toBeChecked.getIdentifier().startsWith( "EPSG:" ) ) {
1723                        if ( value == null || "".equals( value ) ) {
1724                            value = key;
1725                        } else {
1726                            value += ", " + key;
1727                        }
1728                        mapping.remove( key );
1729                        key = toBeChecked.getIdentifier();
1730                    } else {
1731                        if ( value == null || "".equals( value ) ) {
1732                            value = toBeChecked.getIdentifier();
1733                        } else {
1734                            value += ", " + toBeChecked.getIdentifier();
1735                        }
1736                    }
1737                    mapping.put( key, value );
1738                }
1739            } else {
1740                uniqueList.add( toBeChecked );
1741            }
1742        }
1743    }