001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/crs/configuration/gml/GMLCRSProvider.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.gml;
038    
039    import static org.deegree.crs.components.Unit.createUnitFromString;
040    import static org.deegree.framework.xml.XMLTools.getElement;
041    import static org.deegree.framework.xml.XMLTools.getNodesAsStrings;
042    import static org.deegree.framework.xml.XMLTools.getRequiredElement;
043    import static org.deegree.framework.xml.XMLTools.getRequiredNodeAsDouble;
044    import static org.deegree.framework.xml.XMLTools.getRequiredNodeAsString;
045    
046    import java.io.IOException;
047    import java.util.ArrayList;
048    import java.util.Arrays;
049    import java.util.List;
050    import java.util.Properties;
051    
052    import javax.vecmath.Point2d;
053    
054    import org.deegree.crs.Identifiable;
055    import org.deegree.crs.components.Axis;
056    import org.deegree.crs.components.Ellipsoid;
057    import org.deegree.crs.components.GeodeticDatum;
058    import org.deegree.crs.components.PrimeMeridian;
059    import org.deegree.crs.components.Unit;
060    import org.deegree.crs.components.VerticalDatum;
061    import org.deegree.crs.configuration.AbstractCRSProvider;
062    import org.deegree.crs.configuration.resources.XMLResource;
063    import org.deegree.crs.coordinatesystems.CompoundCRS;
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.coordinatesystems.VerticalCRS;
069    import org.deegree.crs.exceptions.CRSConfigurationException;
070    import org.deegree.crs.projections.Projection;
071    import org.deegree.crs.projections.azimuthal.LambertAzimuthalEqualArea;
072    import org.deegree.crs.projections.azimuthal.StereographicAlternative;
073    import org.deegree.crs.projections.azimuthal.StereographicAzimuthal;
074    import org.deegree.crs.projections.conic.LambertConformalConic;
075    import org.deegree.crs.projections.cylindric.TransverseMercator;
076    import org.deegree.crs.transformations.Transformation;
077    import org.deegree.crs.transformations.coordinate.GeocentricTransform;
078    import org.deegree.crs.transformations.coordinate.NotSupportedTransformation;
079    import org.deegree.crs.transformations.helmert.Helmert;
080    import org.deegree.framework.log.ILogger;
081    import org.deegree.framework.log.LoggerFactory;
082    import org.deegree.framework.util.CharsetUtils;
083    import org.deegree.framework.util.Pair;
084    import org.deegree.framework.xml.DOMPrinter;
085    import org.deegree.framework.xml.NamespaceContext;
086    import org.deegree.framework.xml.XMLParsingException;
087    import org.deegree.framework.xml.XMLTools;
088    import org.deegree.ogcbase.CommonNamespaces;
089    import org.w3c.dom.Element;
090    
091    /**
092     * The <code>GMLCRSProvider</code> is a provider for a GML 3.2 backend, this may be a dictionary or a database.
093     * 
094     * Note: not all of the GML3.2. features are implemented yet, but the basics (transformations, crs's, axis, units,
095     * projections) should work quite well.
096     * 
097     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
098     * 
099     * @author last edited by: $Author: rbezema $
100     * 
101     * @version $Revision: 18308 $, $Date: 2009-07-02 14:40:19 +0200 (Do, 02. Jul 2009) $
102     * 
103     */
104    public class GMLCRSProvider extends AbstractCRSProvider<Element> {
105    
106        private static ILogger LOG = LoggerFactory.getLogger( GMLCRSProvider.class );
107    
108        private static String PRE = CommonNamespaces.GML3_2_PREFIX + ":";
109    
110        private static NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
111    
112        /**
113         * The 'default constructor' which will be called by the CRSConfiguration
114         * 
115         * @param properties
116         *            the properties which can hold information about the configuration of this GML provider.
117         */
118        public GMLCRSProvider( Properties properties ) {
119            super( properties, XMLResource.class, null );
120            if ( getResolver() == null ) {
121                setResolver( new GMLFileResource( this, properties ) );
122            }
123        }
124    
125        public boolean canExport() {
126            return false;
127        }
128    
129        public void export( StringBuilder sb, List<CoordinateSystem> crsToExport ) {
130            throw new UnsupportedOperationException( "Exporting to gml is currently not supported." );
131        }
132    
133        public List<String> getAvailableCRSIds()
134                                throws CRSConfigurationException {
135            throw new UnsupportedOperationException( "Retrieval of all crs id's is currently not supported." );
136        }
137    
138        public List<CoordinateSystem> getAvailableCRSs()
139                                throws CRSConfigurationException {
140            throw new UnsupportedOperationException( "Retrieval of all crs is currently not supported." );
141        }
142    
143        /**
144         * @param rootElement
145         *            containing a gml:CRS dom representation.
146         * @return a {@link CoordinateSystem} instance initialized with values from the given xml-dom gml:CRS fragment or
147         *         <code>null</code> if the given root element is <code>null</code>
148         * @throws CRSConfigurationException
149         *             if something went wrong.
150         */
151        @Override
152        protected CoordinateSystem parseCoordinateSystem( Element rootElement )
153                                throws CRSConfigurationException {
154            if ( rootElement == null ) {
155                LOG.logDebug( "The given crs root element is null, returning nothing" );
156                return null;
157            }
158            CoordinateSystem result = null;
159            String localName = rootElement.getLocalName();
160    
161            try {
162                if ( "ProjectedCRS".equalsIgnoreCase( localName ) ) {
163                    result = parseProjectedCRS( rootElement );
164                } else if ( "CompoundCRS".equalsIgnoreCase( localName ) ) {
165                    result = parseCompoundCRS( rootElement );
166                } else if ( "GeodeticCRS".equalsIgnoreCase( localName ) ) {
167                    result = parseGeodeticCRS( rootElement );
168                } else {
169                    LOG.logWarning( "The given coordinate system:" + localName
170                                    + " is currently not supported by the deegree gml provider." );
171                }
172            } catch ( XMLParsingException e ) {
173                throw new CRSConfigurationException( e );
174            } catch ( IOException e ) {
175                throw new CRSConfigurationException( e );
176            }
177    
178            return result;
179        }
180    
181        /**
182         * Calls parseGMLTransformation for the catching of {@link XMLParsingException}.
183         */
184        @Override
185        public Transformation parseTransformation( Element rootElement )
186                                throws CRSConfigurationException {
187            try {
188                return parseGMLTransformation( rootElement );
189            } catch ( XMLParsingException e ) {
190                throw new CRSConfigurationException( e );
191            } catch ( IOException e ) {
192                throw new CRSConfigurationException( e );
193            }
194        }
195    
196        /**
197         * Parses some of the gml 3.2 transformation constructs. Currently only helmert transformations are supported.
198         * 
199         * @param rootElement
200         * @return the transformation.
201         * @throws XMLParsingException
202         * @throws IOException
203         */
204        protected Transformation parseGMLTransformation( Element rootElement )
205                                throws XMLParsingException, IOException {
206            if ( rootElement == null ) {
207                return null;
208            }
209            Identifiable id = parseIdentifiedObject( rootElement );
210            if ( id == null ) {
211                return null;
212            }
213            if ( LOG.isDebug() ) {
214                LOG.logDebug( "Parsing id of transformation method resulted in: " + Arrays.toString( id.getIdentifiers() ) );
215            }
216            Transformation result = getCachedIdentifiable( Transformation.class, id );
217            if ( result == null ) {
218                Element crsProp = getRequiredElement( rootElement, PRE + "sourceCRS", nsContext );
219                Element crsElem = getRequiredXlinkedElement( crsProp, "*[1]" );
220                CoordinateSystem sourceCRS = parseCoordinateSystem( crsElem );
221                if ( sourceCRS == null ) {
222                    throw new XMLParsingException(
223                                                   "The transformation could not be parsed, because the sourceCRS is not supported." );
224                }
225                crsProp = getRequiredElement( rootElement, PRE + "targetCRS", nsContext );
226                crsElem = getRequiredXlinkedElement( crsProp, "*[1]" );
227                CoordinateSystem targetCRS = parseCoordinateSystem( crsElem );
228                if ( targetCRS == null ) {
229                    throw new XMLParsingException(
230                                                   "The transformation could not be parsed, because the targetCRS is not supported." );
231                }
232    
233                Element method = getRequiredElement( rootElement, PRE + "method", nsContext );
234    
235                Element conversionMethod = getRequiredXlinkedElement( method, PRE + "OperationMethod" );
236                Identifiable conversionMethodID = parseIdentifiedObject( conversionMethod );
237                SupportedTransformations transform = mapTransformation( conversionMethodID.getIdentifiers() );
238    
239                List<Pair<Identifiable, Pair<Unit, Double>>> parameterValues = parseParameterValues( rootElement );
240                switch ( transform ) {
241                case GENERAL_POLYNOMIAL:
242                    LOG.logWarning( "The mapping of gml:Transformation to Polynomial transformations is not yet implemented." );
243                    result = new NotSupportedTransformation( sourceCRS, targetCRS, id );
244                    break;
245                case HELMERT_3:
246                case HELMERT_7:
247                    double dx = 0,
248                    dy = 0,
249                    dz = 0,
250                    ex = 0,
251                    ey = 0,
252                    ez = 0,
253                    ppm = 0;
254                    for ( Pair<Identifiable, Pair<Unit, Double>> paramValue : parameterValues ) {
255                        if ( paramValue != null ) {
256                            Pair<Unit, Double> second = paramValue.second;
257                            if ( second != null ) {
258                                double value = second.second;
259                                if ( !Double.isNaN( value ) ) {
260                                    Identifiable paramID = paramValue.first;
261                                    if ( paramID != null ) {
262                                        SupportedTransformationParameters paramType = mapTransformationParameters( paramID.getIdentifiers() );
263                                        Unit unit = second.first;
264                                        LOG.logDebug( "Found value: " + value );
265                                        // If a unit was given, convert the value to the internally used
266                                        // unit.
267                                        if ( unit != null && !unit.isBaseType() ) {
268                                            double t = value;
269                                            value = unit.toBaseUnits( value );
270                                            LOG.logDebug( "Changing value: " + t + " to base type resulted in: " + value );
271                                        }
272                                        switch ( paramType ) {
273                                        case X_AXIS_ROTATION:
274                                            ex = value;
275                                            break;
276                                        case Y_AXIS_ROTATION:
277                                            ey = value;
278                                            break;
279                                        case Z_AXIS_ROTATION:
280                                            ez = value;
281                                            break;
282                                        case X_AXIS_TRANSLATION:
283                                            dx = value;
284                                            break;
285                                        case Y_AXIS_TRANSLATION:
286                                            dy = value;
287                                            break;
288                                        case Z_AXIS_TRANSLATION:
289                                            dz = value;
290                                            break;
291                                        case SCALE_DIFFERENCE:
292                                            ppm = value;
293                                            break;
294                                        default:
295                                            LOG.logWarning( "The (helmert) transformation parameter: "
296                                                            + paramID.getIdAndName()
297                                                            + " could not be mapped to a valid parameter and will not be used." );
298                                            break;
299                                        }
300                                    }
301    
302                                }
303                            }
304                        }
305                    }
306                    result = new Helmert( dx, dy, dz, ex, ey, ez, ppm, sourceCRS, targetCRS, id, true );
307                    break;
308                case GEOGRAPHIC_GEOCENTRIC:
309                    LOG.logWarning( "The mapping of gml:Transformation to Geographic/Geocentic transformations is not necessary." );
310                    if ( targetCRS.getType() == CoordinateSystem.GEOCENTRIC_CRS ) {
311                        result = new GeocentricTransform( sourceCRS, (GeocentricCRS) targetCRS );
312                    } else if ( targetCRS.getType() == CoordinateSystem.COMPOUND_CRS ) {
313                        if ( ( (CompoundCRS) targetCRS ).getUnderlyingCRS().getType() == CoordinateSystem.GEOCENTRIC_CRS ) {
314                            result = new GeocentricTransform(
315                                                              sourceCRS,
316                                                              (GeocentricCRS) ( (CompoundCRS) targetCRS ).getUnderlyingCRS() );
317                        }
318                    } else {
319                        result = new NotSupportedTransformation( sourceCRS, targetCRS, id );
320                    }
321    
322                    break;
323                case LONGITUDE_ROTATION:
324                    LOG.logWarning( "The mapping of gml:Transformation to a longitude rotation is not necessary." );
325                    result = new NotSupportedTransformation( sourceCRS, targetCRS, id );
326                    break;
327                case NTV2:
328                    LOG.logWarning( "The mapping of gml:Transformation to NTV2 transformations is not supported yet." );
329                    result = new NotSupportedTransformation( sourceCRS, targetCRS, id );
330                    break;
331                case NOT_SUPPORTED:
332                    LOG.logWarning( "The gml:Transformation could not be mapped to a deegree transformation." );
333                    result = new NotSupportedTransformation( sourceCRS, targetCRS, id );
334                }
335            }
336    
337            // Element sourceCRSProp = getRequiredElement( rootElement, PRE + "sourceCRS", nsContext
338            // );
339            return addIdToCache( result, false );
340        }
341    
342        /**
343         * @param rootElement
344         *            which is a subtype of gml:IdentifiedObject and gml:DefinitionType or gml:AbstractCRSType
345         * @return the {@link Identifiable} instance, its values are filled with the values of the given gml instance.
346         * @throws XMLParsingException
347         *             if the given rootElement could not be parsed.
348         */
349        public Identifiable parseIdentifiedObject( Element rootElement )
350                                throws XMLParsingException {
351            if ( rootElement == null ) {
352                return null;
353            }
354            List<String> versions = new ArrayList<String>();
355            List<String> descriptions = new ArrayList<String>();
356            List<String> areasOfUse = new ArrayList<String>();
357            String identifier = null;
358            try {
359                identifier = getRequiredNodeAsString( rootElement, PRE + "identifier", nsContext );
360            } catch ( XMLParsingException e ) {
361                LOG.logError( "Could not find the required identifier node for the given gml:identifiable with localname: "
362                              + rootElement.getLocalName() );
363                return null;
364            }
365            String[] identifiers = { identifier };
366    
367            String tmpDesc = XMLTools.getNodeAsString( rootElement, PRE + "description", nsContext, null );
368            if ( tmpDesc != null ) {
369                descriptions.add( tmpDesc );
370            }
371            // try to find the href
372            Element descRef = XMLTools.getElement( rootElement, PRE + "descriptionReference", nsContext );
373            if ( descRef != null ) {
374                String href = descRef.getAttributeNS( CommonNamespaces.XLNNS.toASCIIString(), "href" );
375                if ( !"".equals( href ) ) {
376                    descriptions.add( href );
377                }
378            }
379            List<Element> metaDatas = XMLTools.getElements( rootElement, PRE + "metaDataProperty", nsContext );
380            if ( metaDatas != null && metaDatas.size() > 0 ) {
381                // LOG.logDebug( "metaDataProperties will not be parsed, but put in a version instead" );
382                for ( Element metaDataElement : metaDatas ) {
383                    String metaData = "<![CDATA["
384                                      + DOMPrinter.nodeToString( metaDataElement, CharsetUtils.getSystemCharset() ) + "]]>";
385                    versions.add( metaData );
386                }
387            }
388    
389            List<Element> domainsOfValidity = XMLTools.getElements( rootElement, PRE + "domainOfValidity", nsContext );
390            if ( domainsOfValidity != null && domainsOfValidity.size() > 0 ) {
391                // LOG.logDebug( "domains of validity will not be parsed, but put in a area of use instead" );
392                for ( Element domainOfValidity : domainsOfValidity ) {
393                    String validDomain = " <![CDATA["
394                                         + DOMPrinter.nodeToString( domainOfValidity, CharsetUtils.getSystemCharset() )
395                                         + "]]>";
396                    areasOfUse.add( validDomain );
397                }
398            }
399            String[] scopes = XMLTools.getNodesAsStrings( rootElement, PRE + "scope", nsContext );
400            if ( scopes != null && scopes.length > 0 ) {
401                // LOG.logDebug( "scopes will be put in the area of uses" );
402                for ( String scope : scopes ) {
403                    areasOfUse.add( "Scope: " + scope );
404                }
405            }
406    
407            String[] names = getNodesAsStrings( rootElement, PRE + "name", nsContext );
408            if ( names != null && names.length > 0 ) {
409                // LOG.logDebug( "Using defined names as identifiers as well" );
410                // +1 for the identifier
411                identifiers = new String[names.length + 1];
412                identifiers[0] = identifier;
413                System.arraycopy( names, 0, identifiers, 1, names.length );
414            }
415            Identifiable result = new Identifiable( identifiers, names, versions.toArray( new String[0] ),
416                                                    descriptions.toArray( new String[0] ),
417                                                    areasOfUse.toArray( new String[0] ) );
418            return result;
419    
420        }
421    
422        /**
423         * This methods parses the given element and maps it onto a {@link CompoundCRS}. Currently only gml:CompoundCRS 's
424         * consisting of following combination is supported:
425         * <ul>
426         * <li> Projected CRS with VerticalCRS</li>
427         * </ul>
428         * 
429         * Geographic crs with a height axis can be mapped in a {@link CompoundCRS} by calling the
430         * {@link #parseGeodeticCRS(Element)}
431         * 
432         * @param rootElement
433         *            containing a gml:CompoundCRS dom representation.
434         * @return a {@link CompoundCRS} instance initialized with values from the given xml-dom gml:CompoundCRS fragment.
435         * @throws XMLParsingException
436         * @throws IOException
437         */
438        @SuppressWarnings("null")
439        protected CompoundCRS parseCompoundCRS( Element rootElement )
440                                throws XMLParsingException, IOException {
441            if ( rootElement == null ) {
442                LOG.logDebug( "The given crs root element is null, returning nothing" );
443                return null;
444            }
445    
446            Identifiable id = parseIdentifiedObject( rootElement );
447            if ( id == null ) {
448                return null;
449            }
450            if ( LOG.isDebug() ) {
451                LOG.logDebug( "Parsing id of compound crs resulted in: " + Arrays.toString( id.getIdentifiers() ) );
452            }
453    
454            List<Element> compRefSysProp = XMLTools.getRequiredElements( rootElement, PRE + "componentReferenceSystem",
455                                                                         nsContext );
456            if ( compRefSysProp.size() != 2 ) {
457                throw new XMLParsingException(
458                                               "Currently, compound crs definitions can only constist of exactly two base crs's, you supplied: "
459                                                                       + compRefSysProp.size() );
460            }
461    
462            // Find the first and second crs's of the compound crs.
463            Element first = compRefSysProp.get( 0 );
464            Element second = compRefSysProp.get( 1 );
465            // | " + PRE + "VerticalCRS"
466            Element xlinkedElem1 = retrieveAndResolveXLink( first );
467            Element xlinkedElem2 = retrieveAndResolveXLink( second );
468    
469            Element crsElement1 = null;
470            Element crsElement2 = null;
471    
472            if ( xlinkedElem1 == null ) {
473                crsElement1 = getRequiredElement( first, "*[1]", nsContext );
474            }
475            if ( xlinkedElem2 == null ) {
476                crsElement2 = getRequiredElement( first, "*[2]", nsContext );
477            }
478    
479            ProjectedCRS underlying = null;
480            VerticalCRS vertical = null;
481    
482            if ( "ProjectedCRS".equals( crsElement1.getLocalName() ) ) {
483                if ( "VerticalCRS".equals( crsElement2.getLocalName() ) ) {
484                    CoordinateSystem firstRes = parseProjectedCRS( crsElement1 );
485                    if ( firstRes.getType() == CoordinateSystem.COMPOUND_CRS ) {
486                        underlying = (ProjectedCRS) ( (CompoundCRS) firstRes ).getUnderlyingCRS();
487                    } else {
488                        underlying = (ProjectedCRS) firstRes;
489                    }
490                    vertical = parseVerticalCRS( crsElement2 );
491    
492                } else {
493                    throw new XMLParsingException(
494                                                   "Currently only Compoundcrs's with the ProjectedCRS and VerticalCRS combination are supported, instead a:"
495                                                                           + crsElement2.getLocalName() + " was found." );
496                }
497            } else if ( "VerticalCRS".equals( crsElement1.getLocalName() ) ) {
498                if ( "ProjectedCRS".equals( crsElement2.getLocalName() ) ) {
499                    CoordinateSystem firstRes = parseProjectedCRS( crsElement2 );
500                    if ( firstRes.getType() == CoordinateSystem.COMPOUND_CRS ) {
501                        underlying = (ProjectedCRS) ( (CompoundCRS) firstRes ).getUnderlyingCRS();
502                    } else {
503                        underlying = (ProjectedCRS) firstRes;
504                    }
505                    vertical = parseVerticalCRS( crsElement1 );
506                } else {
507                    throw new XMLParsingException(
508                                                   "Currently only Compoundcrs's with the ProjectedCRS and VerticalCRS combination are supported, instead a:"
509                                                                           + crsElement1.getLocalName() + " was found." );
510                }
511            } else {
512                throw new XMLParsingException(
513                                               "Currently only Compoundcrs's with the ProjectedCRS and VerticalCRS combination are supported, following elements were found:"
514                                                                       + crsElement1.getLocalName()
515                                                                       + " and "
516                                                                       + crsElement2.getLocalName() + "." );
517            }
518    
519            return new CompoundCRS( vertical.getVerticalAxis(), underlying, 0, id );
520        }
521    
522        /**
523         * @param rootElement
524         *            containing a gml:ProjectedCRS dom representation.
525         * @return a {@link ProjectedCRS} instance initialized with values from the given xml-dom gml:ProjectedCRS fragment
526         *         or <code>null</code> if the given root element is <code>null</code>
527         * @throws XMLParsingException
528         *             if the dom tree is not consistent or a required element is missing.
529         * @throws IOException
530         *             if a retrieval of an xlink of one of the subelements failed.
531         */
532        protected CoordinateSystem parseProjectedCRS( Element rootElement )
533                                throws XMLParsingException, IOException {
534            if ( rootElement == null ) {
535                LOG.logDebug( "The given crs root element is null, returning nothing" );
536                return null;
537            }
538            Identifiable id = parseIdentifiedObject( rootElement );
539            if ( id == null ) {
540                return null;
541            }
542            if ( LOG.isDebug() ) {
543                LOG.logDebug( "Parsing id of projected crs resulted in: " + Arrays.toString( id.getIdentifiers() ) );
544            }
545    
546            Element baseGEOCRSElementProperty = getRequiredElement( rootElement, PRE + "baseGeodeticCRS", nsContext );
547    
548            CoordinateSystem parsedBaseCRS = parseGeodeticCRS( getRequiredXlinkedElement( baseGEOCRSElementProperty,
549                                                                                          PRE + "GeodeticCRS" ) );
550            if ( parsedBaseCRS == null ) {
551                throw new XMLParsingException(
552                                               "No basetype for the projected crs found, each projected crs must have a base crs." );
553            }
554            GeographicCRS underlyingCRS = null;
555            if ( parsedBaseCRS.getType() == CoordinateSystem.COMPOUND_CRS ) {
556                CoordinateSystem cmpBase = ( (CompoundCRS) parsedBaseCRS ).getUnderlyingCRS();
557                if ( cmpBase.getType() != CoordinateSystem.GEOGRAPHIC_CRS ) {
558                    throw new XMLParsingException( "Only geographic crs's can be the base type of a projected crs." );
559                }
560                underlyingCRS = (GeographicCRS) cmpBase;
561            } else if ( parsedBaseCRS.getType() == CoordinateSystem.GEOGRAPHIC_CRS ) {
562                underlyingCRS = (GeographicCRS) parsedBaseCRS;
563            } else {
564                throw new XMLParsingException( "Only geographic crs's can be the base type of a projected crs." );
565            }
566    
567            Element cartesianCSProperty = getRequiredElement( rootElement, PRE + "cartesianCS", nsContext );
568            Axis[] axis = parseAxisFromCSType( getRequiredXlinkedElement( cartesianCSProperty, PRE + "CartesianCS" ) );
569            if ( axis.length != 2 ) {
570                throw new XMLParsingException( "The ProjectedCRS may only have 2 axis defined" );
571            }
572    
573            Element conversionElementProperty = getRequiredElement( rootElement, PRE + "conversion", nsContext );
574            Projection projection = parseProjection(
575                                                     getRequiredXlinkedElement( conversionElementProperty, PRE
576                                                                                                           + "Conversion" ),
577                                                     underlyingCRS );
578            CoordinateSystem result = new ProjectedCRS( projection, axis, id );
579            if ( parsedBaseCRS.getType() == CoordinateSystem.COMPOUND_CRS ) {
580                result = new CompoundCRS( ( (CompoundCRS) parsedBaseCRS ).getHeightAxis(), result,
581                                          ( (CompoundCRS) parsedBaseCRS ).getDefaultHeight(), id );
582            }
583            return result;
584        }
585    
586        /**
587         * @param rootElement
588         *            containing a gml:GeodeticCRS dom representation.
589         * @return a {@link CoordinateSystem} instance initialized with values from the given xml-dom gml:GeodeticCRS
590         *         fragment or <code>null</code> if the given root element is <code>null</code>. Note the result may be
591         *         a {@link CompoundCRS}, a {@link GeographicCRS} or a {@link GeocentricCRS}, depending of the definition
592         *         of the CS type.
593         * @throws XMLParsingException
594         * @throws IOException
595         */
596        protected CoordinateSystem parseGeodeticCRS( Element rootElement )
597                                throws XMLParsingException, IOException {
598            if ( rootElement == null ) {
599                LOG.logDebug( "The given crs root element is null, returning nothing" );
600                return null;
601            }
602            // check for xlink in the root element.
603    
604            Identifiable id = parseIdentifiedObject( rootElement );
605            if ( id == null ) {
606                return null;
607            }
608            if ( LOG.isDebug() ) {
609                LOG.logDebug( "Parsing id of geodetic crs resulted in: " + Arrays.toString( id.getIdentifiers() ) );
610            }
611    
612            Element datumElementProp = getRequiredElement( rootElement, PRE + "geodeticDatum", nsContext );
613            Element datumElement = getRequiredXlinkedElement( datumElementProp, PRE + "GeodeticDatum" );
614    
615            Element csTypeProp = getElement( rootElement, PRE + "ellipsoidalCS", nsContext );
616            Element csTypeElement = null;
617            if ( csTypeProp == null ) {
618                csTypeProp = getElement( rootElement, PRE + "cartesianCS", nsContext );
619                if ( csTypeProp == null ) {
620                    csTypeProp = getElement( rootElement, PRE + "sphericalCS", nsContext );
621                    if ( csTypeProp == null ) {
622                        throw new XMLParsingException(
623                                                       "The geodetic datum does not define one of the required cs types: ellipsoidal, cartesian or spherical." );
624                    }
625                    throw new XMLParsingException( "The sphericalCS is currently not supported." );
626                }
627                csTypeElement = getRequiredXlinkedElement( csTypeProp, PRE + "CartesianCS" );
628    
629            } else {
630                csTypeElement = getRequiredXlinkedElement( csTypeProp, PRE + "EllipsoidalCS" );
631            }
632            GeodeticDatum datum = parseDatum( datumElement );
633            Axis[] axis = parseAxisFromCSType( csTypeElement );
634            CoordinateSystem result = null;
635            if ( axis != null ) {
636                if ( "ellipsoidalCS".equals( csTypeProp.getLocalName() ) ) {
637                    if ( axis.length == 2 ) {
638                        result = new GeographicCRS( datum, axis, id );
639                    } else {
640                        result = new CompoundCRS( axis[2], new GeographicCRS( datum, new Axis[] { axis[0], axis[1] }, id ),
641                                                  0, id );
642                    }
643                } else {
644                    result = new GeocentricCRS( datum, axis, id );
645                }
646            } else {
647                throw new XMLParsingException( "No Axis were found in the geodetic crs, this may not be." );
648            }
649    
650            return result;
651        }
652    
653        /**
654         * @param rootElement
655         *            containing a gml:GeodeticDatum dom representation.
656         * @return a {@link GeodeticDatum} instance initialized with values from the given xml-dom fragment or
657         *         <code>null</code> if the given root element is <code>null</code>
658         * @throws XMLParsingException
659         *             if the dom tree is not consistent or a required element is missing.
660         * @throws IOException
661         *             if a retrieval of an xlink of one of the subelements failed.
662         */
663        protected GeodeticDatum parseDatum( Element rootElement )
664                                throws IOException, XMLParsingException {
665            if ( rootElement == null ) {
666                LOG.logDebug( "The given datum element is null, returning nothing" );
667                return null;
668            }
669            Identifiable id = parseIdentifiedObject( rootElement );
670            if ( id == null ) {
671                return null;
672            }
673            if ( LOG.isDebug() ) {
674                LOG.logDebug( "Parsing id of datum resulted in: " + Arrays.toString( id.getIdentifiers() ) );
675            }
676            GeodeticDatum result = getCachedIdentifiable( GeodeticDatum.class, id );
677            if ( result == null ) {
678                Element pmElementProp = getRequiredElement( rootElement, PRE + "primeMeridian", nsContext );
679                Element pmElement = getRequiredXlinkedElement( pmElementProp, PRE + "PrimeMeridian" );
680                PrimeMeridian pm = parsePrimeMeridian( pmElement );
681    
682                Element ellipsoidElementProp = getRequiredElement( rootElement, PRE + "ellipsoid", nsContext );
683                Element ellipsoidElement = getRequiredXlinkedElement( ellipsoidElementProp, PRE + "Ellipsoid" );
684                Ellipsoid ellipsoid = parseEllipsoid( ellipsoidElement );
685                result = new GeodeticDatum( ellipsoid, pm, id );
686            }
687    
688            return addIdToCache( result, false );
689        }
690    
691        /**
692         * For the ellipsoidal and cartesian cs Types, this method also checks the consistency of axis (radian, radian,
693         * [metre] ) or (metre, metre, [metre] ). If the conditions are not met, an xml parsing exception will be thrown as
694         * well.
695         * 
696         * @param rootElement
697         *            containing a (Ellipsoidal, Spherical, Cartesian) CS type dom representation.
698         * @return a {@link Axis} array instance initialized with values from the given xml-dom fragment or
699         *         <code>null</code> if the given root element is <code>null</code>
700         * @throws XMLParsingException
701         *             if the dom tree is not consistent or a required element is missing.
702         * @throws IOException
703         *             if a retrieval of an xlink of one of the subelements failed.
704         */
705        protected Axis[] parseAxisFromCSType( Element rootElement )
706                                throws XMLParsingException, IOException {
707            if ( rootElement == null ) {
708                LOG.logDebug( "The given coordinate type element is null, returning nothing" );
709                return null;
710            }
711            List<Element> axisProps = XMLTools.getRequiredElements( rootElement, PRE + "axis", nsContext );
712    
713            if ( axisProps.size() > 3 ) {
714                throw new XMLParsingException( "The CS type defines to many axis." );
715            }
716            if ( axisProps.size() == 0 ) {
717                throw new XMLParsingException( "The CS type defines no axis." );
718            }
719    
720            Axis[] axis = new Axis[axisProps.size()];
721            for ( int i = 0; i < axisProps.size(); i++ ) {
722                Element axisElement = getRequiredXlinkedElement( axisProps.get( i ), PRE + "CoordinateSystemAxis" );
723                Axis a = parseAxis( axisElement );
724                if ( a == null ) {
725                    throw new XMLParsingException( "Axis: " + i + " of the CS Type is null, this may not be." );
726                }
727                axis[i] = a;
728            }
729            if ( "cartesianCS".equalsIgnoreCase( rootElement.getLocalName() ) ) {
730                for ( int i = 0; i < axis.length; ++i ) {
731                    if ( !axis[i].getUnits().canConvert( Unit.METRE ) ) {
732                        throw new XMLParsingException(
733                                                       "The units of all axis of a (cartesian) cs must be convertable to metres. Axis "
734                                                                               + i + " is not: " + axis[i] );
735                    }
736                }
737            } else if ( "ellipsoidalCS".equalsIgnoreCase( rootElement.getLocalName() ) ) {
738                if ( axis.length < 2 && axis.length > 3 ) {
739                    throw new XMLParsingException( "An ellipsoidal cs can only have 2 or 3 axis." );
740                }
741                if ( axis[0].getUnits() == null ) {
742                    LOG.logDebug( "Could not check axis [0]: " + axis + " because it has no units." );
743                } else if ( axis[1].getUnits() == null ) {
744                    LOG.logDebug( "Could not check axis [1]: " + axis + " because it has no units." );
745                } else {
746                    if ( !( axis[0].getUnits().canConvert( Unit.RADIAN ) && axis[1].getUnits().canConvert( Unit.RADIAN ) ) ) {
747                        throw new XMLParsingException( "The axis of the geodetic (Geographic) crs are not consistent: "
748                                                       + axis[0] + ", " + axis[1] );
749                    }
750                    if ( axis.length == 3 ) {
751                        if ( axis[2].getUnits() == null ) {
752                            LOG.logDebug( "Could not check axis [2]: " + axis + " because it has no units." );
753                        } else {
754                            if ( !axis[2].getUnits().canConvert( Unit.METRE ) ) {
755                                throw new XMLParsingException(
756                                                               "The units of the third axis of the ellipsoidal CS type must be convertable to metre it is not: "
757                                                                                       + axis[2] );
758                            }
759                        }
760    
761                    }
762                }
763    
764            } else if ( "verticalcs".equalsIgnoreCase( rootElement.getLocalName() ) ) {
765                if ( axis.length != 1 ) {
766                    throw new XMLParsingException( "A vertical cs can only have 1 axis." );
767                }
768                if ( !axis[0].getUnits().canConvert( Unit.METRE ) ) {
769                    throw new XMLParsingException(
770                                                   "The axis of the vertical crs is not convertable to metre, other values are currently not supported: "
771                                                                           + axis[0] );
772                }
773            }
774    
775            return axis;
776        }
777    
778        /**
779         * @param rootElement
780         *            containing an gml:CoordinateSystemAxis type dom representation.
781         * @return an {@link Axis} instance initialized with values from the given xml-dom fragment or <code>null</code>
782         *         if the given root element is <code>null</code> if the axis could not be mapped it's orientation will be
783         *         {@link Axis#AO_OTHER}
784         * 
785         * @throws XMLParsingException
786         *             if the dom tree is not consistent or a required element is missing.
787         */
788        protected Axis parseAxis( Element rootElement )
789                                throws XMLParsingException {
790            if ( rootElement == null ) {
791                LOG.logDebug( "The given axis element is null, returning nothing" );
792                return null;
793            }
794            String name = getRequiredNodeAsString( rootElement, PRE + "axisAbbrev", nsContext );
795            String orientation = getRequiredNodeAsString( rootElement, PRE + "axisDirection", nsContext );
796            Unit unit = parseUnitOfMeasure( rootElement );
797            if ( unit == null ) {
798                unit = Unit.METRE;
799            }
800            return new Axis( unit, name, orientation );
801        }
802    
803        /**
804         * @param rootElement
805         *            containing a gml:Ellipsoid dom representation.
806         * @return a {@link Ellipsoid} instance initialized with values from the given xml-dom fragment or <code>null</code>
807         *         if the given root element is <code>null</code>
808         * @throws XMLParsingException
809         *             if the dom tree is not consistent or a required element is missing.
810         * 
811         */
812        protected Ellipsoid parseEllipsoid( Element rootElement )
813                                throws XMLParsingException {
814            if ( rootElement == null ) {
815                LOG.logDebug( "The given ellipsoid element is null, returning nothing" );
816                return null;
817            }
818            Identifiable id = parseIdentifiedObject( rootElement );
819            if ( id == null ) {
820                return null;
821            }
822            if ( LOG.isDebug() ) {
823                LOG.logDebug( "Parsing id of ellipsoid resulted in: " + Arrays.toString( id.getIdentifiers() ) );
824            }
825            Ellipsoid result = getCachedIdentifiable( Ellipsoid.class, id );
826            if ( result == null ) {
827    
828                Element semiMajorAxisElem = getRequiredElement( rootElement, PRE + "semiMajorAxis", nsContext );
829                double semiMajorAxis = getRequiredNodeAsDouble( semiMajorAxisElem, ".", nsContext );
830                Unit unit = parseUnitOfMeasure( semiMajorAxisElem );
831    
832                Element otherParam = getRequiredElement( rootElement, PRE + "secondDefiningParameter/" + PRE
833                                                                      + "SecondDefiningParameter", nsContext );
834                Element param = getElement( otherParam, PRE + "inverseFlattening", nsContext );
835                int type = 0;// inverseFlattening
836                if ( param == null ) {
837                    param = getElement( otherParam, PRE + "semiMinorAxis", nsContext );
838                    if ( param == null ) {
839                        param = getElement( otherParam, PRE + "isSphere", nsContext );
840                        if ( param == null ) {
841                            throw new XMLParsingException(
842                                                           "The ellipsoid is missing one of inverseFlattening, semiMinorAxis or isSphere" );
843                        }
844                        type = 2; // sphere
845                    } else {
846                        type = 1; // semiMinor
847                    }
848                }
849                double value = semiMajorAxis;
850                if ( type == 2 ) {
851                    result = new Ellipsoid( unit, semiMajorAxis, semiMajorAxis, id );
852                } else {
853                    Unit secondUnit = parseUnitOfMeasure( param );
854    
855                    value = XMLTools.getNodeAsDouble( param, ".", nsContext, Double.NaN );
856                    if ( Double.isNaN( value ) ) {
857                        throw new XMLParsingException( "The second defining ellipsoid parameter is missing." );
858                    }
859                    if ( secondUnit != null ) {
860                        if ( !secondUnit.canConvert( unit ) ) {
861                            throw new XMLParsingException(
862                                                           "Ellispoid axis can only contain comparable unit, supplied are: "
863                                                                                   + unit + " and " + secondUnit
864                                                                                   + " which are not convertable." );
865                        }
866                        if ( !secondUnit.equals( unit ) ) {
867                            value = secondUnit.convert( value, unit );
868                        }
869                    }
870                    if ( type == 0 ) {
871                        result = new Ellipsoid( semiMajorAxis, unit, value, id );
872                    } else {
873                        result = new Ellipsoid( unit, semiMajorAxis, value, id );
874                    }
875                }
876            }
877            return addIdToCache( result, false );
878        }
879    
880        /**
881         * @param rootElement
882         *            to create the pm from.
883         * @return {@link PrimeMeridian#GREENWICH} or the appropriate pm if a longitude is defined.
884         * @throws XMLParsingException
885         */
886        protected PrimeMeridian parsePrimeMeridian( Element rootElement )
887                                throws XMLParsingException {
888            if ( rootElement == null ) {
889                LOG.logDebug( "The given prime meridian element is null, returning Greenwich" );
890                return null;
891            }
892            Identifiable id = parseIdentifiedObject( rootElement );
893            if ( id == null ) {
894                return null;
895            }
896            if ( LOG.isDebug() ) {
897                LOG.logDebug( "Parsing id of prime meridian resulted in: " + Arrays.toString( id.getIdentifiers() ) );
898            }
899            PrimeMeridian result = getCachedIdentifiable( PrimeMeridian.class, id.getIdentifiers() );
900            // if ( cache == null ) {
901            // // check if the greenwich is already present.
902            // cache = getCachedIdentifiable( result.getIdentifiers() );
903            // }
904            if ( result == null ) {
905                Element gwLongitudeElem = getRequiredElement( rootElement, PRE + "greenwichLongitude", nsContext );
906                double gwLongitude = getRequiredNodeAsDouble( gwLongitudeElem, ".", nsContext );
907                Unit unit = parseUnitOfMeasure( gwLongitudeElem );
908                if ( unit != null && !unit.canConvert( Unit.RADIAN ) ) {
909                    LOG.logError( "The primemeridian must have RADIAN as a base unit." );
910                }
911    
912                if ( ( Math.abs( gwLongitude ) > 1E-11 ) ) {
913                    result = new PrimeMeridian( unit, gwLongitude, id );
914                }
915                if ( result == null ) {
916                    String[] ids = PrimeMeridian.GREENWICH.getIdentifiers();
917                    String[] foundIDS = id.getIdentifiers();
918                    String[] resultIDS = new String[ids.length + foundIDS.length];
919                    System.arraycopy( ids, 0, resultIDS, 0, ids.length );
920                    System.arraycopy( foundIDS, 0, resultIDS, foundIDS.length, foundIDS.length );
921                    id = new Identifiable( resultIDS, id.getNames(), id.getVersions(), id.getDescriptions(),
922                                           id.getAreasOfUse() );
923                    result = new PrimeMeridian( Unit.RADIAN, 0, id );
924                }
925            }
926            return addIdToCache( result, false );
927        }
928    
929        /**
930         * @param rootElement
931         *            containing a gml:VerticalCRS dom representation.
932         * @return a {@link VerticalCRS} instance initialized with values from the given xml-dom fragment or
933         *         <code>null</code> if the given root element is <code>null</code>
934         * @throws IOException
935         * @throws XMLParsingException
936         *             if the dom tree is not consistent or a required element is missing.
937         * 
938         */
939        protected VerticalCRS parseVerticalCRS( Element rootElement )
940                                throws XMLParsingException, IOException {
941            if ( rootElement == null ) {
942                LOG.logDebug( "The given vertical crs root element is null, returning nothing" );
943                return null;
944            }
945            Identifiable id = parseIdentifiedObject( rootElement );
946            if ( id == null ) {
947                return null;
948            }
949            if ( LOG.isDebug() ) {
950                LOG.logDebug( "Parsing id of vertical crs resulted in: " + Arrays.toString( id.getIdentifiers() ) );
951            }
952            Element verticalCSProp = getRequiredElement( rootElement, PRE + "verticalCS", nsContext );
953            Element verticalCSType = getRequiredXlinkedElement( verticalCSProp, PRE + "VerticalCS" );
954            // the axis will be one which is metre consistent.
955            Axis[] axis = parseAxisFromCSType( verticalCSType );
956            Element verticalDatumProp = getRequiredElement( rootElement, PRE + "verticalDatum", nsContext );
957            Element vdType = getRequiredXlinkedElement( verticalDatumProp, PRE + "VerticalDatum" );
958            VerticalDatum vd = parseVerticalDatum( vdType );
959    
960            return new VerticalCRS( vd, axis, id );
961        }
962    
963        /**
964         * @param rootElement
965         *            containing a gml:VerticalDatum dom representation.
966         * @return a {@link VerticalDatum} instance initialized with values from the given xml-dom fragment or
967         *         <code>null</code> if the given root element is <code>null</code>
968         * @throws XMLParsingException
969         *             if the dom tree is not consistent or a required element is missing.
970         * 
971         */
972        protected VerticalDatum parseVerticalDatum( Element rootElement )
973                                throws XMLParsingException {
974            if ( rootElement == null ) {
975                LOG.logDebug( "The given vertical datum root element is null, returning nothing" );
976                return null;
977            }
978            Identifiable id = parseIdentifiedObject( rootElement );
979            if ( id == null ) {
980                return null;
981            }
982            VerticalDatum result = getCachedIdentifiable( VerticalDatum.class, id );
983            if ( result == null ) {
984                result = new VerticalDatum( id );
985                if ( LOG.isDebug() ) {
986                    LOG.logDebug( "Parsing id of vertical datum resulted in: " + Arrays.toString( id.getIdentifiers() ) );
987                }
988            }
989            return addIdToCache( result, false );
990        }
991    
992        /**
993         * For now this method actually wraps all information in a gml:AbstractGeneralConversionType (or a derived subtype)
994         * into an Identifiable Object (used for the Projections).
995         * 
996         * @param rootElement
997         *            a gml:GeneralConversion element
998         * @param underlyingCRS
999         *            of the projection.
1000         * @return a Projection (Conversion) containing the mapped values from the given gml:Conversion
1001         *         xml-dom-representation.
1002         * @throws XMLParsingException
1003         *             if the dom tree is not consistent or a required element is missing.
1004         * @throws IOException
1005         */
1006        protected Projection parseProjection( Element rootElement, GeographicCRS underlyingCRS )
1007                                throws XMLParsingException, IOException {
1008            if ( rootElement == null || !"Conversion".equals( rootElement.getLocalName() ) ) {
1009                LOG.logDebug( "The given conversion root element is null, returning nothing" );
1010                return null;
1011            }
1012    
1013            Identifiable id = parseIdentifiedObject( rootElement );
1014            if ( id == null ) {
1015                return null;
1016            }
1017            if ( LOG.isDebug() ) {
1018                LOG.logDebug( "Parsing id of projection method resulted in: " + Arrays.toString( id.getIdentifiers() ) );
1019            }
1020    
1021            Projection result = getCachedIdentifiable( Projection.class, id.getIdentifiers() );
1022            if ( result == null ) {
1023                Element method = getRequiredElement( rootElement, PRE + "method", nsContext );
1024    
1025                Element conversionMethod = getRequiredXlinkedElement( method, PRE + "OperationMethod" );
1026                Identifiable conversionMethodID = parseIdentifiedObject( conversionMethod );
1027    
1028                double falseNorthing = 0, falseEasting = 0, scale = 1, firstParallelLatitude = 0, secondParallelLatitude = 0, trueScaleLatitude = 0;
1029                Point2d naturalOrigin = new Point2d();
1030                Unit units = Unit.METRE;
1031                List<Pair<Identifiable, Pair<Unit, Double>>> parameterValues = parseParameterValues( rootElement );
1032                for ( Pair<Identifiable, Pair<Unit, Double>> paramValue : parameterValues ) {
1033                    if ( paramValue != null ) {
1034                        Pair<Unit, Double> second = paramValue.second;
1035                        if ( second != null ) {
1036                            double value = second.second;
1037                            if ( !Double.isNaN( value ) ) {
1038                                Identifiable paramID = paramValue.first;
1039                                if ( paramID != null ) {
1040                                    SupportedProjectionParameters paramType = mapProjectionParameters( paramID.getIdentifiers() );
1041                                    Unit unit = second.first;
1042                                    // If a unit was given, convert the value to the internally used unit.
1043                                    if ( unit != null && !unit.isBaseType() ) {
1044                                        value = unit.toBaseUnits( value );
1045                                    }
1046                                    switch ( paramType ) {
1047                                    case FALSE_EASTING:
1048                                        falseEasting = value;
1049                                        break;
1050                                    case FALSE_NORTHING:
1051                                        falseNorthing = value;
1052                                        break;
1053                                    case FIRST_PARALLEL_LATITUDE:
1054                                        firstParallelLatitude = value;
1055                                        break;
1056                                    case LATITUDE_OF_NATURAL_ORIGIN:
1057                                        naturalOrigin.y = value;
1058                                        break;
1059                                    case LONGITUDE_OF_NATURAL_ORIGIN:
1060                                        naturalOrigin.x = value;
1061                                        break;
1062                                    case SCALE_AT_NATURAL_ORIGIN:
1063                                        scale = value;
1064                                        break;
1065                                    case SECOND_PARALLEL_LATITUDE:
1066                                        secondParallelLatitude = value;
1067                                        break;
1068                                    case TRUE_SCALE_LATITUDE:
1069                                        trueScaleLatitude = value;
1070                                    case NOT_SUPPORTED:
1071                                    default:
1072                                        LOG.logWarning( "The projection parameter: " + paramID.getIdAndName()
1073                                                        + " could not be mapped to any projection and will not be used." );
1074                                        break;
1075                                    }
1076                                }
1077    
1078                            }
1079                        }
1080                    }
1081                }
1082    
1083                SupportedProjections projection = mapProjections( conversionMethodID.getIdentifiers() );
1084                switch ( projection ) {
1085                case TRANSVERSE_MERCATOR:
1086                    boolean northernHemisphere = falseNorthing < 10000000;
1087                    result = new TransverseMercator( northernHemisphere, underlyingCRS, falseNorthing, falseEasting,
1088                                                     naturalOrigin, units, scale, id );
1089                    break;
1090                case LAMBERT_AZIMUTHAL_EQUAL_AREA:
1091                    result = new LambertAzimuthalEqualArea( underlyingCRS, falseNorthing, falseEasting, naturalOrigin,
1092                                                            units, scale, id );
1093                    break;
1094                case LAMBERT_CONFORMAL:
1095                    result = new LambertConformalConic( firstParallelLatitude, secondParallelLatitude, underlyingCRS,
1096                                                        falseNorthing, falseEasting, naturalOrigin, units, scale, id );
1097                    break;
1098                case STEREOGRAPHIC_AZIMUTHAL:
1099                    result = new StereographicAzimuthal( trueScaleLatitude, underlyingCRS, falseNorthing, falseEasting,
1100                                                         naturalOrigin, units, scale, id );
1101                    break;
1102                case STEREOGRAPHIC_AZIMUTHAL_ALTERNATIVE:
1103                    result = new StereographicAlternative( underlyingCRS, falseNorthing, falseEasting, naturalOrigin,
1104                                                           units, scale, id );
1105                    break;
1106                case NOT_SUPPORTED:
1107                default:
1108                    LOG.logError( "The conversion method (Projection): " + conversionMethodID.getIdentifier()
1109                                  + " is currently not supported by the deegree crs package." );
1110                }
1111    
1112                String remarks = XMLTools.getNodeAsString( rootElement, PRE + "remarks", nsContext, null );
1113                LOG.logDebug( "The remarks fo the conversion are not evaluated: " + remarks );
1114                String accuracy = XMLTools.getNodeAsString( rootElement, PRE + "coordinateOperationAccuracy", nsContext,
1115                                                            null );
1116                LOG.logDebug( "The coordinateOperationAccuracy for the conversion are not evaluated: " + accuracy );
1117            }
1118            return addIdToCache( result, false );
1119        }
1120    
1121        /**
1122         * @param rootElement
1123         *            which should contain a list of parameter Value properties.
1124         * @return a list of Pairs containing the parsed OperationParamter and the value as a double, converted to the units
1125         *         defined in the value element, or the empty list if the rootElement is <code>null</code> or no
1126         *         parameterValues were found.
1127         * @throws XMLParsingException
1128         *             if the dom tree is not consistent or a required element is missing.
1129         * @throws IOException
1130         */
1131        protected List<Pair<Identifiable, Pair<Unit, Double>>> parseParameterValues( Element rootElement )
1132                                throws XMLParsingException, IOException {
1133            List<Pair<Identifiable, Pair<Unit, Double>>> result = new ArrayList<Pair<Identifiable, Pair<Unit, Double>>>();
1134            if ( rootElement == null ) {
1135                LOG.logDebug( "The given parameter property root element is null, returning nothing" );
1136                return result;
1137            }
1138            List<Element> parameterValues = XMLTools.getElements( rootElement, PRE + "parameterValue", nsContext );
1139            if ( parameterValues == null || parameterValues.size() < 0 ) {
1140                LOG.logDebug( "The root element: " + rootElement.getLocalName() + " does not define any parameters." );
1141            } else {
1142                for ( Element paramValueProp : parameterValues ) {
1143                    if ( paramValueProp != null ) {
1144                        Pair<Identifiable, Pair<Unit, Double>> r = parseParameterValue( paramValueProp );
1145                        if ( r != null ) {
1146                            result.add( r );
1147                        }
1148                    }
1149                }
1150            }
1151            return result;
1152        }
1153    
1154        /**
1155         * @param rootElement
1156         *            containing a parameter Value property.
1157         * @return a Pair containing the parsed OperationParamter and the value as a double or null if the rootElement is
1158         *         <code>null</code>
1159         * @throws XMLParsingException
1160         *             if the dom tree is not consistent or a required element is missing.
1161         * @throws IOException
1162         */
1163        protected Pair<Identifiable, Pair<Unit, Double>> parseParameterValue( Element rootElement )
1164                                throws XMLParsingException, IOException {
1165            if ( rootElement == null ) {
1166                LOG.logDebug( "The given parameter property root element is null, returning nothing" );
1167                return null;
1168            }
1169            Element paramValue = getRequiredElement( rootElement, PRE + "ParameterValue", nsContext );
1170    
1171            Element operationParameterProp = getRequiredElement( paramValue, PRE + "operationParameter", nsContext );
1172    
1173            Element operationParameter = getRequiredXlinkedElement( operationParameterProp, PRE + "OperationParameter" );
1174            Identifiable paramID = parseIdentifiedObject( operationParameter );
1175    
1176            Element valueElem = XMLTools.getElement( paramValue, PRE + "value", nsContext );
1177            if ( valueElem == null ) {
1178                LOG.logDebug( "No gml:value found in the gml:Conversion/gml:parameterValue/gml:ParameterValue/ node, trying gml:integerValue instead." );
1179                valueElem = XMLTools.getElement( paramValue, PRE + "integerValue", nsContext );
1180                if ( valueElem == null ) {
1181                    LOG.logDebug( "Neither found a gml:integerValue in the gml:Conversion/gml:parameterValue/gml:ParameterValue/ node, ignoring this parameter value." );
1182                }
1183            }
1184    
1185            double value = XMLTools.getNodeAsDouble( valueElem, ".", nsContext, Double.NaN );
1186            Unit units = parseUnitOfMeasure( valueElem );
1187            return new Pair<Identifiable, Pair<Unit, Double>>( paramID, new Pair<Unit, Double>( units, value ) );
1188        }
1189    
1190        /**
1191         * Returns the unit defined by the uomAttribute given of the given element. This method will use a 'colon' heuristic
1192         * to determine if the given uom is actually an urn (and thus represents an xlink-type). This will then be resolved
1193         * and mapped onto an unit.
1194         * 
1195         * @param elementContainingUOMAttribute
1196         *            an element containing the 'uom' attribute which will be mapped onto a known unit.
1197         * @return the mapped {@link Unit} or <code>null</code> if the given uomAttribute is empty or <code>null</code>,
1198         *         or no appropriate mapping could be found.
1199         * @throws XMLParsingException
1200         */
1201        protected Unit parseUnitOfMeasure( Element elementContainingUOMAttribute )
1202                                throws XMLParsingException {
1203            if ( elementContainingUOMAttribute == null ) {
1204                return null;
1205            }
1206    
1207            String uomAttribute = elementContainingUOMAttribute.getAttribute( "uom" );
1208            if ( "".equals( uomAttribute.trim() ) ) {
1209                return null;
1210            }
1211            Unit result = getCachedIdentifiable( Unit.class, uomAttribute );
1212            if ( result == null ) {
1213                result = createUnitFromString( uomAttribute );
1214                if ( result == null && uomAttribute.indexOf( ":" ) != -1 ) {
1215                    LOG.logDebug( "Trying to resolve the uri: " + uomAttribute + " from a gml:value/@uom node" );
1216                    Element unitElement = null;
1217                    try {
1218                        unitElement = getResolver().getURIAsType( uomAttribute );
1219                    } catch ( IOException e ) {
1220                        // return null
1221                    }
1222                    if ( unitElement == null ) {
1223                        LOG.logError( "Although an uri was determined, the XLinkresolver was not able to retrieve a valid XML-DOM representation of the uom-uri. Error while resolving the following uom uri: "
1224                                      + uomAttribute + "." );
1225                    } else {
1226                        Identifiable unitID = parseIdentifiedObject( unitElement );
1227                        if ( unitID != null ) {
1228                            String[] ids = unitID.getIdentifiers();
1229                            for ( int i = 0; i < ids.length && result == null; ++i ) {
1230                                result = createUnitFromString( ids[i] );
1231                            }
1232                        }
1233    
1234                    }
1235                }
1236            }
1237    
1238            return addIdToCache( result, false );
1239        }
1240    
1241        /**
1242         * convenience method to retrieve a given required element either by resolving a optional xlink or by evaluating the
1243         * required element denoted by the xpath.
1244         * 
1245         * @param propertyElement
1246         *            to resolve an xlink from.
1247         * @param alternativeXPath
1248         *            denoting a path to the required node starting from the given propertyElement.
1249         * @return the dom-element in the xlink:href attribute of the given propertyElement or the required alternativeXPath
1250         *         element.
1251         * @throws XMLParsingException
1252         *             if the given propertyElement is <code>null</code> or the resulting xml dom-tree could not be parsed
1253         *             or the alternative xpath does not result in an Element.
1254         * @throws IOException
1255         *             if the xlink could not be properly resolved
1256         */
1257        protected Element getRequiredXlinkedElement( Element propertyElement, String alternativeXPath )
1258                                throws XMLParsingException, IOException {
1259            if ( propertyElement == null ) {
1260                throw new XMLParsingException( "The propertyElement may not be null" );
1261            }
1262            Element child = retrieveAndResolveXLink( propertyElement );
1263            if ( child == null ) {
1264                child = getRequiredElement( propertyElement, alternativeXPath, nsContext );
1265            }
1266            return child;
1267        }
1268    
1269        /**
1270         * Retrieves the xlink:href of the given rootElement and use the XLinkResolver to resolve the xlink if it was given.
1271         * 
1272         * @param rootElement
1273         *            to retrieve and resolve
1274         * @return the resolved xlink:href attribute as an xml-dom element or <code>null</code> if the xlink could not be
1275         *         resolved (or was not given) or the rootElement is null.
1276         * @throws IOException
1277         */
1278        protected Element retrieveAndResolveXLink( Element rootElement )
1279                                throws IOException {
1280            if ( rootElement == null ) {
1281                LOG.logDebug( "Rootelement is null no xlink to retrieve." );
1282                return null;
1283            }
1284            String xlink = retrieveXLink( rootElement );
1285            Element result = null;
1286            if ( !"".equals( xlink ) ) {
1287                LOG.logDebug( "Found an xlink: " + xlink );
1288                // The conversion is given by a link, so resolve it.
1289                result = getResolver().getURIAsType( xlink );
1290                if ( result == null ) {
1291                    LOG.logError( "Although an xlink was given, the XLInkresolver was not able to retrieve a valid XML-DOM representation of the uri it denotes. Error while resolving the following conversion uri: "
1292                                  + xlink + ". No further evaluation can be done." );
1293                }
1294            } else {
1295                LOG.logDebug( "No xlink found in: " + rootElement.getLocalName() );
1296            }
1297            return result;
1298        }
1299    
1300        public Identifiable getIdentifiable( String id )
1301                                throws CRSConfigurationException {
1302            Identifiable result = getCachedIdentifiable( id );
1303            if ( result == null ) {
1304                throw new UnsupportedOperationException(
1305                                                         "The retrieval of an arbitrary Identifiable Object is currently not supported by the GML Provider." );
1306            }
1307            return result;
1308    
1309        }
1310    
1311        /**
1312         * Find an xlink:href attribute and return it's value, if not found, the empty String will be returned.
1313         * 
1314         * @param rootElement
1315         *            to get the attribute from.
1316         * @return the trimmed xlink:href attribute value or the empty String if not found or the rootElement is null;
1317         */
1318        protected String retrieveXLink( Element rootElement ) {
1319            if ( rootElement == null ) {
1320                return "";
1321            }
1322            return rootElement.getAttributeNS( CommonNamespaces.XLNNS.toASCIIString(), "href" ).trim();
1323        }
1324    
1325        public Transformation getTransformation( CoordinateSystem sourceCRS, CoordinateSystem targetCRS )
1326                                throws CRSConfigurationException {
1327            return getResolver().getTransformation( sourceCRS, targetCRS );
1328        }
1329    }