001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/crs/configuration/deegree/CRSParser.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005     Department of Geography, University of Bonn
006     and
007     lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035     ----------------------------------------------------------------------------*/
036    
037    package org.deegree.crs.configuration.deegree;
038    
039    import java.io.IOException;
040    import java.lang.reflect.Constructor;
041    import java.lang.reflect.InvocationTargetException;
042    import java.util.LinkedList;
043    import java.util.List;
044    import java.util.Map;
045    import java.util.Properties;
046    
047    import javax.vecmath.Point2d;
048    
049    import org.deegree.crs.Identifiable;
050    import org.deegree.crs.components.Axis;
051    import org.deegree.crs.components.Ellipsoid;
052    import org.deegree.crs.components.GeodeticDatum;
053    import org.deegree.crs.components.PrimeMeridian;
054    import org.deegree.crs.components.Unit;
055    import org.deegree.crs.configuration.resources.XMLFileResource;
056    import org.deegree.crs.coordinatesystems.CompoundCRS;
057    import org.deegree.crs.coordinatesystems.CoordinateSystem;
058    import org.deegree.crs.coordinatesystems.GeocentricCRS;
059    import org.deegree.crs.coordinatesystems.GeographicCRS;
060    import org.deegree.crs.coordinatesystems.ProjectedCRS;
061    import org.deegree.crs.exceptions.CRSConfigurationException;
062    import org.deegree.crs.projections.Projection;
063    import org.deegree.crs.projections.azimuthal.LambertAzimuthalEqualArea;
064    import org.deegree.crs.projections.azimuthal.StereographicAlternative;
065    import org.deegree.crs.projections.azimuthal.StereographicAzimuthal;
066    import org.deegree.crs.projections.conic.LambertConformalConic;
067    import org.deegree.crs.projections.cylindric.Mercator;
068    import org.deegree.crs.projections.cylindric.TransverseMercator;
069    import org.deegree.crs.transformations.Transformation;
070    import org.deegree.crs.transformations.helmert.Helmert;
071    import org.deegree.crs.transformations.polynomial.LeastSquareApproximation;
072    import org.deegree.crs.transformations.polynomial.PolynomialTransformation;
073    import org.deegree.framework.log.ILogger;
074    import org.deegree.framework.log.LoggerFactory;
075    import org.deegree.framework.xml.XMLParsingException;
076    import org.deegree.framework.xml.XMLTools;
077    import org.deegree.i18n.Messages;
078    import org.deegree.ogcbase.CommonNamespaces;
079    import org.w3c.dom.Element;
080    
081    /**
082     * The <code>CRSParser</code> class TODO add class documentation here.
083     * 
084     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
085     * 
086     * @author last edited by: $Author: mschneider $
087     * 
088     * @version $Revision: 20382 $, $Date: 2009-10-27 12:27:31 +0100 (Di, 27. Okt 2009) $
089     * 
090     */
091    public class CRSParser extends XMLFileResource {
092    
093        /**
094         * 
095         */
096        private static final long serialVersionUID = -5621078575657838568L;
097    
098        private static ILogger LOG = LoggerFactory.getLogger( CRSParser.class );
099    
100        /**
101         * The prefix to use.
102         */
103        private final static String PRE = CommonNamespaces.CRS_PREFIX + ":";
104    
105        private final static String XPATH_PRE = "*[" + PRE + "id='";
106    
107        /**
108         * @param provider
109         *            to be used for callback.
110         * @param rootElement
111         *            to be used for the configuration.
112         */
113        public CRSParser( DeegreeCRSProvider provider, Element rootElement ) {
114            super( provider, rootElement );
115        }
116    
117        /**
118         * @param provider
119         * @param properties
120         */
121        public CRSParser( DeegreeCRSProvider provider, Properties properties ) {
122            this( provider, properties, "definitions", CommonNamespaces.CRSNS.toASCIIString() );
123            // TODO Auto-generated constructor stub
124        }
125    
126        /**
127         * @param provider
128         * @param properties
129         * @param defaultRootElement
130         * @param namespace
131         */
132        public CRSParser( DeegreeCRSProvider provider, Properties properties, String defaultRootElement, String namespace ) {
133            super( provider, properties, defaultRootElement, namespace );
134        }
135    
136        /**
137         * @param crsDefintion
138         *            to be parsed
139         * @return an instance of the given crs or <code>null</code> if the crsDefinition is <code>null</code> or could
140         *         not be mapped to a valid type.
141         * @throws CRSConfigurationException
142         *             if something went wrong while constructing the crs.
143         */
144        public CoordinateSystem parseCoordinateSystem( Element crsDefintion )
145                                throws CRSConfigurationException {
146            if ( crsDefintion == null ) {
147                return null;
148            }
149            String crsType = crsDefintion.getLocalName();
150    
151            CoordinateSystem result = null;
152            if ( "geographicCRS".equalsIgnoreCase( crsType ) ) {
153                result = parseGeographicCRS( crsDefintion );
154            } else if ( "projectedCRS".equalsIgnoreCase( crsType ) ) {
155                result = parseProjectedCRS( crsDefintion );
156            } else if ( "geocentricCRS".equalsIgnoreCase( crsType ) ) {
157                result = parseGeocentricCRS( crsDefintion );
158            } else if ( "compoundCRS".equalsIgnoreCase( crsType ) ) {
159                result = parseCompoundCRS( crsDefintion );
160            }
161    
162            if ( result == null && LOG.isDebug() ) {
163                LOG.logDebug( "The element with localname "
164                              + crsDefintion.getLocalName()
165                              + " could not be mapped to a valid deegreec-crs, currently projectedCRS, geographicCRS, geocentricCRS and compoundCRS are supported." );
166            }
167            return result;
168        }
169    
170        public Element getURIAsType( String uri )
171                                throws IOException {
172            if ( uri == null || "".equals( uri ) ) {
173                return null;
174            }
175            String id = uri.trim().toUpperCase();
176            Element crsElement = null;
177            // String xPath ="//*[crs:id='EPSG:31466']";
178            String xPath = XPATH_PRE + id + "']";
179            try {
180                crsElement = XMLTools.getElement( getRootElement(), xPath, nsContext );
181            } catch ( XMLParsingException e ) {
182                LOG.logError( Messages.getMessage( "CRS_CONFIG_NO_RESULT_FOR_ID", id, e.getMessage() ), e );
183            }
184            if ( LOG.isDebug() ) {
185                LOG.logDebug( "Trying to find elements with xpath: " + xPath
186                              + ( ( crsElement == null ) ? " [failed]" : " [success]" ) );
187            }
188            return crsElement;
189        }
190    
191        public Helmert getWGS84Transformation( GeographicCRS sourceCRS ) {
192            if ( sourceCRS == null ) {
193                return null;
194            }
195            return sourceCRS.getGeodeticDatum().getWGS84Conversion();
196        }
197    
198        /**
199         * @return the version of the root element of the empty string if no version attribute was found in the root
200         *         element.
201         * @throws CRSConfigurationException
202         *             if the root element is empty
203         */
204        public String getVersion()
205                                throws CRSConfigurationException {
206            if ( getRootElement() == null ) {
207                throw new CRSConfigurationException( "The crs parser has no root element, this cannot be." );
208            }
209            return getRootElement().getAttribute( "version" );
210        }
211    
212        /**
213         * Parses all elements of the identifiable object.
214         * 
215         * @param element
216         *            the xml-representation of the id-object
217         * @return the identifiable object or <code>null</code> if no id was given.
218         * @throws CRSConfigurationException
219         */
220        protected Identifiable parseIdentifiable( Element element )
221                                throws CRSConfigurationException {
222            try {
223                String[] identifiers = XMLTools.getNodesAsStrings( element, PRE + "id", nsContext );
224                if ( identifiers == null || identifiers.length == 0 ) {
225                    String msg = Messages.getMessage( "CRS_CONFIG_NO_ID", ( ( element == null ) ? "null"
226                                                                                               : element.getLocalName() ) );
227                    throw new CRSConfigurationException( msg );
228                }
229                for ( int i = 0; i < identifiers.length; ++i ) {
230                    if ( identifiers[i] != null ) {
231                        identifiers[i] = identifiers[i].toUpperCase().trim();
232                    }
233                }
234                String[] names = XMLTools.getNodesAsStrings( element, PRE + "name", nsContext );
235                String[] versions = XMLTools.getNodesAsStrings( element, PRE + "version", nsContext );
236                String[] descriptions = XMLTools.getNodesAsStrings( element, PRE + "description", nsContext );
237                String[] areasOfUse = XMLTools.getNodesAsStrings( element, PRE + "areaOfUse", nsContext );
238                return new Identifiable( identifiers, names, versions, descriptions, areasOfUse );
239            } catch ( XMLParsingException e ) {
240                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR", "Identifiable",
241                                                                          ( ( element == null ) ? "null"
242                                                                                               : element.getLocalName() ),
243                                                                          e.getMessage() ), e );
244            }
245        }
246    
247        /**
248         * Creates an axis array for the given crs element.
249         * 
250         * @param crsElement
251         *            to be parsed
252         * @return an Array of axis defining their order.
253         * @throws CRSConfigurationException
254         *             if a required element could not be found, or an xmlParsingException occurred, or the axisorder uses
255         *             names which were not defined in the axis elements.
256         */
257        protected Axis[] parseAxisOrder( Element crsElement )
258                                throws CRSConfigurationException {
259            String axisOrder = null;
260            try {
261                axisOrder = XMLTools.getRequiredNodeAsString( crsElement, PRE + "axisOrder", nsContext );
262            } catch ( XMLParsingException e ) {
263                throw new CRSConfigurationException(
264                                                     Messages.getMessage(
265                                                                          "CRS_CONFIG_PARSE_ERROR",
266                                                                          "AxisOrder",
267                                                                          ( ( crsElement == null ) ? "null"
268                                                                                                  : crsElement.getLocalName() ),
269                                                                          e.getMessage() ), e );
270            }
271            if ( axisOrder == null || "".equals( axisOrder.trim() ) ) {
272                throw new CRSConfigurationException(
273                                                     Messages.getMessage(
274                                                                          "CRS_CONFIG_PARSE_ERROR",
275                                                                          "AxisOrder",
276                                                                          ( ( crsElement == null ) ? "null"
277                                                                                                  : crsElement.getLocalName() ),
278                                                                          " axisOrder element may not be empty" ) );
279            }
280            axisOrder = axisOrder.trim();
281            String[] order = axisOrder.trim().split( "," );
282            Axis[] axis = new Axis[order.length];
283            String XPATH = PRE + "Axis[" + PRE + "name = '";
284            for ( int i = 0; i < order.length; ++i ) {
285                String t = order[i];
286                if ( t != null && !"".equals( t.trim() ) ) {
287                    t = t.trim();
288                    try {
289                        Element axisElement = XMLTools.getRequiredElement( crsElement, XPATH + t + "']", nsContext );
290                        String axisOrientation = XMLTools.getRequiredNodeAsString( axisElement, PRE + "axisOrientation",
291                                                                                   nsContext );
292                        Unit unit = parseUnit( axisElement );
293                        axis[i] = new Axis( unit, t, axisOrientation );
294                    } catch ( XMLParsingException e ) {
295                        throw new CRSConfigurationException(
296                                                             Messages.getMessage(
297                                                                                  "CRS_CONFIG_PARSE_ERROR",
298                                                                                  "Axis",
299                                                                                  ( ( crsElement == null ) ? "null"
300                                                                                                          : crsElement.getLocalName() ),
301                                                                                  e.getMessage() ), e );
302                    }
303                }
304            }
305            return axis;
306        }
307    
308        /**
309         * Retrieves a transformation from the resource.
310         * 
311         * @param transformationDefinition
312         * @return the parsed transformation or <code>null</code> if no transformation could be parsed.
313         */
314        public Transformation parseTransformation( Element transformationDefinition ) {
315            throw new UnsupportedOperationException(
316                                                     "The parsing of transformations is not supported by this version of the deegree crs parser." );
317        }
318    
319        /**
320         * Parse all polynomial transformations for a given crs.
321         * 
322         * @param crsElement
323         *            to parse the transformations for.
324         * @return the list of transformations or the empty list if no transformations were found. Never <code>null</code>.
325         */
326        protected List<PolynomialTransformation> parseAlternativeTransformations( Element crsElement ) {
327            List<Element> usedTransformations = null;
328            try {
329                usedTransformations = XMLTools.getElements( crsElement, PRE + "polynomialTransformation", nsContext );
330            } catch ( XMLParsingException e ) {
331                LOG.logError( e.getMessage(), e );
332            }
333            List<PolynomialTransformation> result = new LinkedList<PolynomialTransformation>();
334            if ( usedTransformations != null ) {
335                for ( Element transformationElement : usedTransformations ) {
336                    PolynomialTransformation transform = getTransformation( transformationElement );
337                    if ( transform != null ) {
338                        result.add( transform );
339                    }
340                }
341            }
342            return result;
343        }
344    
345        /**
346         * Parses the transformation variables from the given crs:coordinatesystem/crs:polynomialTransformation element. If
347         * the class attribute is given, this method will try to invoke an instance of the given class, if it fails
348         * <code>null</code> will be returned.
349         * 
350         * @param transformationElement
351         *            to parse the values from.
352         * @return the instantiated transformation or <code>null</code> if it could not be instantiated.
353         */
354        protected PolynomialTransformation getTransformation( Element transformationElement ) {
355            if ( transformationElement == null ) {
356                throw new CRSConfigurationException( Messages.getMessage( "CRS_INVALID_NULL_PARAMETER",
357                                                                          "transformationElement" ) );
358            }
359    
360            // order is not evaluated yet, because I do not know if it is required.
361            // int order = -1;
362            String tCRS = null;
363            List<Double> aValues = new LinkedList<Double>();
364            List<Double> bValues = new LinkedList<Double>();
365            Element usedTransformation = null;
366            try {
367                usedTransformation = XMLTools.getRequiredElement( transformationElement, "*[1]", nsContext );
368            } catch ( XMLParsingException e ) {
369                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
370                                                                          "the transformation to use",
371                                                                          transformationElement.getLocalName(),
372                                                                          e.getLocalizedMessage() ), e );
373            }
374    
375            try {
376                // order = XMLTools.getNodeAsInt( usedTransformation, PRE + "polynomialOrder", nsContext, -1 );
377                tCRS = XMLTools.getRequiredNodeAsString( usedTransformation, PRE + "targetCRS", nsContext );
378                Element tmp = XMLTools.getRequiredElement( usedTransformation, PRE + "xParameters", nsContext );
379                String tmpValues = XMLTools.getStringValue( tmp );
380                if ( tmpValues != null && !"".equals( tmpValues.trim() ) ) {
381                    String[] split = tmpValues.split( "\\s" );
382                    for ( String t : split ) {
383                        aValues.add( Double.parseDouble( t ) );
384                    }
385                }
386                tmp = XMLTools.getRequiredElement( usedTransformation, PRE + "yParameters", nsContext );
387                tmpValues = XMLTools.getStringValue( tmp );
388                if ( tmpValues != null && !"".equals( tmpValues.trim() ) ) {
389                    String[] split = tmpValues.split( "\\s" );
390                    for ( String t : split ) {
391                        bValues.add( Double.parseDouble( t ) );
392                    }
393                }
394            } catch ( XMLParsingException e ) {
395                LOG.logError( e.getMessage(), e );
396            }
397    
398            if ( tCRS == null ) {
399                throw new CRSConfigurationException(
400                                                     Messages.getMessage(
401                                                                          "CRS_CONFIG_PARSE_ERROR",
402                                                                          "targetCRS",
403                                                                          ( ( usedTransformation == null ) ? "null"
404                                                                                                          : usedTransformation.getLocalName() ),
405                                                                          "it is required and must denote a valid crs )" ) );
406            }
407    
408            if ( aValues.size() == 0 || bValues.size() == 0 ) {
409                throw new CRSConfigurationException(
410                                                     "The polynomial variables (xParameters and yParameters element) defining the approximation to a given transformation function are required and may not be empty" );
411            }
412    
413            CoordinateSystem targetCRS = getProvider().getCRSByID( tCRS );
414    
415            PolynomialTransformation result = null;
416            String name = usedTransformation.getLocalName().trim();
417            String className = transformationElement.getAttribute( "class" );
418            LOG.logDebug( "Trying to create transformation with name: " + name );
419            if ( null != className && !"".equals( className.trim() ) ) {
420                LOG.logDebug( "Trying to load user defined transformation class: " + className );
421                try {
422                    Class<?> t = Class.forName( className );
423                    t.asSubclass( PolynomialTransformation.class );
424    
425                    List<Element> children = XMLTools.getElements( usedTransformation, "*", nsContext );
426                    List<Element> otherValues = new LinkedList<Element>();
427                    for ( Element child : children ) {
428                        if ( child != null ) {
429                            String localName = child.getLocalName().trim();
430                            if ( !( "targetCRS".equals( localName ) || "xParameters".equals( localName )
431                                    || "yParameters".equals( localName ) || "polynomialOrder".equals( localName ) ) ) {
432                                otherValues.add( child );
433                            }
434                        }
435                    }
436                    /**
437                     * Load the constructor with the standard projection values and the element list.
438                     */
439                    /**
440                     * For now, just load the constructor with the two lists and the crs class.
441                     */
442                    Constructor<?> constructor = t.getConstructor( aValues.getClass(), bValues.getClass(),
443                                                                   targetCRS.getClass() );
444                    result = (PolynomialTransformation) constructor.newInstance( aValues, bValues, targetCRS );
445                } catch ( ClassNotFoundException e ) {
446                    LOG.logError( e.getMessage(), e );
447                } catch ( SecurityException e ) {
448                    LOG.logError( e.getMessage(), e );
449                } catch ( NoSuchMethodException e ) {
450                    LOG.logError( e.getMessage(), e );
451                } catch ( IllegalArgumentException e ) {
452                    LOG.logError( e.getMessage(), e );
453                } catch ( InstantiationException e ) {
454                    LOG.logError( e.getMessage(), e );
455                } catch ( IllegalAccessException e ) {
456                    LOG.logError( e.getMessage(), e );
457                } catch ( InvocationTargetException e ) {
458                    LOG.logError( e.getMessage(), e );
459                } catch ( XMLParsingException e ) {
460                    // this will probably never happen.
461                    LOG.logError( e.getMessage(), e );
462                }
463                if ( result == null ) {
464                    LOG.logDebug( "Loading of user defined transformation class: " + className + " was not successful" );
465                }
466            } else if ( "leastsquare".equalsIgnoreCase( name ) ) {
467                float scaleX = 1;
468                float scaleY = 1;
469                try {
470                    scaleX = (float) XMLTools.getNodeAsDouble( usedTransformation, PRE + "scaleX", nsContext, 1 );
471                } catch ( XMLParsingException e ) {
472                    LOG.logError( "Could not parse scaleX from crs:leastsquare, because: " + e.getMessage(), e );
473                }
474                try {
475                    scaleY = (float) XMLTools.getNodeAsDouble( usedTransformation, PRE + "scaleY", nsContext, 1 );
476                } catch ( XMLParsingException e ) {
477                    LOG.logError( "Could not parse scaleY from crs:leastsquare, because: " + e.getMessage(), e );
478                }
479                result = new LeastSquareApproximation( aValues, bValues, null, targetCRS, scaleX, scaleY );
480            }
481            return result;
482        }
483    
484        /**
485         * Parses a unit from the given xml-parent.
486         * 
487         * @param parent
488         *            xml-node to parse the unit from.
489         * @return the unit object.
490         * @throws CRSConfigurationException
491         *             if the unit object could not be created.
492         */
493        protected Unit parseUnit( Element parent )
494                                throws CRSConfigurationException {
495            String unitId = null;
496            try {
497                unitId = XMLTools.getNodeAsString( parent, PRE + "units", nsContext, null );
498            } catch ( XMLParsingException e ) {
499                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR", "units",
500                                                                          ( ( parent == null ) ? "null"
501                                                                                              : parent.getLocalName() ),
502                                                                          e.getMessage() ), e );
503            }
504            Unit result = getProvider().getCachedIdentifiable( Unit.class, unitId );
505            if ( result == null ) {
506                result = Unit.createUnitFromString( unitId );
507                if ( result == null ) {
508                    throw new CRSConfigurationException(
509                                                         Messages.getMessage(
510                                                                              "CRS_CONFIG_PARSE_ERROR",
511                                                                              "units",
512                                                                              ( ( parent == null ) ? "null"
513                                                                                                  : parent.getLocalName() ),
514                                                                              "unknown unit: " + unitId ) );
515                }
516            }
517            return result;
518        }
519    
520        /**
521         * @param crsElement
522         *            from which the crs is to be created (using chached datums, conversioninfos and projections).
523         * @return a projected coordinatesystem based on the given xml-element.
524         * @throws CRSConfigurationException
525         *             if a required element could not be found, or an xmlParsingException occurred.
526         */
527        protected CoordinateSystem parseProjectedCRS( Element crsElement )
528                                throws CRSConfigurationException {
529            if ( crsElement == null ) {
530                return null;
531            }
532            // no need to get it from the cache, because the abstract provider checked it already.
533            Identifiable id = parseIdentifiable( crsElement );
534    
535            Axis[] axis = parseAxisOrder( crsElement );
536            List<PolynomialTransformation> transformations = parseAlternativeTransformations( crsElement );
537            // Unit units = parseUnit( crsElement );
538    
539            Element usedProjection = null;
540            String usedGeographicCRS = null;
541            try {
542                usedProjection = XMLTools.getRequiredElement( crsElement, PRE + "projection", nsContext );
543                usedGeographicCRS = XMLTools.getRequiredNodeAsString( crsElement, PRE + "usedGeographicCRS", nsContext );
544    
545            } catch ( XMLParsingException e ) {
546                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
547                                                                          "projectiontType or usedGeographicCRS",
548                                                                          crsElement.getLocalName(), e.getMessage() ), e );
549    
550            }
551            // first create the datum.
552            if ( usedGeographicCRS == null || "".equals( usedGeographicCRS.trim() ) ) {
553                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_REFERENCE_ID_IS_EMPTY",
554                                                                          "usedGeographicCRS", id.getIdentifier() ) );
555            }
556            GeographicCRS geoCRS = (GeographicCRS) getProvider().getCRSByID( usedGeographicCRS );
557            if ( geoCRS == null || geoCRS.getType() != CoordinateSystem.GEOGRAPHIC_CRS ) {
558                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJECTEDCRS_FALSE_CRSREF",
559                                                                          id.getIdentifier(), usedGeographicCRS ) );
560            }
561    
562            // // then the projection.
563            // if ( usedProjection == null || "".equals( usedProjection.trim() ) ) {
564            // throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_REFERENCE_ID_IS_EMPTY",
565            // "projectionType", id.getIdentifier() ) );
566            // }
567            // Projection projection = getProjectionByID( usedProjection, (GeographicCRS) geoCRS, axis[0].getUnits() );
568            Projection projection = parseProjection( usedProjection, geoCRS, axis[0].getUnits() );
569            if ( projection == null ) {
570                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PROJECTEDCRS_FALSE_PROJREF",
571                                                                          id.getIdentifier(), usedProjection ) );
572            }
573            // adding to cache will be done in AbstractCRSProvider.
574            return new ProjectedCRS( transformations, projection, axis, id );
575        }
576    
577        /**
578         * @param crsElement
579         *            from which the crs is to be created (using cached datums, conversioninfos and projections).
580         * 
581         * @return a geographic coordinatesystem based on the given xml-element.
582         * @throws CRSConfigurationException
583         *             if a required element could not be found, or an xmlParsingException occurred.
584         */
585        protected CoordinateSystem parseGeographicCRS( Element crsElement )
586                                throws CRSConfigurationException {
587            if ( crsElement == null ) {
588                return null;
589            }
590            Identifiable id = parseIdentifiable( crsElement );
591            // no need to get it from the cache, because the abstract provider checked it already.
592            Axis[] axis = parseAxisOrder( crsElement );
593            List<PolynomialTransformation> transformations = parseAlternativeTransformations( crsElement );
594            // get the datum
595            GeodeticDatum usedDatum = parseReferencedGeodeticDatum( crsElement, id.getIdentifier() );
596    
597            GeographicCRS result = new GeographicCRS( transformations, usedDatum, axis, id );
598            // adding to cache will be done in AbstractCRSProvider.
599            return result;
600        }
601    
602        /**
603         * @param crsElement
604         *            from which the crs is to be created (using cached datums, conversioninfos and projections).
605         * @return a geocentric coordinatesystem based on the given xml-element.
606         * @throws CRSConfigurationException
607         *             if a required element could not be found, or an xmlParsingException occurred.
608         */
609        protected CoordinateSystem parseGeocentricCRS( Element crsElement )
610                                throws CRSConfigurationException {
611            // no need to get it from the cache, because the abstract provider checked it already.
612            Identifiable id = parseIdentifiable( crsElement );
613            Axis[] axis = parseAxisOrder( crsElement );
614            List<PolynomialTransformation> transformations = parseAlternativeTransformations( crsElement );
615            GeodeticDatum usedDatum = parseReferencedGeodeticDatum( crsElement, id.getIdentifier() );
616            GeocentricCRS result = new GeocentricCRS( transformations, usedDatum, axis, id );
617            // adding to cache will be done in AbstractCRSProvider.
618            return result;
619        }
620    
621        /**
622         * @param crsElement
623         *            from which the crs is to be created.
624         * 
625         * @return a compound coordinatesystem based on the given xml-element.
626         * @throws CRSConfigurationException
627         *             if a required element could not be found, or an xmlParsingException occurred.
628         */
629        protected CoordinateSystem parseCompoundCRS( Element crsElement ) {
630            // no need to get it from the cache, because the abstract provider checked it already.
631            Identifiable id = parseIdentifiable( crsElement );
632            String usedCRS = null;
633            try {
634                usedCRS = XMLTools.getRequiredNodeAsString( crsElement, PRE + "usedCRS", nsContext );
635            } catch ( XMLParsingException e ) {
636                throw new CRSConfigurationException(
637                                                     Messages.getMessage(
638                                                                          "CRS_CONFIG_PARSE_ERROR",
639                                                                          "usedCRS",
640                                                                          ( ( crsElement == null ) ? "null"
641                                                                                                  : crsElement.getLocalName() ),
642                                                                          e.getMessage() ), e );
643    
644            }
645            if ( usedCRS == null || "".equals( usedCRS.trim() ) ) {
646                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_REFERENCE_ID_IS_EMPTY", "usedCRS",
647                                                                          id.getIdentifier() ) );
648            }
649            CoordinateSystem usedCoordinateSystem = getProvider().getCRSByID( usedCRS );
650            if ( usedCoordinateSystem == null
651                 || ( usedCoordinateSystem.getType() != CoordinateSystem.GEOGRAPHIC_CRS && usedCoordinateSystem.getType() != CoordinateSystem.PROJECTED_CRS ) ) {
652                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_COMPOUND_FALSE_CRSREF",
653                                                                          id.getIdentifier(), usedCRS ) );
654            }
655    
656            // get the datum
657            Axis heigtAxis = null;
658            double defaultHeight = 0;
659            try {
660                Element axisElement = XMLTools.getRequiredElement( crsElement, PRE + "heightAxis", nsContext );
661                String axisName = XMLTools.getRequiredNodeAsString( axisElement, PRE + "name", nsContext );
662                String axisOrientation = XMLTools.getRequiredNodeAsString( axisElement, PRE + "axisOrientation", nsContext );
663                Unit unit = parseUnit( axisElement );
664                heigtAxis = new Axis( unit, axisName, axisOrientation );
665                defaultHeight = XMLTools.getNodeAsDouble( crsElement, PRE + "defaultHeight", nsContext, 0 );
666            } catch ( XMLParsingException e ) {
667                LOG.logError( e.getMessage(), e );
668                throw new CRSConfigurationException( e.getMessage() );
669            }
670            // adding to cache will be done in AbstractCRSProvider.
671            return new CompoundCRS( heigtAxis, usedCoordinateSystem, defaultHeight, id );
672        }
673    
674        /**
675         * Parses the required usedDatum element from the given parentElement (probably a crs element).
676         * 
677         * @param parentElement
678         *            to parse the required usedDatum element from.
679         * @param parentID
680         *            optional for an appropriate error message.
681         * @return the Datum.
682         * @throws CRSConfigurationException
683         *             if a parsing error occurred, the node was not defined or an illegal id reference (not found) was
684         *             given.
685         */
686        protected GeodeticDatum parseReferencedGeodeticDatum( Element parentElement, String parentID )
687                                throws CRSConfigurationException {
688            String datumID = null;
689            try {
690                datumID = XMLTools.getRequiredNodeAsString( parentElement, PRE + "usedDatum", nsContext );
691            } catch ( XMLParsingException e ) {
692                throw new CRSConfigurationException(
693                                                     Messages.getMessage(
694                                                                          "CRS_CONFIG_PARSE_ERROR",
695                                                                          "datumID",
696                                                                          ( ( parentElement == null ) ? "null"
697                                                                                                     : parentElement.getLocalName() ),
698                                                                          e.getMessage() ), e );
699            }
700            if ( datumID == null || "".equals( datumID.trim() ) ) {
701                throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_REFERENCE_ID_IS_EMPTY", "usedDatum",
702                                                                          parentID ) );
703            }
704            GeodeticDatum usedDatum = getGeodeticDatumFromID( datumID );
705            if ( usedDatum == null ) {
706                throw new CRSConfigurationException(
707                                                     Messages.getMessage( "CRS_CONFIG_USEDDATUM_IS_NULL", datumID, parentID ) );
708            }
709            return usedDatum;
710        }
711    
712        /**
713         * @param datumID
714         * @return the
715         * @throws CRSConfigurationException
716         */
717        protected GeodeticDatum getGeodeticDatumFromID( String datumID )
718                                throws CRSConfigurationException {
719            if ( datumID == null || "".equals( datumID.trim() ) ) {
720                return null;
721            }
722            String tmpDatumID = datumID.trim();
723            GeodeticDatum result = getProvider().getCachedIdentifiable( GeodeticDatum.class, tmpDatumID );
724            if ( result == null ) {
725                Element datumElement = null;
726                try {
727                    datumElement = getURIAsType( tmpDatumID );
728                } catch ( IOException e ) {
729                    throw new CRSConfigurationException( e );
730                }
731                if ( datumElement == null ) {
732                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_ELEMENT", "datum", tmpDatumID ) );
733                }
734                // get the identifiable.
735                Identifiable id = parseIdentifiable( datumElement );
736    
737                // get the ellipsoid.
738                Ellipsoid ellipsoid = null;
739                try {
740                    String ellipsID = XMLTools.getRequiredNodeAsString( datumElement, PRE + "usedEllipsoid", nsContext );
741                    if ( ellipsID != null && !"".equals( ellipsID.trim() ) ) {
742                        ellipsoid = getEllipsoidFromID( ellipsID );
743                    }
744                } catch ( XMLParsingException e ) {
745                    throw new CRSConfigurationException(
746                                                         Messages.getMessage( "CRS_CONFIG_PARSE_ERROR", "usedEllipsoid",
747                                                                              datumElement.getLocalName(), e.getMessage() ),
748                                                         e );
749                }
750                if ( ellipsoid == null ) {
751                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_DATUM_HAS_NO_ELLIPSOID",
752                                                                              tmpDatumID ) );
753                }
754    
755                // get the primemeridian if any.
756                PrimeMeridian pMeridian = null;
757                try {
758                    String pMeridianID = XMLTools.getNodeAsString( datumElement, PRE + "usedPrimeMeridian", nsContext, null );
759                    if ( pMeridianID != null && !"".equals( pMeridianID.trim() ) ) {
760                        pMeridian = getPrimeMeridianFromID( pMeridianID );
761                    }
762                    if ( pMeridian == null ) {
763                        pMeridian = PrimeMeridian.GREENWICH;
764                    }
765                } catch ( XMLParsingException e ) {
766                    throw new CRSConfigurationException(
767                                                         Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
768                                                                              "usedPrimeMeridian",
769                                                                              datumElement.getLocalName(), e.getMessage() ),
770                                                         e );
771                }
772    
773                // get the WGS84 if any.
774                Helmert cInfo = null;
775                try {
776                    String infoID = XMLTools.getNodeAsString( datumElement, PRE + "usedWGS84ConversionInfo", nsContext,
777                                                              null );
778                    if ( infoID != null && !"".equals( infoID.trim() ) ) {
779                        cInfo = getConversionInfoFromID( infoID );
780                    }
781                    // if ( cInfo == null ) {
782                    // cInfo = new Helmert( "Created by DeegreeCRSProvider" );
783                    // }
784                } catch ( XMLParsingException e ) {
785                    throw new CRSConfigurationException(
786                                                         Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
787                                                                              "wgs84ConversionInfo",
788                                                                              datumElement.getLocalName(), e.getMessage() ),
789                                                         e );
790                }
791    
792                result = new GeodeticDatum( ellipsoid, pMeridian, cInfo, id.getIdentifiers(), id.getNames(),
793                                            id.getVersions(), id.getDescriptions(), id.getAreasOfUse() );
794            }
795            return getProvider().addIdToCache( result, false );
796    
797        }
798    
799        /**
800         * @param meridianID
801         *            the id to search for.
802         * @return the primeMeridian with given id or <code>null</code>
803         * @throws CRSConfigurationException
804         *             if the longitude was not set or the units could not be parsed.
805         */
806        protected PrimeMeridian getPrimeMeridianFromID( String meridianID )
807                                throws CRSConfigurationException {
808            if ( meridianID == null || "".equals( meridianID.trim() ) ) {
809                return null;
810            }
811            PrimeMeridian result = getProvider().getCachedIdentifiable( PrimeMeridian.class, meridianID );
812            if ( result == null ) {
813                Element meridianElement = null;
814                try {
815                    meridianElement = getURIAsType( meridianID );
816                } catch ( IOException e ) {
817                    throw new CRSConfigurationException( e );
818                }
819                if ( meridianElement == null ) {
820                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_ELEMENT", "primeMeridian",
821                                                                              meridianID ) );
822                }
823                Identifiable id = parseIdentifiable( meridianElement );
824                Unit units = parseUnit( meridianElement );
825                double longitude = 0;
826                try {
827                    longitude = XMLTools.getRequiredNodeAsDouble( meridianElement, PRE + "longitude", nsContext );
828                    boolean inDegrees = XMLTools.getNodeAsBoolean( meridianElement, PRE + "longitude/@inDegrees",
829                                                                   nsContext, true );
830                    longitude = ( longitude != 0 && inDegrees ) ? Math.toRadians( longitude ) : longitude;
831                } catch ( XMLParsingException e ) {
832                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR", "longitude",
833                                                                              meridianElement.getLocalName(),
834                                                                              e.getMessage() ), e );
835                }
836                result = new PrimeMeridian( units, longitude, id );
837            }
838            return getProvider().addIdToCache( result, false );
839        }
840    
841        /**
842         * Tries to find a cached ellipsoid, if not found, the config will be checked.
843         * 
844         * @param ellipsoidID
845         * @return an ellipsoid or <code>null</code> if no ellipsoid with given id was found, or the id was
846         *         <code>null</code> or empty.
847         * @throws CRSConfigurationException
848         *             if something went wrong.
849         */
850        protected Ellipsoid getEllipsoidFromID( String ellipsoidID )
851                                throws CRSConfigurationException {
852            if ( ellipsoidID == null || "".equals( ellipsoidID.trim() ) ) {
853                return null;
854            }
855            Ellipsoid result = getProvider().getCachedIdentifiable( Ellipsoid.class, ellipsoidID );
856            if ( result == null ) {
857                Element ellipsoidElement = null;
858                try {
859                    ellipsoidElement = getURIAsType( ellipsoidID );
860                } catch ( IOException e ) {
861                    throw new CRSConfigurationException( e );
862                }
863                if ( ellipsoidElement == null ) {
864                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_ELEMENT", "ellipsoid",
865                                                                              ellipsoidID ) );
866                }
867                Identifiable id = parseIdentifiable( ellipsoidElement );
868                Unit units = parseUnit( ellipsoidElement );
869    
870                double semiMajor = Double.NaN;
871                double inverseFlattening = Double.NaN;
872                double eccentricity = Double.NaN;
873                double semiMinorAxis = Double.NaN;
874    
875                try {
876                    semiMajor = XMLTools.getRequiredNodeAsDouble( ellipsoidElement, PRE + "semiMajorAxis", nsContext );
877                    inverseFlattening = XMLTools.getNodeAsDouble( ellipsoidElement, PRE + "inverseFlattening", nsContext,
878                                                                  Double.NaN );
879                    eccentricity = XMLTools.getNodeAsDouble( ellipsoidElement, PRE + "eccentricity", nsContext, Double.NaN );
880                    semiMinorAxis = XMLTools.getNodeAsDouble( ellipsoidElement, PRE + "semiMinorAxis", nsContext,
881                                                              Double.NaN );
882                } catch ( XMLParsingException e ) {
883                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR", "ellipsoid",
884                                                                              ellipsoidElement.getLocalName(),
885                                                                              e.getMessage() ), e );
886                }
887                if ( Double.isNaN( inverseFlattening ) && Double.isNaN( eccentricity ) && Double.isNaN( semiMinorAxis ) ) {
888                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_ELLIPSOID_MISSES_PARAM",
889                                                                              ellipsoidID ) );
890                }
891                if ( !Double.isNaN( inverseFlattening ) ) {
892                    result = new Ellipsoid( semiMajor, units, inverseFlattening, id.getIdentifiers(), id.getNames(),
893                                            id.getVersions(), id.getDescriptions(), id.getAreasOfUse() );
894                } else if ( !Double.isNaN( eccentricity ) ) {
895                    result = new Ellipsoid( semiMajor, eccentricity, units, id.getIdentifiers(), id.getNames(),
896                                            id.getVersions(), id.getDescriptions(), id.getAreasOfUse() );
897                } else {
898                    result = new Ellipsoid( units, semiMajor, semiMinorAxis, id.getIdentifiers(), id.getNames(),
899                                            id.getVersions(), id.getDescriptions(), id.getAreasOfUse() );
900                }
901            }
902    
903            return getProvider().addIdToCache( result, false );
904        }
905    
906        /**
907         * @param infoID
908         *            to get the conversioninfo from.
909         * @return the configured wgs84 conversion info parameters.
910         * @throws CRSConfigurationException
911         */
912        protected Helmert getConversionInfoFromID( String infoID )
913                                throws CRSConfigurationException {
914            if ( infoID == null || "".equals( infoID.trim() ) ) {
915                return null;
916            }
917            LOG.logDebug( "Searching for the wgs84 with id: " + infoID );
918            Helmert result = getProvider().getCachedIdentifiable( Helmert.class, infoID );
919            if ( result == null ) {
920    
921                Element cInfoElement = null;
922                try {
923                    cInfoElement = getURIAsType( infoID );
924                } catch ( IOException e ) {
925                    throw new CRSConfigurationException( e );
926                }
927                if ( cInfoElement == null ) {
928                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_NO_ELEMENT",
929                                                                              "wgs84ConversionInfo", infoID ) );
930                }
931                Identifiable identifiable = parseIdentifiable( cInfoElement );
932                double xT = 0, yT = 0, zT = 0, xR = 0, yR = 0, zR = 0, scale = 0;
933                try {
934                    xT = XMLTools.getNodeAsDouble( cInfoElement, PRE + "xAxisTranslation", nsContext, 0 );
935                    yT = XMLTools.getNodeAsDouble( cInfoElement, PRE + "yAxisTranslation", nsContext, 0 );
936                    zT = XMLTools.getNodeAsDouble( cInfoElement, PRE + "zAxisTranslation", nsContext, 0 );
937                    xR = XMLTools.getNodeAsDouble( cInfoElement, PRE + "xAxisRotation", nsContext, 0 );
938                    yR = XMLTools.getNodeAsDouble( cInfoElement, PRE + "yAxisRotation", nsContext, 0 );
939                    zR = XMLTools.getNodeAsDouble( cInfoElement, PRE + "zAxisRotation", nsContext, 0 );
940                    scale = XMLTools.getNodeAsDouble( cInfoElement, PRE + "scaleDifference", nsContext, 0 );
941                } catch ( XMLParsingException e ) {
942                    throw new CRSConfigurationException( Messages.getMessage( "CRS_CONFIG_PARSE_ERROR", "conversionInfo",
943                                                                              "definitions", e.getMessage() ), e );
944                }
945                result = new Helmert( xT, yT, zT, xR, yR, zR, scale, null, GeographicCRS.WGS84, identifiable );
946            }
947            return getProvider().addIdToCache( result, false );
948        }
949    
950        /**
951         * Parses and instantiates the projection from the given element.
952         * 
953         * @param projectionElement
954         *            to create the projection from.
955         * @param underlyingCRS
956         *            of the projected crs
957         * @param units
958         *            of the projected crs
959         * @return the configured projection or <code>null</code> if not defined or found.
960         * @throws CRSConfigurationException
961         */
962        protected Projection parseProjection( Element projectionElement, GeographicCRS underlyingCRS, Unit units )
963                                throws CRSConfigurationException {
964            if ( projectionElement == null ) {
965                throw new CRSConfigurationException(
966                                                     Messages.getMessage( "CRS_INVALID_NULL_PARAMETER", "projectionElement" ) );
967            }
968            try {
969                Element usedProjection = XMLTools.getRequiredElement( projectionElement, "*[1]", nsContext );
970                // All projections will have following parameters
971                double latitudeOfNaturalOrigin = XMLTools.getNodeAsDouble( usedProjection, PRE + "latitudeOfNaturalOrigin",
972                                                                           nsContext, 0 );
973                boolean inDegrees = XMLTools.getNodeAsBoolean( usedProjection, PRE + "latitudeOfNaturalOrigin/@inDegrees",
974                                                               nsContext, true );
975                latitudeOfNaturalOrigin = ( latitudeOfNaturalOrigin != 0 && inDegrees ) ? Math.toRadians( latitudeOfNaturalOrigin )
976                                                                                       : latitudeOfNaturalOrigin;
977    
978                double longitudeOfNaturalOrigin = XMLTools.getNodeAsDouble( usedProjection, PRE
979                                                                                            + "longitudeOfNaturalOrigin",
980                                                                            nsContext, 0 );
981                inDegrees = XMLTools.getNodeAsBoolean( usedProjection, PRE + "longitudeOfNaturalOrigin/@inDegrees",
982                                                       nsContext, true );
983                longitudeOfNaturalOrigin = ( longitudeOfNaturalOrigin != 0 && inDegrees ) ? Math.toRadians( longitudeOfNaturalOrigin )
984                                                                                         : longitudeOfNaturalOrigin;
985    
986                double scaleFactor = XMLTools.getNodeAsDouble( usedProjection, PRE + "scaleFactor", nsContext, 0 );
987                double falseEasting = XMLTools.getNodeAsDouble( usedProjection, PRE + "falseEasting", nsContext, 0 );
988                double falseNorthing = XMLTools.getNodeAsDouble( usedProjection, PRE + "falseNorthing", nsContext, 0 );
989    
990                String projectionName = usedProjection.getLocalName().trim();
991                String className = projectionElement.getAttribute( "class" );
992                Point2d naturalOrigin = new Point2d( longitudeOfNaturalOrigin, latitudeOfNaturalOrigin );
993                Projection result = null;
994                if ( className != null && !"".equals( className.trim() ) ) {
995                    LOG.logDebug( "Trying to load user defined projection class: " + className );
996                    try {
997                        Class<?> t = Class.forName( className );
998                        t.asSubclass( Projection.class );
999                        /**
1000                         * try to get a constructor with a native type as a parameter, by going over the 'names' of the
1001                         * classes of the parameters, the native type will show up as the typename e.g. int or long.....
1002                         * <code>
1003                         * public Projection( GeographicCRS geographicCRS, double falseNorthing, double falseEasting,
1004                         * Point2d naturalOrigin, Unit units, double scale, boolean conformal, boolean equalArea )
1005                         * </code>
1006                         */
1007                        List<Element> children = XMLTools.getElements( usedProjection, "*", nsContext );
1008                        List<Element> otherValues = new LinkedList<Element>();
1009                        for ( Element child : children ) {
1010                            if ( child != null ) {
1011                                String localName = child.getLocalName().trim();
1012                                if ( !( "latitudeOfNaturalOrigin".equals( localName )
1013                                        || "longitudeOfNaturalOrigin".equals( localName )
1014                                        || "scaleFactor".equals( localName ) || "falseEasting".equals( localName ) || "falseNorthing".equals( localName ) ) ) {
1015                                    otherValues.add( child );
1016                                }
1017                            }
1018                        }
1019                        /**
1020                         * Load the constructor with the standard projection values and the element list.
1021                         */
1022                        Constructor<?> constructor = t.getConstructor( GeographicCRS.class, double.class, double.class,
1023                                                                       Point2d.class, Unit.class, double.class, List.class );
1024                        result = (Projection) constructor.newInstance( underlyingCRS, falseNorthing, falseEasting,
1025                                                                       naturalOrigin, units, scaleFactor, otherValues );
1026                    } catch ( ClassNotFoundException e ) {
1027                        LOG.logError( e.getMessage(), e );
1028                    } catch ( SecurityException e ) {
1029                        LOG.logError( e.getMessage(), e );
1030                    } catch ( NoSuchMethodException e ) {
1031                        LOG.logError( e.getMessage(), e );
1032                    } catch ( IllegalArgumentException e ) {
1033                        LOG.logError( e.getMessage(), e );
1034                    } catch ( InstantiationException e ) {
1035                        LOG.logError( e.getMessage(), e );
1036                    } catch ( IllegalAccessException e ) {
1037                        LOG.logError( e.getMessage(), e );
1038                    } catch ( InvocationTargetException e ) {
1039                        LOG.logError( e.getMessage(), e );
1040                    }
1041                    if ( result == null ) {
1042                        LOG.logDebug( "Loading of user defined transformation class: " + className + " was not successful" );
1043                    }
1044    
1045                } else {
1046                    // no selfdefined projection, try one of the following, for the projection specific parameters, if any.
1047                    if ( "transverseMercator".equalsIgnoreCase( projectionName ) ) {
1048                        // change schema to let projection be identifiable. fix method geodetic
1049                        boolean northernHemi = XMLTools.getNodeAsBoolean( usedProjection, PRE + "northernHemisphere",
1050                                                                          nsContext, true );
1051                        result = new TransverseMercator( northernHemi, underlyingCRS, falseNorthing, falseEasting,
1052                                                         naturalOrigin, units, scaleFactor );
1053                    } else if ( "lambertAzimuthalEqualArea".equalsIgnoreCase( projectionName ) ) {
1054                        result = new LambertAzimuthalEqualArea( underlyingCRS, falseNorthing, falseEasting, naturalOrigin,
1055                                                                units, scaleFactor );
1056                    } else if ( "lambertConformalConic".equalsIgnoreCase( projectionName ) ) {
1057                        double firstP = XMLTools.getNodeAsDouble( usedProjection, PRE + "firstParallelLatitude", nsContext,
1058                                                                  Double.NaN );
1059                        inDegrees = XMLTools.getNodeAsBoolean( usedProjection, PRE + "firstParallelLatitude/@inDegrees",
1060                                                               nsContext, true );
1061                        firstP = ( !Double.isNaN( firstP ) && inDegrees ) ? Math.toRadians( firstP ) : firstP;
1062    
1063                        double secondP = XMLTools.getNodeAsDouble( usedProjection, PRE + "secondParallelLatitude",
1064                                                                   nsContext, Double.NaN );
1065                        inDegrees = XMLTools.getNodeAsBoolean( usedProjection, PRE + "secondParallelLatitude/@inDegrees",
1066                                                               nsContext, true );
1067                        secondP = ( !Double.isNaN( secondP ) && inDegrees ) ? Math.toRadians( secondP ) : secondP;
1068                        result = new LambertConformalConic( firstP, secondP, underlyingCRS, falseNorthing, falseEasting,
1069                                                            naturalOrigin, units, scaleFactor );
1070                    } else if ( "stereographicAzimuthal".equalsIgnoreCase( projectionName ) ) {
1071                        double trueScaleL = XMLTools.getNodeAsDouble( usedProjection, PRE + "trueScaleLatitude", nsContext,
1072                                                                      Double.NaN );
1073                        inDegrees = XMLTools.getNodeAsBoolean( usedProjection, PRE + "trueScaleLatitude/@inDegrees",
1074                                                               nsContext, true );
1075                        trueScaleL = ( !Double.isNaN( trueScaleL ) && inDegrees ) ? Math.toRadians( trueScaleL )
1076                                                                                 : trueScaleL;
1077                        result = new StereographicAzimuthal( trueScaleL, underlyingCRS, falseNorthing, falseEasting,
1078                                                             naturalOrigin, units, scaleFactor );
1079                    } else if ( "StereographicAlternative".equalsIgnoreCase( projectionName ) ) {
1080                        result = new StereographicAlternative( underlyingCRS, falseNorthing, falseEasting, naturalOrigin,
1081                                                               units, scaleFactor );
1082                    } else if ( "mercator".equalsIgnoreCase( projectionName ) ) {
1083                        result = new Mercator( underlyingCRS, falseNorthing, falseEasting, naturalOrigin, units,
1084                                               scaleFactor );
1085                    } else {
1086                        throw new CRSConfigurationException(
1087                                                             Messages.getMessage(
1088                                                                                  "CRS_CONFIG_PROJECTEDCRS_INVALID_PROJECTION",
1089                                                                                  projectionName,
1090                                                                                  "StereographicAlternative, stereographicAzimuthal, lambertConformalConic, lambertAzimuthalEqualArea, transverseMercator" ) );
1091    
1092                    }
1093                }
1094                return result;
1095            } catch ( XMLParsingException e ) {
1096                throw new CRSConfigurationException(
1097                                                     Messages.getMessage( "CRS_CONFIG_PARSE_ERROR",
1098                                                                          "projection parameters",
1099                                                                          projectionElement.getLocalName(), e.getMessage() ),
1100                                                     e );
1101    
1102            }
1103        }
1104    
1105        /**
1106         * @param <T>
1107         *            should be at least of Type Identifiable.
1108         * @param uniqueList
1109         *            to check against
1110         * @param mapping
1111         *            to added the id of T to if it is found duplicate.
1112         * @param toBeChecked
1113         *            to check.
1114         * @return the cached T if found or the given identifiable.
1115         */
1116        protected <T extends Identifiable> T checkForUniqueness( List<T> uniqueList, Map<String, String> mapping,
1117                                                                 T toBeChecked ) {
1118            T result = toBeChecked;
1119            if ( uniqueList.contains( toBeChecked ) ) {
1120                int index = uniqueList.indexOf( toBeChecked );
1121                LOG.logInfo( "The Identifiable with id: " + toBeChecked.getIdentifier() + " was found to be equal with: "
1122                             + uniqueList.get( index ).getIdentifier() );
1123                String key = uniqueList.get( index ).getIdentifier();
1124                boolean updatedEPSG = false;
1125                if ( key != null && !"".equals( key.trim() ) ) {
1126                    String value = mapping.get( key );
1127                    String tbcID = toBeChecked.getIdentifier().toUpperCase();
1128                    // it would be nicest to get the epsg code if any.
1129                    if ( !key.toUpperCase().startsWith( "EPSG:" ) && tbcID.startsWith( "EPSG:" ) ) {
1130                        if ( value == null || "".equals( value ) ) {
1131                            value = key;
1132                        } else {
1133                            value += ", " + key;
1134                        }
1135                        updatedEPSG = true;
1136                        mapping.remove( key );
1137                        key = toBeChecked.getIdentifier();
1138                    } else {
1139                        if ( value == null || "".equals( value ) ) {
1140                            value = toBeChecked.getIdentifier();
1141                        } else {
1142                            value += ", " + toBeChecked.getIdentifier();
1143                        }
1144                    }
1145                    mapping.put( key, value );
1146                }
1147                // if updated to epsg, cache the epsg instead and remove the old identifiable.
1148                if ( updatedEPSG ) {
1149                    uniqueList.remove( index );
1150                    uniqueList.add( toBeChecked );
1151                } else {
1152                    result = uniqueList.get( index );
1153                }
1154            } else {
1155                LOG.logDebug( "Adding: " + toBeChecked.getIdentifier() + " to cache." );
1156                uniqueList.add( toBeChecked );
1157            }
1158            return result;
1159        }
1160    
1161        public Transformation getTransformation( CoordinateSystem sourceCRS, CoordinateSystem targetCRS ) {
1162            LOG.logError( "The retrieval of transformations is not supported for this deegree crs configuration format." );
1163            return null;
1164        }
1165    
1166        /**
1167         * Gets the Element for the given id and heuristically check the localname of the resulting root Element. This
1168         * version supports following local names (see schema): <code>
1169         * <ul>
1170         * <li>ellipsoid</li>
1171         * <li>geodeticDatum</li>
1172         * <li>projectedCRS</li>
1173         * <li>geographicCRS</li>
1174         * <li>compoundCRS</li>
1175         * <li>geocentricCRS</li>
1176         * <li>primeMeridian</li>
1177         * <li>wgs84Transformation</li>
1178         * </ul>
1179         * </code>
1180         * 
1181         * @param id
1182         *            to look for.
1183         * @return the instantiated {@link Identifiable} or <code>null</code> if it could not be parsed.
1184         */
1185        public Identifiable parseIdentifiableObject( String id ) {
1186            if ( id == null || "".equals( id ) ) {
1187                return null;
1188            }
1189            Element resolvedID = null;
1190            try {
1191                resolvedID = getURIAsType( id );
1192            } catch ( IOException e ) {
1193                throw new CRSConfigurationException( e );
1194            }
1195            Identifiable result = null;
1196            if ( resolvedID != null ) {
1197                String localName = resolvedID.getLocalName();
1198                if ( localName != null && !"".equals( localName.trim() ) ) {
1199                    if ( localName.equals( "ellipsoid" ) ) {
1200                        result = getEllipsoidFromID( id );
1201                    } else if ( localName.equals( "geodeticDatum" ) ) {
1202                        result = getGeodeticDatumFromID( id );
1203                    } else if ( localName.equals( "projectedCRS" ) || localName.equals( "geographicCRS" )
1204                                || localName.equals( "compoundCRS" ) || localName.equals( "geocentricCRS" ) ) {
1205                        result = getProvider().getCRSByID( id );
1206                    } else if ( localName.equals( "primeMeridian" ) ) {
1207                        result = getPrimeMeridianFromID( id );
1208                    } else if ( localName.equals( "wgs84Transformation" ) ) {
1209                        result = getConversionInfoFromID( id );
1210                    }
1211                }
1212            }
1213            return result;
1214        }
1215    }