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