001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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: 27765 $, $Date: 2010-11-04 07:50:32 +0100 (Do, 04 Nov 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 1332 public List<Transformation> getTransformations() { 1333 return getResolver().getTransformations(); 1334 } 1335 }