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 }