EVOLUTION-MANAGER
Edit File: ogr_srs_xml.cpp
/****************************************************************************** * * Project: OpenGIS Simple Features Reference Implementation * Purpose: OGRSpatialReference interface to OGC XML (014r4). * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2001, Frank Warmerdam (warmerdam@pobox.com) * Copyright (c) 2008-2012, Even Rouault <even dot rouault at mines-paris dot org> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "cpl_port.h" #include "ogr_srs_api.h" #include <cstdio> #include <cstdlib> #include <cstring> #include "cpl_conv.h" #include "cpl_error.h" #include "cpl_minixml.h" #include "cpl_multiproc.h" #include "cpl_string.h" #include "ogr_core.h" #include "ogr_p.h" #include "ogr_spatialref.h" CPL_CVSID("$Id: ogr_srs_xml.cpp 8e5eeb35bf76390e3134a4ea7076dab7d478ea0e 2018-11-14 22:55:13 +0100 Even Rouault $") /************************************************************************/ /* parseURN() */ /* */ /* Parses requested sections out of URN. The passed in URN */ /* *is* altered but the returned values point into the */ /* original string. */ /************************************************************************/ static bool parseURN( char *pszURN, const char **ppszObjectType, const char **ppszAuthority, const char **ppszCode, const char **ppszVersion = nullptr ) { if( ppszObjectType != nullptr ) *ppszObjectType = ""; if( ppszAuthority != nullptr ) *ppszAuthority = ""; if( ppszCode != nullptr ) *ppszCode = ""; if( ppszVersion != nullptr ) *ppszVersion = ""; /* -------------------------------------------------------------------- */ /* Verify prefix. */ /* -------------------------------------------------------------------- */ if( !STARTS_WITH_CI(pszURN, "urn:ogc:def:") ) return false; /* -------------------------------------------------------------------- */ /* Extract object type */ /* -------------------------------------------------------------------- */ if( ppszObjectType != nullptr ) *ppszObjectType = pszURN + 12; int i = 12; while( pszURN[i] != ':' && pszURN[i] != '\0' ) i++; if( pszURN[i] == '\0' ) return false; pszURN[i] = '\0'; i++; /* -------------------------------------------------------------------- */ /* Extract authority */ /* -------------------------------------------------------------------- */ if( ppszAuthority != nullptr ) *ppszAuthority = pszURN + i; while( pszURN[i] != ':' && pszURN[i] != '\0' ) i++; if( pszURN[i] == '\0' ) return false; pszURN[i] = '\0'; i++; /* -------------------------------------------------------------------- */ /* Extract version */ /* -------------------------------------------------------------------- */ if( ppszVersion != nullptr ) *ppszVersion = pszURN + i; while( pszURN[i] != ':' && pszURN[i] != '\0' ) i++; if( pszURN[i] == '\0' ) return false; pszURN[i] = '\0'; i++; /* -------------------------------------------------------------------- */ /* Extract code. */ /* -------------------------------------------------------------------- */ if( ppszCode != nullptr ) *ppszCode = pszURN + i; return true; } /************************************************************************/ /* addURN() */ /************************************************************************/ static void addURN( CPLXMLNode *psTarget, const char *pszAuthority, const char *pszObjectType, int nCode, const char *pszVersion = "" ) { if( pszVersion == nullptr ) pszVersion = ""; char szURN[200] = {}; CPLAssert( strlen(pszAuthority) + strlen(pszObjectType) < sizeof(szURN) - 30 ); snprintf( szURN, sizeof(szURN), "urn:ogc:def:%s:%s:%s:", pszObjectType, pszAuthority, pszVersion ); if( nCode != 0 ) snprintf( szURN + strlen(szURN), sizeof(szURN) - strlen(szURN), "%d", nCode ); CPLCreateXMLNode( CPLCreateXMLNode( psTarget, CXT_Attribute, "xlink:href" ), CXT_Text, szURN ); } /************************************************************************/ /* AddValueIDWithURN() */ /* */ /* Adds element of the form <ElementName */ /* xlink:href="urn_without_id">id</ElementName>" */ /************************************************************************/ static CPLXMLNode * AddValueIDWithURN( CPLXMLNode *psTarget, const char *pszElement, const char *pszAuthority, const char *pszObjectType, int nCode, const char *pszVersion = "" ) { CPLXMLNode *psElement = CPLCreateXMLNode( psTarget, CXT_Element, pszElement ); addURN( psElement, pszAuthority, pszObjectType, nCode, pszVersion ); return psElement; } /************************************************************************/ /* addAuthorityIDBlock() */ /* */ /* Creates a structure like: */ /* <srsId> */ /* <name codeSpace="urn">code</name> */ /* </srsId> */ /************************************************************************/ static CPLXMLNode *addAuthorityIDBlock( CPLXMLNode *psTarget, const char *pszElement, const char *pszAuthority, const char *pszObjectType, int nCode, const char *pszVersion = "" ) { /* -------------------------------------------------------------------- */ /* Prepare partial URN without the actual code. */ /* -------------------------------------------------------------------- */ if( pszVersion == nullptr ) pszVersion = ""; char szURN[200] = {}; CPLAssert( strlen(pszAuthority) + strlen(pszObjectType) < sizeof(szURN) - 30 ); snprintf( szURN, sizeof(szURN), "urn:ogc:def:%s:%s:%s:", pszObjectType, pszAuthority, pszVersion ); /* -------------------------------------------------------------------- */ /* Prepare the base name, eg. <srsID>. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psElement = CPLCreateXMLNode( psTarget, CXT_Element, pszElement ); /* -------------------------------------------------------------------- */ /* Prepare the name element. */ /* -------------------------------------------------------------------- */ CPLXMLNode * psName = CPLCreateXMLNode( psElement, CXT_Element, "gml:name" ); /* -------------------------------------------------------------------- */ /* Prepare the codespace attribute. */ /* -------------------------------------------------------------------- */ CPLCreateXMLNode( CPLCreateXMLNode( psName, CXT_Attribute, "codeSpace" ), CXT_Text, szURN ); /* -------------------------------------------------------------------- */ /* Attach code value to name node. */ /* -------------------------------------------------------------------- */ char szCode[32] = {}; snprintf( szCode, sizeof(szCode), "%d", nCode ); CPLCreateXMLNode( psName, CXT_Text, szCode ); return psElement; } /************************************************************************/ /* addGMLId() */ /************************************************************************/ static void addGMLId( CPLXMLNode *psParent ) { static CPLMutex *hGMLIdMutex = nullptr; CPLMutexHolderD( &hGMLIdMutex ); static int nNextGMLId = 1; char szIdText[40] = {}; snprintf( szIdText, sizeof(szIdText), "ogrcrs%d", nNextGMLId++ ); CPLCreateXMLNode( CPLCreateXMLNode( psParent, CXT_Attribute, "gml:id" ), CXT_Text, szIdText ); } /************************************************************************/ /* exportAuthorityToXML() */ /************************************************************************/ static CPLXMLNode *exportAuthorityToXML( const OGR_SRSNode *poAuthParent, const char *pszTagName, CPLXMLNode *psXMLParent, const char *pszObjectType, int bUseSubName = TRUE ) { /* -------------------------------------------------------------------- */ /* Get authority node from parent. */ /* -------------------------------------------------------------------- */ const int nAuthority = poAuthParent->FindChild("AUTHORITY"); if( nAuthority == -1 ) return nullptr; const OGR_SRSNode *poAuthority = poAuthParent->GetChild(nAuthority); /* -------------------------------------------------------------------- */ /* Create identification. */ /* -------------------------------------------------------------------- */ if(poAuthority->GetChildCount() < 2) return nullptr; const char *pszCodeSpace = poAuthority->GetChild(0)->GetValue(); const char *pszCode = poAuthority->GetChild(1)->GetValue(); const char *pszEdition = nullptr; if( bUseSubName ) return addAuthorityIDBlock(psXMLParent, pszTagName, pszCodeSpace, pszObjectType, atoi(pszCode), pszEdition); return AddValueIDWithURN(psXMLParent, pszTagName, pszCodeSpace, pszObjectType, atoi(pszCode), pszEdition); } /************************************************************************/ /* addProjArg() */ /************************************************************************/ static void addProjArg( const OGRSpatialReference *poSRS, CPLXMLNode *psBase, const char *pszMeasureType, double dfDefault, int nParameterID, const char *pszWKTName ) { CPLXMLNode *psNode = CPLCreateXMLNode(psBase, CXT_Element, "gml:usesValue"); /* -------------------------------------------------------------------- */ /* Handle the UOM. */ /* -------------------------------------------------------------------- */ const char *pszUOMValue = EQUAL(pszMeasureType, "Angular") ? "urn:ogc:def:uom:EPSG::9102" : "urn:ogc:def:uom:EPSG::9001"; CPLXMLNode *psValue = CPLCreateXMLNode(psNode, CXT_Element, "gml:value"); CPLCreateXMLNode( CPLCreateXMLNode( psValue, CXT_Attribute, "uom" ), CXT_Text, pszUOMValue ); /* -------------------------------------------------------------------- */ /* Add the parameter value itself. */ /* -------------------------------------------------------------------- */ double dfParmValue = poSRS->GetNormProjParm( pszWKTName, dfDefault, nullptr ); CPLCreateXMLNode( psValue, CXT_Text, CPLString().Printf( "%.16g", dfParmValue ) ); /* -------------------------------------------------------------------- */ /* Add the valueOfParameter. */ /* -------------------------------------------------------------------- */ AddValueIDWithURN( psNode, "gml:valueOfParameter", "EPSG", "parameter", nParameterID ); } /************************************************************************/ /* addAxis() */ /* */ /* Added the <usesAxis> element and down. */ /************************************************************************/ static CPLXMLNode *addAxis( CPLXMLNode *psXMLParent, const char *pszAxis, // "Lat", "Long", "E" or "N" const OGR_SRSNode * /* poUnitsSrc */ ) { CPLXMLNode *psAxisXML = CPLCreateXMLNode( CPLCreateXMLNode( psXMLParent, CXT_Element, "gml:usesAxis" ), CXT_Element, "gml:CoordinateSystemAxis" ); if( !psAxisXML ) { CPLError( CE_Failure, CPLE_AppDefined, "addAxis failed." ); return nullptr; } addGMLId( psAxisXML ); if( EQUAL(pszAxis, "Lat") ) { CPLCreateXMLNode( CPLCreateXMLNode( psAxisXML, CXT_Attribute, "gml:uom" ), CXT_Text, "urn:ogc:def:uom:EPSG::9102" ); CPLCreateXMLElementAndValue( psAxisXML, "gml:name", "Geodetic latitude" ); addAuthorityIDBlock( psAxisXML, "gml:axisID", "EPSG", "axis", 9901 ); CPLCreateXMLElementAndValue( psAxisXML, "gml:axisAbbrev", "Lat" ); CPLCreateXMLElementAndValue( psAxisXML, "gml:axisDirection", "north" ); } else if( EQUAL(pszAxis, "Long") ) { CPLCreateXMLNode( CPLCreateXMLNode( psAxisXML, CXT_Attribute, "gml:uom" ), CXT_Text, "urn:ogc:def:uom:EPSG::9102" ); CPLCreateXMLElementAndValue( psAxisXML, "gml:name", "Geodetic longitude" ); addAuthorityIDBlock( psAxisXML, "gml:axisID", "EPSG", "axis", 9902 ); CPLCreateXMLElementAndValue( psAxisXML, "gml:axisAbbrev", "Lon" ); CPLCreateXMLElementAndValue( psAxisXML, "gml:axisDirection", "east" ); } else if( EQUAL(pszAxis, "E") ) { CPLCreateXMLNode( CPLCreateXMLNode( psAxisXML, CXT_Attribute, "gml:uom" ), CXT_Text, "urn:ogc:def:uom:EPSG::9001" ); CPLCreateXMLElementAndValue( psAxisXML, "gml:name", "Easting" ); addAuthorityIDBlock( psAxisXML, "gml:axisID", "EPSG", "axis", 9906 ); CPLCreateXMLElementAndValue( psAxisXML, "gml:axisAbbrev", "E" ); CPLCreateXMLElementAndValue( psAxisXML, "gml:axisDirection", "east" ); } else if( EQUAL(pszAxis, "N") ) { CPLCreateXMLNode( CPLCreateXMLNode( psAxisXML, CXT_Attribute, "gml:uom" ), CXT_Text, "urn:ogc:def:uom:EPSG::9001" ); CPLCreateXMLElementAndValue( psAxisXML, "gml:name", "Northing" ); addAuthorityIDBlock( psAxisXML, "gml:axisID", "EPSG", "axis", 9907 ); CPLCreateXMLElementAndValue( psAxisXML, "gml:axisAbbrev", "N" ); CPLCreateXMLElementAndValue( psAxisXML, "gml:axisDirection", "north" ); } else { CPLAssert( false ); } return psAxisXML; } /************************************************************************/ /* exportGeogCSToXML() */ /************************************************************************/ static CPLXMLNode *exportGeogCSToXML( const OGRSpatialReference *poSRS ) { const OGR_SRSNode *poGeogCS = poSRS->GetAttrNode( "GEOGCS" ); if( poGeogCS == nullptr ) return nullptr; /* -------------------------------------------------------------------- */ /* Establish initial infrastructure. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psGCS_XML = CPLCreateXMLNode( nullptr, CXT_Element, "gml:GeographicCRS" ); addGMLId( psGCS_XML ); /* -------------------------------------------------------------------- */ /* Attach symbolic name (srsName). */ /* -------------------------------------------------------------------- */ CPLCreateXMLElementAndValue( psGCS_XML, "gml:srsName", poGeogCS->GetChild(0)->GetValue() ); /* -------------------------------------------------------------------- */ /* Does the overall coordinate system have an authority? If so */ /* attach as an identification section. */ /* -------------------------------------------------------------------- */ exportAuthorityToXML( poGeogCS, "gml:srsID", psGCS_XML, "crs" ); /* -------------------------------------------------------------------- */ /* Insert a big whack of fixed stuff defining the */ /* ellipsoidalCS. Basically this defines the axes and their */ /* units. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psECS = CPLCreateXMLNode( CPLCreateXMLNode( psGCS_XML, CXT_Element, "gml:usesEllipsoidalCS" ), CXT_Element, "gml:EllipsoidalCS" ); addGMLId( psECS ); CPLCreateXMLElementAndValue( psECS, "gml:csName", "ellipsoidal" ); addAuthorityIDBlock( psECS, "gml:csID", "EPSG", "cs", 6402 ); addAxis( psECS, "Lat", nullptr ); addAxis( psECS, "Long", nullptr ); /* -------------------------------------------------------------------- */ /* Start with the datum. */ /* -------------------------------------------------------------------- */ const OGR_SRSNode *poDatum = poGeogCS->GetNode( "DATUM" ); if( poDatum == nullptr ) { CPLDestroyXMLNode( psGCS_XML ); return nullptr; } CPLXMLNode *psDatumXML = CPLCreateXMLNode( CPLCreateXMLNode( psGCS_XML, CXT_Element, "gml:usesGeodeticDatum" ), CXT_Element, "gml:GeodeticDatum" ); addGMLId( psDatumXML ); /* -------------------------------------------------------------------- */ /* Set the datumName. */ /* -------------------------------------------------------------------- */ CPLCreateXMLElementAndValue( psDatumXML, "gml:datumName", poDatum->GetChild(0)->GetValue() ); /* -------------------------------------------------------------------- */ /* Set authority id info if available. */ /* -------------------------------------------------------------------- */ exportAuthorityToXML( poDatum, "gml:datumID", psDatumXML, "datum" ); /* -------------------------------------------------------------------- */ /* Setup prime meridian information. */ /* -------------------------------------------------------------------- */ const OGR_SRSNode *poPMNode = poGeogCS->GetNode( "PRIMEM" ); const char *pszPMName = "Greenwich"; double dfPMOffset = poSRS->GetPrimeMeridian( &pszPMName ); CPLXMLNode *psPM = CPLCreateXMLNode( CPLCreateXMLNode(psDatumXML, CXT_Element, "gml:usesPrimeMeridian"), CXT_Element, "gml:PrimeMeridian"); addGMLId( psPM ); CPLCreateXMLElementAndValue( psPM, "gml:meridianName", pszPMName ); if( poPMNode ) exportAuthorityToXML( poPMNode, "gml:meridianID", psPM, "meridian" ); CPLXMLNode *psAngle = CPLCreateXMLNode( CPLCreateXMLNode( psPM, CXT_Element, "gml:greenwichLongitude" ), CXT_Element, "gml:angle" ); CPLCreateXMLNode( CPLCreateXMLNode( psAngle, CXT_Attribute, "uom" ), CXT_Text, "urn:ogc:def:uom:EPSG::9102" ); CPLCreateXMLNode( psAngle, CXT_Text, CPLString().Printf( "%.16g", dfPMOffset ) ); /* -------------------------------------------------------------------- */ /* Translate the ellipsoid. */ /* -------------------------------------------------------------------- */ const OGR_SRSNode *poEllipsoid = poDatum->GetNode( "SPHEROID" ); if( poEllipsoid != nullptr ) { CPLXMLNode *psEllipseXML = CPLCreateXMLNode( CPLCreateXMLNode(psDatumXML, CXT_Element, "gml:usesEllipsoid" ), CXT_Element, "gml:Ellipsoid" ); addGMLId( psEllipseXML ); CPLCreateXMLElementAndValue( psEllipseXML, "gml:ellipsoidName", poEllipsoid->GetChild(0)->GetValue() ); exportAuthorityToXML( poEllipsoid, "gml:ellipsoidID", psEllipseXML, "ellipsoid"); CPLXMLNode *psParmXML = CPLCreateXMLNode( psEllipseXML, CXT_Element, "gml:semiMajorAxis" ); CPLCreateXMLNode( CPLCreateXMLNode(psParmXML, CXT_Attribute, "uom"), CXT_Text, "urn:ogc:def:uom:EPSG::9001" ); CPLCreateXMLNode( psParmXML, CXT_Text, poEllipsoid->GetChild(1)->GetValue() ); psParmXML = CPLCreateXMLNode( CPLCreateXMLNode( psEllipseXML, CXT_Element, "gml:secondDefiningParameter" ), CXT_Element, "gml:inverseFlattening" ); CPLCreateXMLNode( CPLCreateXMLNode(psParmXML, CXT_Attribute, "uom"), CXT_Text, "urn:ogc:def:uom:EPSG::9201" ); CPLCreateXMLNode( psParmXML, CXT_Text, poEllipsoid->GetChild(2)->GetValue() ); } return psGCS_XML; } /************************************************************************/ /* exportProjCSToXML() */ /************************************************************************/ static CPLXMLNode *exportProjCSToXML( const OGRSpatialReference *poSRS ) { const OGR_SRSNode *poProjCS = poSRS->GetAttrNode( "PROJCS" ); if( poProjCS == nullptr ) return nullptr; /* -------------------------------------------------------------------- */ /* Establish initial infrastructure. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psCRS_XML = CPLCreateXMLNode( nullptr, CXT_Element, "gml:ProjectedCRS" ); addGMLId( psCRS_XML ); /* -------------------------------------------------------------------- */ /* Attach symbolic name (a name in a nameset). */ /* -------------------------------------------------------------------- */ CPLCreateXMLElementAndValue( psCRS_XML, "gml:srsName", poProjCS->GetChild(0)->GetValue() ); /* -------------------------------------------------------------------- */ /* Add authority info if we have it. */ /* -------------------------------------------------------------------- */ exportAuthorityToXML( poProjCS, "gml:srsID", psCRS_XML, "crs" ); /* -------------------------------------------------------------------- */ /* Use the GEOGCS as a <baseCRS> */ /* -------------------------------------------------------------------- */ CPLXMLNode *psBaseCRSXML = CPLCreateXMLNode( psCRS_XML, CXT_Element, "gml:baseCRS" ); CPLAddXMLChild( psBaseCRSXML, exportGeogCSToXML( poSRS ) ); /* -------------------------------------------------------------------- */ /* Our projected coordinate system is "defined by Conversion". */ /* -------------------------------------------------------------------- */ CPLXMLNode *psDefinedBy = CPLCreateXMLNode( psCRS_XML, CXT_Element, "gml:definedByConversion" ); /* -------------------------------------------------------------------- */ /* Projections are handled as ParameterizedTransformations. */ /* -------------------------------------------------------------------- */ const char *pszProjection = poSRS->GetAttrValue("PROJECTION"); CPLXMLNode *psConv = CPLCreateXMLNode( psDefinedBy, CXT_Element, "gml:Conversion"); addGMLId( psConv ); CPLCreateXMLNode( CPLCreateXMLNode(psConv, CXT_Element, "gml:coordinateOperationName"), CXT_Text, pszProjection); /* -------------------------------------------------------------------- */ /* Transverse Mercator */ /* -------------------------------------------------------------------- */ if( pszProjection == nullptr ) { CPLError(CE_Failure, CPLE_NotSupported, "No projection method"); } else if( EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR) ) { AddValueIDWithURN( psConv, "gml:usesMethod", "EPSG", "method", 9807 ); addProjArg( poSRS, psConv, "Angular", 0.0, 8801, SRS_PP_LATITUDE_OF_ORIGIN ); addProjArg( poSRS, psConv, "Angular", 0.0, 8802, SRS_PP_CENTRAL_MERIDIAN ); addProjArg( poSRS, psConv, "Unitless", 1.0, 8805, SRS_PP_SCALE_FACTOR ); addProjArg( poSRS, psConv, "Linear", 0.0, 8806, SRS_PP_FALSE_EASTING ); addProjArg( poSRS, psConv, "Linear", 0.0, 8807, SRS_PP_FALSE_NORTHING ); } /* -------------------------------------------------------------------- */ /* Lambert Conformal Conic */ /* -------------------------------------------------------------------- */ else if( EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP) ) { AddValueIDWithURN( psConv, "gml:usesMethod", "EPSG", "method", 9801 ); addProjArg( poSRS, psConv, "Angular", 0.0, 8801, SRS_PP_LATITUDE_OF_ORIGIN ); addProjArg( poSRS, psConv, "Angular", 0.0, 8802, SRS_PP_CENTRAL_MERIDIAN ); addProjArg( poSRS, psConv, "Unitless", 1.0, 8805, SRS_PP_SCALE_FACTOR ); addProjArg( poSRS, psConv, "Linear", 0.0, 8806, SRS_PP_FALSE_EASTING ); addProjArg( poSRS, psConv, "Linear", 0.0, 8807, SRS_PP_FALSE_NORTHING ); } else { CPLError(CE_Warning, CPLE_NotSupported, "Unhandled projection method %s", pszProjection); } /* -------------------------------------------------------------------- */ /* Define the cartesian coordinate system. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psCCS = CPLCreateXMLNode( CPLCreateXMLNode( psCRS_XML, CXT_Element, "gml:usesCartesianCS" ), CXT_Element, "gml:CartesianCS" ); addGMLId( psCCS ); CPLCreateXMLElementAndValue( psCCS, "gml:csName", "Cartesian" ); addAuthorityIDBlock( psCCS, "gml:csID", "EPSG", "cs", 4400 ); addAxis( psCCS, "E", nullptr ); addAxis( psCCS, "N", nullptr ); return psCRS_XML; } /************************************************************************/ /* exportToXML() */ /************************************************************************/ /** * \brief Export coordinate system in XML format. * * Converts the loaded coordinate reference system into XML format * to the extent possible. The string returned in ppszRawXML should be * deallocated by the caller with CPLFree() when no longer needed. * * LOCAL_CS coordinate systems are not translatable. An empty string * will be returned along with OGRERR_NONE. * * This method is the equivalent of the C function OSRExportToXML(). * * @param ppszRawXML pointer to which dynamically allocated XML definition * will be assigned. * @param pszDialect currently ignored. The dialect used is GML based. * * @return OGRERR_NONE on success or an error code on failure. */ OGRErr OGRSpatialReference::exportToXML( char **ppszRawXML, CPL_UNUSED const char * pszDialect ) const { CPLXMLNode *psXMLTree = nullptr; if( IsGeographic() ) { psXMLTree = exportGeogCSToXML( this ); } else if( IsProjected() ) { psXMLTree = exportProjCSToXML( this ); } else return OGRERR_UNSUPPORTED_SRS; *ppszRawXML = CPLSerializeXMLTree( psXMLTree ); CPLDestroyXMLNode( psXMLTree ); return OGRERR_NONE; } /************************************************************************/ /* OSRExportToXML() */ /************************************************************************/ /** * \brief Export coordinate system in XML format. * * This function is the same as OGRSpatialReference::exportToXML(). */ OGRErr OSRExportToXML( OGRSpatialReferenceH hSRS, char **ppszRawXML, const char *pszDialect ) { VALIDATE_POINTER1( hSRS, "OSRExportToXML", OGRERR_FAILURE ); return OGRSpatialReference::FromHandle(hSRS)->exportToXML( ppszRawXML, pszDialect ); } #ifdef notdef /************************************************************************/ /* importXMLUnits() */ /************************************************************************/ static void importXMLUnits( CPLXMLNode *psSrcXML, const char *pszClass, OGRSpatialReference *poSRS, const char *pszTarget) { OGR_SRSNode *poNode = poSRS->GetAttrNode( pszTarget ); CPLAssert( EQUAL(pszClass, "AngularUnit") || EQUAL(pszClass, "LinearUnit") ); psSrcXML = CPLGetXMLNode( psSrcXML, pszClass ); OGR_SRSNode *poUnits = NULL; const char *pszUnitName = NULL; const char *pszUnitsPer = NULL; // TODO(schwehr): Remove the goto. if( psSrcXML == NULL ) goto DefaultTarget; pszUnitName = CPLGetXMLValue( psSrcXML, "NameSet.name", "unnamed" ); pszUnitsPer = EQUAL(pszClass, "AngularUnit") ? CPLGetXMLValue( psSrcXML, "radiansPerUnit", NULL ) : CPLGetXMLValue( psSrcXML, "metresPerUnit", NULL ); if( pszUnitsPer == NULL ) { CPLDebug( "OGR_SRS_XML", "Missing PerUnit value for %s.", pszClass ); goto DefaultTarget; } if( poNode == NULL ) { CPLDebug("OGR_SRS_XML", "Can't find %s in importXMLUnits.", pszTarget); goto DefaultTarget; } if( poNode->FindChild("UNIT") != -1 ) { poUnits = poNode->GetChild( poNode->FindChild( "UNIT" ) ); poUnits->GetChild(0)->SetValue( pszUnitName ); poUnits->GetChild(1)->SetValue( pszUnitsPer ); } else { poUnits = new OGR_SRSNode( "UNIT" ); poUnits->AddChild( new OGR_SRSNode( pszUnitName ) ); poUnits->AddChild( new OGR_SRSNode( pszUnitsPer ) ); poNode->AddChild( poUnits ); } return; DefaultTarget: poUnits = new OGR_SRSNode( "UNIT" ); if( EQUAL(pszClass, "AngularUnit") ) { poUnits->AddChild( new OGR_SRSNode( SRS_UA_DEGREE ) ); poUnits->AddChild( new OGR_SRSNode( SRS_UA_DEGREE_CONV ) ); } else { poUnits->AddChild( new OGR_SRSNode( SRS_UL_METER ) ); poUnits->AddChild( new OGR_SRSNode( "1.0" ) ); } poNode->AddChild( poUnits ); } #endif /************************************************************************/ /* importXMLAuthority() */ /************************************************************************/ static void importXMLAuthority( CPLXMLNode *psSrcXML, OGRSpatialReference *poSRS, const char *pszSourceKey, const char *pszTargetKey ) { CPLXMLNode *psIDNode = CPLGetXMLNode( psSrcXML, pszSourceKey ); CPLXMLNode *psNameNode = CPLGetXMLNode( psIDNode, "name" ); CPLXMLNode *psCodeSpace = CPLGetXMLNode( psNameNode, "codeSpace" ); if( psIDNode == nullptr || psNameNode == nullptr || psCodeSpace == nullptr ) return; char *pszURN = CPLStrdup(CPLGetXMLValue( psCodeSpace, "", "" )); const char *pszAuthority; const char *pszCode; if( !parseURN( pszURN, nullptr, &pszAuthority, &pszCode ) ) { CPLFree( pszURN ); return; } if( strlen(pszCode) == 0 ) pszCode = CPLGetXMLValue( psNameNode, "", "" ); const int nCode = pszCode != nullptr ? atoi(pszCode) :0; if( nCode != 0 ) poSRS->SetAuthority( pszTargetKey, pszAuthority, nCode ); CPLFree( pszURN ); } /************************************************************************/ /* ParseOGCDefURN() */ /* */ /* Parse out fields from a URN of the form: */ /* urn:ogc:def:parameter:EPSG:6.3:9707 */ /************************************************************************/ static bool ParseOGCDefURN( const char *pszURN, CPLString *poObjectType, CPLString *poAuthority, CPLString *poVersion, CPLString *poValue ) { if( poObjectType != nullptr ) *poObjectType = ""; if( poAuthority != nullptr ) *poAuthority = ""; if( poVersion != nullptr ) *poVersion = ""; if( poValue != nullptr ) *poValue = ""; if( pszURN == nullptr || !STARTS_WITH_CI(pszURN, "urn:ogc:def:") ) return false; char **papszTokens = CSLTokenizeStringComplex( pszURN + 12, ":", FALSE, TRUE ); if( CSLCount(papszTokens) != 4 ) { CSLDestroy( papszTokens ); return false; } if( poObjectType != nullptr ) *poObjectType = papszTokens[0]; if( poAuthority != nullptr ) *poAuthority = papszTokens[1]; if( poVersion != nullptr ) *poVersion = papszTokens[2]; if( poValue != nullptr ) *poValue = papszTokens[3]; CSLDestroy( papszTokens ); return true; } /************************************************************************/ /* getEPSGObjectCodeValue() */ /* */ /* Fetch a code value from the indicated node. Should work on */ /* something of the form <elem xlink:href="urn:...:n" /> or */ /* something of the form <elem xlink:href="urn:...:">n</a>. */ /************************************************************************/ static int getEPSGObjectCodeValue( CPLXMLNode *psNode, const char *pszEPSGObjectType, /*"method" */ int nDefault ) { if( psNode == nullptr ) return nDefault; const char* pszHrefVal = CPLGetXMLValue( psNode, "xlink:href", nullptr ); if( pszHrefVal == nullptr ) pszHrefVal = CPLGetXMLValue( psNode, "href", nullptr ); CPLString osObjectType; CPLString osAuthority; CPLString osValue; if( !ParseOGCDefURN( pszHrefVal, &osObjectType, &osAuthority, nullptr, &osValue ) ) return nDefault; if( !EQUAL(osAuthority, "EPSG") || !EQUAL(osObjectType, pszEPSGObjectType) ) return nDefault; if( !osValue.empty() ) return atoi(osValue); const char *pszValue = CPLGetXMLValue( psNode, "", nullptr); if( pszValue != nullptr ) return atoi(pszValue); return nDefault; } /************************************************************************/ /* getProjectionParm() */ /************************************************************************/ static double getProjectionParm( CPLXMLNode *psRootNode, int nParameterCode, const char * /*pszMeasureType */, double dfDefault ) { for( CPLXMLNode *psUsesParameter = psRootNode->psChild; psUsesParameter != nullptr; psUsesParameter = psUsesParameter->psNext ) { if( psUsesParameter->eType != CXT_Element ) continue; if( !EQUAL(psUsesParameter->pszValue, "usesParameterValue") && !EQUAL(psUsesParameter->pszValue, "usesValue") ) continue; if( getEPSGObjectCodeValue( CPLGetXMLNode(psUsesParameter, "valueOfParameter"), "parameter", 0 ) == nParameterCode ) { const char *pszValue = CPLGetXMLValue( psUsesParameter, "value", nullptr ); if( pszValue == nullptr ) return dfDefault; return CPLAtof(pszValue); } } return dfDefault; } /************************************************************************/ /* getNormalizedValue() */ /* */ /* Parse a node to get its numerical value, and then normalize */ /* into meters of degrees depending on the measure type. */ /************************************************************************/ static double getNormalizedValue( CPLXMLNode *psNode, const char *pszPath, const char * /*pszMeasure*/, double dfDefault ) { CPLXMLNode *psTargetNode = pszPath == nullptr || strlen(pszPath) == 0 ? psNode : CPLGetXMLNode( psNode, pszPath ); if( psTargetNode == nullptr ) return dfDefault; CPLXMLNode *psValueNode = psTargetNode->psChild; // Used after for. for( ; psValueNode != nullptr && psValueNode->eType != CXT_Text; psValueNode = psValueNode->psNext ) {} if( psValueNode == nullptr ) return dfDefault; // Add normalization later. return CPLAtof(psValueNode->pszValue); } /************************************************************************/ /* importGeogCSFromXML() */ /************************************************************************/ static OGRErr importGeogCSFromXML( OGRSpatialReference *poSRS, CPLXMLNode *psCRS ) { /* -------------------------------------------------------------------- */ /* Set the GEOGCS name from the srsName. */ /* -------------------------------------------------------------------- */ const char *pszGeogName = CPLGetXMLValue( psCRS, "srsName", "Unnamed GeogCS" ); /* -------------------------------------------------------------------- */ /* If we don't seem to have a detailed coordinate system */ /* definition, check if we can define based on an EPSG code. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psDatum = CPLGetXMLNode( psCRS, "usesGeodeticDatum.GeodeticDatum" ); if( psDatum == nullptr ) { OGRSpatialReference oIdSRS; oIdSRS.SetLocalCS( "dummy" ); importXMLAuthority( psCRS, &oIdSRS, "srsID", "LOCAL_CS" ); if( oIdSRS.GetAuthorityCode( "LOCAL_CS" ) != nullptr && oIdSRS.GetAuthorityName( "LOCAL_CS" ) != nullptr && EQUAL(oIdSRS.GetAuthorityName("LOCAL_CS"), "EPSG") ) { return poSRS->importFromEPSG( atoi(oIdSRS.GetAuthorityCode("LOCAL_CS")) ); } } /* -------------------------------------------------------------------- */ /* Get datum name. */ /* -------------------------------------------------------------------- */ const char *pszDatumName = CPLGetXMLValue( psDatum, "datumName", "Unnamed Datum" ); /* -------------------------------------------------------------------- */ /* Get ellipsoid information. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psE = CPLGetXMLNode( psDatum, "usesEllipsoid.Ellipsoid" ); const char *pszEllipsoidName = CPLGetXMLValue( psE, "ellipsoidName", "Unnamed Ellipsoid" ); const double dfSemiMajor = getNormalizedValue( psE, "semiMajorAxis", "Linear", SRS_WGS84_SEMIMAJOR ); const double dfInvFlattening = getNormalizedValue( psE, "secondDefiningParameter.inverseFlattening", "Unitless", 0.0 ); if( dfInvFlattening == 0.0 ) { CPLError( CE_Failure, CPLE_AppDefined, "Ellipsoid inverseFlattening corrupt or missing." ); return OGRERR_CORRUPT_DATA; } /* -------------------------------------------------------------------- */ /* Get the prime meridian. */ /* -------------------------------------------------------------------- */ const char *pszPMName = nullptr; double dfPMOffset = 0.0; CPLXMLNode *psPM = CPLGetXMLNode( psDatum, "usesPrimeMeridian.PrimeMeridian" ); if( psPM == nullptr ) { pszPMName = "Greenwich"; dfPMOffset = 0.0; } else { pszPMName = CPLGetXMLValue( psPM, "meridianName", "Unnamed Prime Meridian"); dfPMOffset = getNormalizedValue( psPM, "greenwichLongitude.angle", "Angular", 0.0 ); } /* -------------------------------------------------------------------- */ /* Set the geographic definition. */ /* -------------------------------------------------------------------- */ poSRS->SetGeogCS( pszGeogName, pszDatumName, pszEllipsoidName, dfSemiMajor, dfInvFlattening, pszPMName, dfPMOffset ); /* -------------------------------------------------------------------- */ /* Look for angular units. We don't check that all axes match */ /* at this time. */ /* -------------------------------------------------------------------- */ #if 0 // Does not compile. CPLXMLNode *psAxis = CPLGetXMLNode( psGeo2DCRS, "EllipsoidalCoordinateSystem.CoordinateAxis" ); importXMLUnits( psAxis, "AngularUnit", poSRS, "GEOGCS" ); #endif /* -------------------------------------------------------------------- */ /* Can we set authorities for any of the levels? */ /* -------------------------------------------------------------------- */ importXMLAuthority( psCRS, poSRS, "srsID", "GEOGCS" ); importXMLAuthority( psDatum, poSRS, "datumID", "GEOGCS|DATUM" ); importXMLAuthority( psE, poSRS, "ellipsoidID", "GEOGCS|DATUM|SPHEROID" ); importXMLAuthority( psDatum, poSRS, "usesPrimeMeridian.PrimeMeridian.meridianID", "GEOGCS|PRIMEM" ); return OGRERR_NONE; } /************************************************************************/ /* importProjCSFromXML() */ /************************************************************************/ static OGRErr importProjCSFromXML( OGRSpatialReference *poSRS, CPLXMLNode *psCRS ) { /* -------------------------------------------------------------------- */ /* Setup the PROJCS node with a name. */ /* -------------------------------------------------------------------- */ poSRS->SetProjCS( CPLGetXMLValue( psCRS, "srsName", "Unnamed" ) ); /* -------------------------------------------------------------------- */ /* Get authority information if available. If we got it, and */ /* we seem to be lacking inline definition values, try and */ /* define according to the EPSG code for the PCS. */ /* -------------------------------------------------------------------- */ importXMLAuthority( psCRS, poSRS, "srsID", "PROJCS" ); if( poSRS->GetAuthorityCode( "PROJCS" ) != nullptr && poSRS->GetAuthorityName( "PROJCS" ) != nullptr && EQUAL(poSRS->GetAuthorityName("PROJCS"), "EPSG") && (CPLGetXMLNode( psCRS, "definedByConversion.Conversion" ) == nullptr || CPLGetXMLNode( psCRS, "baseCRS.GeographicCRS" ) == nullptr) ) { return poSRS->importFromEPSG( atoi(poSRS->GetAuthorityCode("PROJCS")) ); } /* -------------------------------------------------------------------- */ /* Try to set the GEOGCS info. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psSubXML = CPLGetXMLNode( psCRS, "baseCRS.GeographicCRS" ); if( psSubXML != nullptr ) { const OGRErr eErr = importGeogCSFromXML( poSRS, psSubXML ); if( eErr != OGRERR_NONE ) return eErr; } /* -------------------------------------------------------------------- */ /* Get the conversion node. It should be the only child of the */ /* definedByConversion node. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psConv = CPLGetXMLNode(psCRS, "definedByConversion.Conversion"); if( psConv == nullptr || psConv->eType != CXT_Element ) { CPLError( CE_Failure, CPLE_AppDefined, "Unable to find a conversion node under the " "definedByConversion node of the ProjectedCRS." ); return OGRERR_CORRUPT_DATA; } /* -------------------------------------------------------------------- */ /* Determine the conversion method in effect. */ /* -------------------------------------------------------------------- */ const int nMethod = getEPSGObjectCodeValue( CPLGetXMLNode( psConv, "usesMethod"), "method", 0 ); /* -------------------------------------------------------------------- */ /* Transverse Mercator. */ /* -------------------------------------------------------------------- */ if( nMethod == 9807 ) { poSRS->SetTM( getProjectionParm( psConv, 8801, "Angular", 0.0 ), getProjectionParm( psConv, 8802, "Angular", 0.0 ), getProjectionParm( psConv, 8805, "Unitless", 1.0 ), getProjectionParm( psConv, 8806, "Linear", 0.0 ), getProjectionParm( psConv, 8807, "Linear", 0.0 ) ); } /* -------------------------------------------------------------------- */ /* Didn't recognise? */ /* -------------------------------------------------------------------- */ else { CPLError( CE_Failure, CPLE_AppDefined, "Conversion method %d not recognised.", nMethod ); return OGRERR_CORRUPT_DATA; } // Re-set authority as all editions above will have removed it importXMLAuthority( psCRS, poSRS, "srsID", "PROJCS" ); // Need to get linear units here! return OGRERR_NONE; } /************************************************************************/ /* importFromXML() */ /************************************************************************/ /** * \brief Import coordinate system from XML format (GML only currently). * * This method is the same as the C function OSRImportFromXML() * @param pszXML XML string to import * @return OGRERR_NONE on success or OGRERR_CORRUPT_DATA on failure. */ OGRErr OGRSpatialReference::importFromXML( const char *pszXML ) { Clear(); /* -------------------------------------------------------------------- */ /* Parse the XML. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psTree = CPLParseXMLString( pszXML ); if( psTree == nullptr ) return OGRERR_CORRUPT_DATA; CPLStripXMLNamespace( psTree, "gml", TRUE ); /* -------------------------------------------------------------------- */ /* Import according to the root node type. We walk through */ /* root elements as there is sometimes prefix stuff like */ /* <?xml>. */ /* -------------------------------------------------------------------- */ OGRErr eErr = OGRERR_UNSUPPORTED_SRS; for( CPLXMLNode* psNode = psTree; psNode != nullptr; psNode = psNode->psNext ) { if( EQUAL(psNode->pszValue, "GeographicCRS") ) { eErr = importGeogCSFromXML( this, psNode ); break; } else if( EQUAL(psNode->pszValue, "ProjectedCRS") ) { eErr = importProjCSFromXML( this, psNode ); break; } } CPLDestroyXMLNode( psTree ); return eErr; } /************************************************************************/ /* OSRImportFromXML() */ /************************************************************************/ /** * \brief Import coordinate system from XML format (GML only currently). * * This function is the same as OGRSpatialReference::importFromXML(). */ OGRErr OSRImportFromXML( OGRSpatialReferenceH hSRS, const char *pszXML ) { VALIDATE_POINTER1( hSRS, "OSRImportFromXML", OGRERR_FAILURE ); VALIDATE_POINTER1( pszXML, "OSRImportFromXML", OGRERR_FAILURE ); return OGRSpatialReference::FromHandle(hSRS)->importFromXML( pszXML ); }