/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  The OGRSpatialReference class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999,  Les Technologies SoftMap Inc.
 * Copyright (c) 2008-2013, 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_spatialref.h"

#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <string>
#include <vector>

#include "cpl_atomic_ops.h"
#include "cpl_conv.h"
#include "cpl_csv.h"
#include "cpl_error.h"
#include "cpl_http.h"
#include "cpl_multiproc.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#include "ogr_core.h"
#include "ogr_p.h"
#include "ogr_srs_api.h"

CPL_CVSID("$Id: ogrspatialreference.cpp 4942e6b19a5f82ab343ec686154644effd0fcbde 2018-04-18 21:13:03 +0200 Even Rouault $")

// The current opinion is that WKT longitudes like central meridian
// should be relative to Greenwich, not the prime meridian in use.
// Define the following if they should be relative to the prime meridian
// of then geogcs.
#undef WKT_LONGITUDE_RELATIVE_TO_PM

/************************************************************************/
/*                           ToPointer()                                */
/************************************************************************/

inline OGRSpatialReference* ToPointer(OGRSpatialReferenceH hSRS)
{
    return OGRSpatialReference::FromHandle(hSRS);
}

/************************************************************************/
/*                           ToHandle()                                 */
/************************************************************************/

inline OGRSpatialReferenceH ToHandle(OGRSpatialReference* poSRS)
{
    return OGRSpatialReference::ToHandle(poSRS);
}

/************************************************************************/
/*                           OGRsnPrintDouble()                         */
/************************************************************************/

void OGRsnPrintDouble( char * pszStrBuf, size_t size, double dfValue );

void OGRsnPrintDouble( char * pszStrBuf, size_t size, double dfValue )

{
    CPLsnprintf( pszStrBuf, size, "%.16g", dfValue );

    const size_t nLen = strlen(pszStrBuf);

    // The following hack is intended to truncate some "precision" in cases
    // that appear to be roundoff error.
    if( nLen > 15
        && (strcmp(pszStrBuf+nLen-6, "999999") == 0
            || strcmp(pszStrBuf+nLen-6, "000001") == 0) )
    {
        CPLsnprintf( pszStrBuf, size, "%.15g", dfValue );
    }

    // Force to user periods regardless of locale.
    if( strchr( pszStrBuf, ',' ) != nullptr )
    {
        char * const pszDelim = strchr( pszStrBuf, ',' );
        *pszDelim = '.';
    }
}

/************************************************************************/
/*                        OGRSpatialReference()                         */
/************************************************************************/

/**
 * \brief Constructor.
 *
 * This constructor takes an optional string argument which if passed
 * should be a WKT representation of an SRS.  Passing this is equivalent
 * to not passing it, and then calling importFromWkt() with the WKT string.
 *
 * Note that newly created objects are given a reference count of one.
 *
 * The C function OSRNewSpatialReference() does the same thing as this
 * constructor.
 *
 * @param pszWKT well known text definition to which the object should
 * be initialized, or NULL (the default).
 */

OGRSpatialReference::OGRSpatialReference( const char * pszWKT ) :
    dfFromGreenwich(0.0),
    dfToMeter(0.0),
    dfToDegrees(0.0),
    poRoot(nullptr),
    nRefCount(1),
    bNormInfoSet(FALSE)
{
    if( pszWKT != nullptr )
        importFromWkt( pszWKT );
}

/************************************************************************/
/*                       OSRNewSpatialReference()                       */
/************************************************************************/

/**
 * \brief Constructor.
 *
 * This function is the same as OGRSpatialReference::OGRSpatialReference()
 */
OGRSpatialReferenceH CPL_STDCALL OSRNewSpatialReference( const char *pszWKT )

{
    OGRSpatialReference * poSRS = new OGRSpatialReference();

    if( pszWKT != nullptr && strlen(pszWKT) > 0 )
    {
        if( poSRS->importFromWkt( pszWKT ) != OGRERR_NONE )
        {
            delete poSRS;
            poSRS = nullptr;
        }
    }

    return ToHandle( poSRS );
}

/************************************************************************/
/*                        OGRSpatialReference()                         */
/************************************************************************/

/** Simple copy constructor. See also Clone().
 * @param oOther other spatial reference
 */
OGRSpatialReference::OGRSpatialReference(const OGRSpatialReference &oOther) :
    dfFromGreenwich(0.0),
    dfToMeter(0.0),
    dfToDegrees(0.0),
    poRoot(nullptr),
    nRefCount(1),
    bNormInfoSet(FALSE)
{
    if( oOther.poRoot != nullptr )
        poRoot = oOther.poRoot->Clone();
}

/************************************************************************/
/*                        ~OGRSpatialReference()                        */
/************************************************************************/

/**
 * \brief OGRSpatialReference destructor.
 *
 * The C function OSRDestroySpatialReference() does the same thing as this
 * method. Preferred C++ method : OGRSpatialReference::DestroySpatialReference()
  *
 * @deprecated
 */

OGRSpatialReference::~OGRSpatialReference()

{
    delete poRoot;
}

/************************************************************************/
/*                      DestroySpatialReference()                       */
/************************************************************************/

/**
 * \brief OGRSpatialReference destructor.
 *
 * This static method will destroy a OGRSpatialReference.  It is
 * equivalent to calling delete on the object, but it ensures that the
 * deallocation is properly executed within the OGR libraries heap on
 * platforms where this can matter (win32).
 *
 * This function is the same as OSRDestroySpatialReference()
 *
 * @param poSRS the object to delete
 *
 * @since GDAL 1.7.0
 */

void OGRSpatialReference::DestroySpatialReference(OGRSpatialReference* poSRS)
{
    delete poSRS;
}

/************************************************************************/
/*                     OSRDestroySpatialReference()                     */
/************************************************************************/

/**
 * \brief OGRSpatialReference destructor.
 *
 * This function is the same as OGRSpatialReference::~OGRSpatialReference()
 * and OGRSpatialReference::DestroySpatialReference()
 *
 * @param hSRS the object to delete
 */
void CPL_STDCALL OSRDestroySpatialReference( OGRSpatialReferenceH hSRS )

{
    delete ToPointer(hSRS);
}

/************************************************************************/
/*                               Clear()                                */
/************************************************************************/

/**
 * \brief Wipe current definition.
 *
 * Returns OGRSpatialReference to a state with no definition, as it
 * exists when first created.  It does not affect reference counts.
 */

void OGRSpatialReference::Clear()

{
    delete poRoot;
    poRoot = nullptr;

    bNormInfoSet = FALSE;
    dfFromGreenwich = 1.0;
    dfToMeter = 1.0;
    dfToDegrees = 1.0;
}

/************************************************************************/
/*                             operator=()                              */
/************************************************************************/

/** Assignment operator.
 * @param oSource SRS to assing to *this
 * @return *this
 */
OGRSpatialReference &
OGRSpatialReference::operator=(const OGRSpatialReference &oSource)

{
    if( &oSource != this )
    {
        Clear();
#ifdef CPPCHECK
        // Otherwise cppcheck would protest that nRefCount isn't modified
        nRefCount = (nRefCount + 1) - 1;
#endif

        if( oSource.poRoot != nullptr )
            poRoot = oSource.poRoot->Clone();
    }

    return *this;
}

/************************************************************************/
/*                             Reference()                              */
/************************************************************************/

/**
 * \brief Increments the reference count by one.
 *
 * The reference count is used keep track of the number of OGRGeometry objects
 * referencing this SRS.
 *
 * The method does the same thing as the C function OSRReference().
 *
 * @return the updated reference count.
 */

int OGRSpatialReference::Reference()

{
    return CPLAtomicInc(&nRefCount);
}

/************************************************************************/
/*                            OSRReference()                            */
/************************************************************************/

/**
 * \brief Increments the reference count by one.
 *
 * This function is the same as OGRSpatialReference::Reference()
 */
int OSRReference( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRReference", 0 );

    return ToPointer(hSRS)->Reference();
}

/************************************************************************/
/*                            Dereference()                             */
/************************************************************************/

/**
 * \brief Decrements the reference count by one.
 *
 * The method does the same thing as the C function OSRDereference().
 *
 * @return the updated reference count.
 */

int OGRSpatialReference::Dereference()

{
    if( nRefCount <= 0 )
        CPLDebug( "OSR",
                  "Dereference() called on an object with refcount %d,"
                  "likely already destroyed!",
                  nRefCount );
    return CPLAtomicDec(&nRefCount);
}

/************************************************************************/
/*                           OSRDereference()                           */
/************************************************************************/

/**
 * \brief Decrements the reference count by one.
 *
 * This function is the same as OGRSpatialReference::Dereference()
 */
int OSRDereference( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRDereference", 0 );

    return ToPointer(hSRS)->Dereference();
}

/************************************************************************/
/*                         GetReferenceCount()                          */
/************************************************************************/

/**
 * \fn int OGRSpatialReference::GetReferenceCount() const;
 *
 * \brief Fetch current reference count.
 *
 * @return the current reference count.
 */

/************************************************************************/
/*                              Release()                               */
/************************************************************************/

/**
 * \brief Decrements the reference count by one, and destroy if zero.
 *
 * The method does the same thing as the C function OSRRelease().
 */

void OGRSpatialReference::Release()

{
    if( Dereference() <= 0 )
        delete this;
}

/************************************************************************/
/*                             OSRRelease()                             */
/************************************************************************/

/**
 * \brief Decrements the reference count by one, and destroy if zero.
 *
 * This function is the same as OGRSpatialReference::Release()
 */
void OSRRelease( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER0( hSRS, "OSRRelease" );

    ToPointer(hSRS)->Release();
}

/************************************************************************/
/*                              SetRoot()                               */
/************************************************************************/

/**
 * \brief Set the root SRS node.
 *
 * If the object has an existing tree of OGR_SRSNodes, they are destroyed
 * as part of assigning the new root.  Ownership of the passed OGR_SRSNode is
 * is assumed by the OGRSpatialReference.
 *
 * @param poNewRoot object to assign as root.
 */

void OGRSpatialReference::SetRoot( OGR_SRSNode * poNewRoot )

{
    delete poRoot;
    poRoot = poNewRoot;
}

/************************************************************************/
/*                            GetAttrNode()                             */
/************************************************************************/

/**
 * \brief Find named node in tree.
 *
 * This method does a pre-order traversal of the node tree searching for
 * a node with this exact value (case insensitive), and returns it.  Leaf
 * nodes are not considered, under the assumption that they are just
 * attribute value nodes.
 *
 * If a node appears more than once in the tree (such as UNIT for instance),
 * the first encountered will be returned.  Use GetNode() on a subtree to be
 * more specific.
 *
 * @param pszNodePath the name of the node to search for.  May contain multiple
 * components such as "GEOGCS|UNIT".
 *
 * @return a pointer to the node found, or NULL if none.
 */

OGR_SRSNode *OGRSpatialReference::GetAttrNode( const char * pszNodePath )

{
    if( strchr(pszNodePath, '|') == nullptr )
    {
        // Fast path
        OGR_SRSNode *poNode = GetRoot();
        if( poNode )
            poNode = poNode->GetNode( pszNodePath );
        return poNode;
    }

    char **papszPathTokens =
        CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);

    if( CSLCount( papszPathTokens ) < 1 )
    {
        CSLDestroy(papszPathTokens);
        return nullptr;
    }

    OGR_SRSNode *poNode = GetRoot();
    for( int i = 0; poNode != nullptr && papszPathTokens[i] != nullptr; i++ )
    {
        poNode = poNode->GetNode( papszPathTokens[i] );
    }

    CSLDestroy( papszPathTokens );

    return poNode;
}

/**
 * \brief Find named node in tree.
 *
 * This method does a pre-order traversal of the node tree searching for
 * a node with this exact value (case insensitive), and returns it.  Leaf
 * nodes are not considered, under the assumption that they are just
 * attribute value nodes.
 *
 * If a node appears more than once in the tree (such as UNIT for instance),
 * the first encountered will be returned.  Use GetNode() on a subtree to be
 * more specific.
 *
 * @param pszNodePath the name of the node to search for.  May contain multiple
 * components such as "GEOGCS|UNIT".
 *
 * @return a pointer to the node found, or NULL if none.
 */

const OGR_SRSNode *
OGRSpatialReference::GetAttrNode( const char * pszNodePath ) const

{
    OGR_SRSNode *poNode =
        const_cast<OGRSpatialReference *>(this)->GetAttrNode(pszNodePath);

    return poNode;
}

/************************************************************************/
/*                            GetAttrValue()                            */
/************************************************************************/

/**
 * \brief Fetch indicated attribute of named node.
 *
 * This method uses GetAttrNode() to find the named node, and then extracts
 * the value of the indicated child.  Thus a call to GetAttrValue("UNIT",1)
 * would return the second child of the UNIT node, which is normally the
 * length of the linear unit in meters.
 *
 * This method does the same thing as the C function OSRGetAttrValue().
 *
 * @param pszNodeName the tree node to look for (case insensitive).
 * @param iAttr the child of the node to fetch (zero based).
 *
 * @return the requested value, or NULL if it fails for any reason.
 */

const char *OGRSpatialReference::GetAttrValue( const char * pszNodeName,
                                               int iAttr ) const

{
    const OGR_SRSNode *poNode = GetAttrNode( pszNodeName );
    if( poNode == nullptr )
        return nullptr;

    if( iAttr < 0 || iAttr >= poNode->GetChildCount() )
        return nullptr;

    return poNode->GetChild(iAttr)->GetValue();
}

/************************************************************************/
/*                          OSRGetAttrValue()                           */
/************************************************************************/

/**
 * \brief Fetch indicated attribute of named node.
 *
 * This function is the same as OGRSpatialReference::GetAttrValue()
 */
const char * CPL_STDCALL OSRGetAttrValue( OGRSpatialReferenceH hSRS,
                             const char * pszKey, int iChild )

{
    VALIDATE_POINTER1( hSRS, "OSRGetAttrValue", nullptr );

    return ToPointer(hSRS)->
        GetAttrValue( pszKey, iChild );
}

/************************************************************************/
/*                               Clone()                                */
/************************************************************************/

/**
 * \brief Make a duplicate of this OGRSpatialReference.
 *
 * This method is the same as the C function OSRClone().
 *
 * @return a new SRS, which becomes the responsibility of the caller.
 */

OGRSpatialReference *OGRSpatialReference::Clone() const

{
    OGRSpatialReference *poNewRef = new OGRSpatialReference();

    if( poRoot != nullptr )
        poNewRef->poRoot = poRoot->Clone();

    return poNewRef;
}

/************************************************************************/
/*                              OSRClone()                              */
/************************************************************************/

/**
 * \brief Make a duplicate of this OGRSpatialReference.
 *
 * This function is the same as OGRSpatialReference::Clone()
 */
OGRSpatialReferenceH CPL_STDCALL OSRClone( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRClone", nullptr );

    return ToHandle(
        ToPointer(hSRS)->Clone() );
}

/************************************************************************/
/*                            dumpReadable()                            */
/************************************************************************/

/** Dump pretty wkt to stdout, mostly for debugging.
 */
void OGRSpatialReference::dumpReadable()

{
    char *pszPrettyWkt = nullptr;

    exportToPrettyWkt( &pszPrettyWkt, FALSE );
    printf( "%s\n", pszPrettyWkt );/*ok*/
    CPLFree( pszPrettyWkt );
}

/************************************************************************/
/*                         exportToPrettyWkt()                          */
/************************************************************************/

/**
 * Convert this SRS into a nicely formatted WKT string for display to a person.
 * 
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System Issues</a> page
 * for implementation details of WKT in OGR.
 *
 * Note that the returned WKT string should be freed with
 * CPLFree() when no longer needed.  It is the responsibility of the caller.
 *
 * This method is the same as the C function OSRExportToPrettyWkt().
 *
 * @param ppszResult the resulting string is returned in this pointer.
 * @param bSimplify TRUE if the AXIS, AUTHORITY and EXTENSION nodes should be
 *   stripped off.
 *
 * @return currently OGRERR_NONE is always returned, but the future it
 * is possible error conditions will develop.
 */

OGRErr OGRSpatialReference::exportToPrettyWkt( char ** ppszResult,
                                               int bSimplify ) const

{
    if( poRoot == nullptr )
    {
        *ppszResult = CPLStrdup("");
        return OGRERR_NONE;
    }

    if( bSimplify )
    {
        OGRSpatialReference *poSimpleClone = Clone();

        poSimpleClone->GetRoot()->StripNodes( "AXIS" );
        poSimpleClone->GetRoot()->StripNodes( "AUTHORITY" );
        poSimpleClone->GetRoot()->StripNodes( "EXTENSION" );
        const OGRErr eErr =
            poSimpleClone->GetRoot()->exportToPrettyWkt( ppszResult, 1 );
        delete poSimpleClone;
        return eErr;
    }

    return poRoot->exportToPrettyWkt( ppszResult, 1 );
}

/************************************************************************/
/*                        OSRExportToPrettyWkt()                        */
/************************************************************************/

/**
 * \brief Convert this SRS into a nicely formatted WKT string for display to a
 * person.
 *
 * This function is the same as OGRSpatialReference::exportToPrettyWkt().
 */

OGRErr CPL_STDCALL OSRExportToPrettyWkt( OGRSpatialReferenceH hSRS, char ** ppszReturn,
                             int bSimplify)

{
    VALIDATE_POINTER1( hSRS, "OSRExportToPrettyWkt", OGRERR_FAILURE );

    *ppszReturn = nullptr;

    return ToPointer(hSRS)->
        exportToPrettyWkt( ppszReturn, bSimplify );
}

/************************************************************************/
/*                            exportToWkt()                             */
/************************************************************************/

/**
 * \brief Convert this SRS into WKT format.
 * 
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System Issues</a> page
 * for implementation details of WKT in OGR.
 *
 * Note that the returned WKT string should be freed with
 * CPLFree() when no longer needed.  It is the responsibility of the caller.
 *
 * This method is the same as the C function OSRExportToWkt().
 *
 * @param ppszResult the resulting string is returned in this pointer.
 *
 * @return currently OGRERR_NONE is always returned, but the future it
 * is possible error conditions will develop.
 */

OGRErr OGRSpatialReference::exportToWkt( char ** ppszResult ) const

{
    if( poRoot == nullptr )
    {
        *ppszResult = CPLStrdup("");
        return OGRERR_NONE;
    }

    return poRoot->exportToWkt(ppszResult);
}

/************************************************************************/
/*                           OSRExportToWkt()                           */
/************************************************************************/

/**
 * \brief Convert this SRS into WKT format.
 * 
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System Issues</a> page
 * for implementation details of WKT in OGR.
 *
 * This function is the same as OGRSpatialReference::exportToWkt().
 */

OGRErr CPL_STDCALL OSRExportToWkt( OGRSpatialReferenceH hSRS,
                                   char ** ppszReturn )

{
    VALIDATE_POINTER1( hSRS, "OSRExportToWkt", OGRERR_FAILURE );

    *ppszReturn = nullptr;

    return ToPointer(hSRS)->exportToWkt( ppszReturn );
}

/************************************************************************/
/*                           importFromWkt()                            */
/************************************************************************/

/**
 * \brief Import from WKT string.
 *
 * This method will wipe the existing SRS definition, and
 * reassign it based on the contents of the passed WKT string.  Only as
 * much of the input string as needed to construct this SRS is consumed from
 * the input string, and the input string pointer
 * is then updated to point to the remaining (unused) input.
 * 
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System Issues</a> page
 * for implementation details of WKT in OGR.
 *
 * This method is the same as the C function OSRImportFromWkt().
 *
 * @param ppszInput Pointer to pointer to input.  The pointer is updated to
 * point to remaining unused input text.
 *
 * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
 * fails for any reason.
 * @since GDAL 2.3
 */

OGRErr OGRSpatialReference::importFromWkt( const char ** ppszInput )

{
    if( !ppszInput || !*ppszInput )
        return OGRERR_FAILURE;

    Clear();

    poRoot = new OGR_SRSNode();

    const OGRErr eErr = poRoot->importFromWkt( ppszInput );
    if( eErr != OGRERR_NONE )
        return eErr;

/* -------------------------------------------------------------------- */
/*      The following seems to try and detect and unconsumed            */
/*      VERTCS[] coordinate system definition (ESRI style) and to       */
/*      import and attach it to the existing root.  Likely we will      */
/*      need to extend this somewhat to bring it into an acceptable     */
/*      OGRSpatialReference organization at some point.                 */
/* -------------------------------------------------------------------- */
    if( strlen(*ppszInput) > 0 && strstr(*ppszInput, "VERTCS") )
    {
        if( ((*ppszInput)[0]) == ',' )
            (*ppszInput)++;
        OGR_SRSNode *poNewChild = new OGR_SRSNode();
        poRoot->AddChild( poNewChild );
        return poNewChild->importFromWkt( ppszInput );
    }

    return OGRERR_NONE;
}

/**
 * \brief Import from WKT string.
 *
 * This method will wipe the existing SRS definition, and
 * reassign it based on the contents of the passed WKT string.  Only as
 * much of the input string as needed to construct this SRS is consumed from
 * the input string, and the input string pointer
 * is then updated to point to the remaining (unused) input.
 * 
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System Issues</a> page
 * for implementation details of WKT in OGR.
 *
 * This method is the same as the C function OSRImportFromWkt().
 *
 * @param ppszInput Pointer to pointer to input.  The pointer is updated to
 * point to remaining unused input text.
 *
 * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
 * fails for any reason.
 * @deprecated GDAL 2.3. Use importFromWkt(const char**) or importFromWkt(const char*)
 */

OGRErr OGRSpatialReference::importFromWkt( char ** ppszInput )

{
    return importFromWkt( const_cast<const char**>(ppszInput) );
}

/**
 * \brief Import from WKT string.
 *
 * This method will wipe the existing SRS definition, and
 * reassign it based on the contents of the passed WKT string.  Only as
 * much of the input string as needed to construct this SRS is consumed from
 * the input string, and the input string pointer
 * is then updated to point to the remaining (unused) input.
 *
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System Issues</a> page
 * for implementation details of WKT in OGR.
 *
 * @param pszInput Input WKT
 *
 * @return OGRERR_NONE if import succeeds, or OGRERR_CORRUPT_DATA if it
 * fails for any reason.
 * @since GDAL 2.3
 */

OGRErr OGRSpatialReference::importFromWkt( const char* pszInput )
{
    return importFromWkt(&pszInput);
}

/************************************************************************/
/*                          OSRImportFromWkt()                          */
/************************************************************************/

/**
 * \brief Import from WKT string.
 * 
 * Consult also the <a href="wktproblems.html">OGC WKT Coordinate System Issues</a> page
 * for implementation details of WKT in OGR.
 *
 * This function is the same as OGRSpatialReference::importFromWkt().
 */

OGRErr OSRImportFromWkt( OGRSpatialReferenceH hSRS, char **ppszInput )

{
    VALIDATE_POINTER1( hSRS, "OSRImportFromWkt", OGRERR_FAILURE );

    return ToPointer(hSRS)->importFromWkt(
                const_cast<const char**>(ppszInput) );
}

/************************************************************************/
/*                              SetNode()                               */
/************************************************************************/

/**
 * \brief Set attribute value in spatial reference.
 *
 * Missing intermediate nodes in the path will be created if not already
 * in existence.  If the attribute has no children one will be created and
 * assigned the value otherwise the zeroth child will be assigned the value.
 *
 * This method does the same as the C function OSRSetAttrValue().
 *
 * @param pszNodePath full path to attribute to be set.  For instance
 * "PROJCS|GEOGCS|UNIT".
 *
 * @param pszNewNodeValue value to be assigned to node, such as "meter".
 * This may be NULL if you just want to force creation of the intermediate
 * path.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetNode( const char * pszNodePath,
                                     const char * pszNewNodeValue )

{
    char **papszPathTokens =
        CSLTokenizeStringComplex(pszNodePath, "|", TRUE, FALSE);

    if( CSLCount( papszPathTokens ) < 1 )
    {
        CSLDestroy(papszPathTokens);
        return OGRERR_FAILURE;
    }

    if( GetRoot() == nullptr || !EQUAL(papszPathTokens[0], GetRoot()->GetValue()) )
    {
        SetRoot( new OGR_SRSNode( papszPathTokens[0] ) );
    }

    OGR_SRSNode *poNode = GetRoot();
    for( int i = 1; papszPathTokens[i] != nullptr; i++ )
    {
        int j = 0;  // Used after for.

        for( ; j < poNode->GetChildCount(); j++ )
        {
            if( EQUAL(poNode->GetChild( j )->GetValue(), papszPathTokens[i]) )
            {
                poNode = poNode->GetChild(j);
                j = -1;
                break;
            }
        }

        if( j != -1 )
        {
            OGR_SRSNode *poNewNode = new OGR_SRSNode( papszPathTokens[i] );
            poNode->AddChild( poNewNode );
            poNode = poNewNode;
        }
    }

    CSLDestroy( papszPathTokens );

    if( pszNewNodeValue != nullptr )
    {
        if( poNode->GetChildCount() > 0 )
            poNode->GetChild(0)->SetValue( pszNewNodeValue );
        else
            poNode->AddChild( new OGR_SRSNode( pszNewNodeValue ) );
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                          OSRSetAttrValue()                           */
/************************************************************************/

/**
 * \brief Set attribute value in spatial reference.
 *
 * This function is the same as OGRSpatialReference::SetNode()
 */
OGRErr CPL_STDCALL OSRSetAttrValue( OGRSpatialReferenceH hSRS,
                        const char * pszPath, const char * pszValue )

{
    VALIDATE_POINTER1( hSRS, "OSRSetAttrValue", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetNode( pszPath, pszValue );
}

/************************************************************************/
/*                              SetNode()                               */
/************************************************************************/

/**
 * \brief Set attribute value in spatial reference.
 *
 * Missing intermediate nodes in the path will be created if not already
 * in existence.  If the attribute has no children one will be created and
 * assigned the value otherwise the zeroth child will be assigned the value.
 *
 * This method does the same as the C function OSRSetAttrValue().
 *
 * @param pszNodePath full path to attribute to be set.  For instance
 * "PROJCS|GEOGCS|UNIT".
 *
 * @param dfValue value to be assigned to node.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetNode( const char *pszNodePath,
                                     double dfValue )

{
    char szValue[64] = { '\0' };

    if( std::abs(dfValue - static_cast<int>(dfValue)) == 0.0 )
        snprintf( szValue, sizeof(szValue), "%d", static_cast<int>(dfValue) );
    else
        OGRsnPrintDouble( szValue, sizeof(szValue), dfValue );

    return SetNode( pszNodePath, szValue );
}

/************************************************************************/
/*                          SetAngularUnits()                           */
/************************************************************************/

/**
 * \brief Set the angular units for the geographic coordinate system.
 *
 * This method creates a UNIT subnode with the specified values as a
 * child of the GEOGCS node.
 *
 * This method does the same as the C function OSRSetAngularUnits().
 *
 * @param pszUnitsName the units name to be used.  Some preferred units
 * names can be found in ogr_srs_api.h such as SRS_UA_DEGREE.
 *
 * @param dfInRadians the value to multiple by an angle in the indicated
 * units to transform to radians.  Some standard conversion factors can
 * be found in ogr_srs_api.h.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetAngularUnits( const char * pszUnitsName,
                                             double dfInRadians )

{
    bNormInfoSet = FALSE;

    OGR_SRSNode *poCS = GetAttrNode( "GEOGCS" );

    if( poCS == nullptr )
        return OGRERR_FAILURE;

    char szValue[128] = { '\0' };
    OGRsnPrintDouble( szValue, sizeof(szValue), dfInRadians );

    OGR_SRSNode *poUnits = nullptr;
    if( poCS->FindChild( "UNIT" ) >= 0 )
    {
        poUnits = poCS->GetChild( poCS->FindChild( "UNIT" ) );
        if( poUnits->GetChildCount() < 2 )
            return OGRERR_FAILURE;
        poUnits->GetChild(0)->SetValue( pszUnitsName );
        poUnits->GetChild(1)->SetValue( szValue );
    }
    else
    {
        poUnits = new OGR_SRSNode( "UNIT" );
        poUnits->AddChild( new OGR_SRSNode( pszUnitsName ) );
        poUnits->AddChild( new OGR_SRSNode( szValue ) );

        poCS->AddChild( poUnits );
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                         OSRSetAngularUnits()                         */
/************************************************************************/

/**
 * \brief Set the angular units for the geographic coordinate system.
 *
 * This function is the same as OGRSpatialReference::SetAngularUnits()
 */
OGRErr OSRSetAngularUnits( OGRSpatialReferenceH hSRS,
                           const char * pszUnits, double dfInRadians )

{
    VALIDATE_POINTER1( hSRS, "OSRSetAngularUnits", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetAngularUnits( pszUnits, dfInRadians );
}

/************************************************************************/
/*                          GetAngularUnits()                           */
/************************************************************************/

/**
 * \brief Fetch angular geographic coordinate system units.
 *
 * If no units are available, a value of "degree" and SRS_UA_DEGREE_CONV
 * will be assumed.  This method only checks directly under the GEOGCS node
 * for units.
 *
 * This method does the same thing as the C function OSRGetAngularUnits().
 *
 * @param ppszName a pointer to be updated with the pointer to the units name.
 * The returned value remains internal to the OGRSpatialReference and should
 * not be freed, or modified.  It may be invalidated on the next
 * OGRSpatialReference call.
 *
 * @return the value to multiply by angular distances to transform them to
 * radians.
 * @deprecated GDAL 2.3.0. Use GetAngularUnits(const char**) const.
 */

double OGRSpatialReference::GetAngularUnits( const char ** ppszName ) const

{
    const OGR_SRSNode *poCS = GetAttrNode( "GEOGCS" );

    if( ppszName != nullptr )
        *ppszName = "degree";

    if( poCS == nullptr )
        return CPLAtof(SRS_UA_DEGREE_CONV);

    for( int iChild = 0; iChild < poCS->GetChildCount(); iChild++ )
    {
        const OGR_SRSNode *poChild = poCS->GetChild(iChild);

        if( EQUAL(poChild->GetValue(), "UNIT")
            && poChild->GetChildCount() >= 2 )
        {
            if( ppszName != nullptr )
              *ppszName = poChild->GetChild(0)->GetValue();

            return CPLAtof( poChild->GetChild(1)->GetValue() );
        }
    }

    return 1.0;
}

/**
 * \brief Fetch angular geographic coordinate system units.
 *
 * If no units are available, a value of "degree" and SRS_UA_DEGREE_CONV
 * will be assumed.  This method only checks directly under the GEOGCS node
 * for units.
 *
 * This method does the same thing as the C function OSRGetAngularUnits().
 *
 * @param ppszName a pointer to be updated with the pointer to the units name.
 * The returned value remains internal to the OGRSpatialReference and should
 * not be freed, or modified.  It may be invalidated on the next
 * OGRSpatialReference call.
 *
 * @return the value to multiply by angular distances to transform them to
 * radians.
 * @since GDAL 2.3.0
 */

double OGRSpatialReference::GetAngularUnits( char ** ppszName ) const

{
    return GetAngularUnits( const_cast<const char**>(ppszName) );
}

/************************************************************************/
/*                         OSRGetAngularUnits()                         */
/************************************************************************/

/**
 * \brief Fetch angular geographic coordinate system units.
 *
 * This function is the same as OGRSpatialReference::GetAngularUnits()
 */
double OSRGetAngularUnits( OGRSpatialReferenceH hSRS, char ** ppszName )

{
    VALIDATE_POINTER1( hSRS, "OSRGetAngularUnits", 0 );

    return ToPointer(hSRS)->
        GetAngularUnits( const_cast<const char**>(ppszName) );
}

/************************************************************************/
/*                 SetLinearUnitsAndUpdateParameters()                  */
/************************************************************************/

/**
 * \brief Set the linear units for the projection.
 *
 * This method creates a UNIT subnode with the specified values as a
 * child of the PROJCS or LOCAL_CS node.   It works the same as the
 * SetLinearUnits() method, but it also updates all existing linear
 * projection parameter values from the old units to the new units.
 *
 * @param pszName the units name to be used.  Some preferred units
 * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
 * and SRS_UL_US_FOOT.
 *
 * @param dfInMeters the value to multiple by a length in the indicated
 * units to transform to meters.  Some standard conversion factors can
 * be found in ogr_srs_api.h.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetLinearUnitsAndUpdateParameters(
    const char *pszName, double dfInMeters )

{
    if( dfInMeters == 0.0 )
        return OGRERR_FAILURE;

    const double dfOldInMeters = GetLinearUnits();
    OGR_SRSNode *poPROJCS = GetAttrNode( "PROJCS" );

    if( dfInMeters == dfOldInMeters || poPROJCS == nullptr )
        return SetLinearUnits( pszName, dfInMeters );

    for( int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++ )
    {
        const OGR_SRSNode *poChild = poPROJCS->GetChild(iChild);

        if( EQUAL(poChild->GetValue(), "PARAMETER")
            && poChild->GetChildCount() > 1 )
        {
            char *const pszParmName =
                CPLStrdup(poChild->GetChild(0)->GetValue());

            if( IsLinearParameter( pszParmName ) )
            {
                const double dfOldValue = GetProjParm( pszParmName );

                SetProjParm( pszParmName,
                             dfOldValue * dfOldInMeters / dfInMeters );
            }

            CPLFree( pszParmName );
        }
    }

    return SetLinearUnits( pszName, dfInMeters );
}

/************************************************************************/
/*                OSRSetLinearUnitsAndUpdateParameters()                */
/************************************************************************/

/**
 * \brief Set the linear units for the projection.
 *
 * This function is the same as
 *   OGRSpatialReference::SetLinearUnitsAndUpdateParameters()
 */
OGRErr OSRSetLinearUnitsAndUpdateParameters( OGRSpatialReferenceH hSRS,
                                             const char * pszUnits,
                                             double dfInMeters )

{
    VALIDATE_POINTER1( hSRS, "OSRSetLinearUnitsAndUpdateParameters",
                       OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetLinearUnitsAndUpdateParameters( pszUnits, dfInMeters );
}

/************************************************************************/
/*                           SetLinearUnits()                           */
/************************************************************************/

/**
 * \brief Set the linear units for the projection.
 *
 * This method creates a UNIT subnode with the specified values as a
 * child of the PROJCS, GEOCCS or LOCAL_CS node.
 *
 * This method does the same as the C function OSRSetLinearUnits().
 *
 * @param pszUnitsName the units name to be used.  Some preferred units
 * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
 * and SRS_UL_US_FOOT.
 *
 * @param dfInMeters the value to multiple by a length in the indicated
 * units to transform to meters.  Some standard conversion factors can
 * be found in ogr_srs_api.h.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetLinearUnits( const char * pszUnitsName,
                                            double dfInMeters )

{
    return SetTargetLinearUnits( nullptr, pszUnitsName, dfInMeters );
}

/************************************************************************/
/*                         OSRSetLinearUnits()                          */
/************************************************************************/

/**
 * \brief Set the linear units for the projection.
 *
 * This function is the same as OGRSpatialReference::SetLinearUnits()
 */
OGRErr OSRSetLinearUnits( OGRSpatialReferenceH hSRS,
                          const char * pszUnits, double dfInMeters )

{
    VALIDATE_POINTER1( hSRS, "OSRSetLinearUnits", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetLinearUnits( pszUnits, dfInMeters );
}

/************************************************************************/
/*                        SetTargetLinearUnits()                        */
/************************************************************************/

/**
 * \brief Set the linear units for the projection.
 *
 * This method creates a UNIT subnode with the specified values as a
 * child of the target node.
 *
 * This method does the same as the C function OSRSetTargetLinearUnits().
 *
 * @param pszTargetKey the keyword to set the linear units for.
 * i.e. "PROJCS" or "VERT_CS"
 *
 * @param pszUnitsName the units name to be used.  Some preferred units
 * names can be found in ogr_srs_api.h such as SRS_UL_METER, SRS_UL_FOOT
 * and SRS_UL_US_FOOT.
 *
 * @param dfInMeters the value to multiple by a length in the indicated
 * units to transform to meters.  Some standard conversion factors can
 * be found in ogr_srs_api.h.
 *
 * @return OGRERR_NONE on success.
 *
 * @since OGR 1.9.0
 */

OGRErr OGRSpatialReference::SetTargetLinearUnits( const char *pszTargetKey,
                                                  const char * pszUnitsName,
                                                  double dfInMeters )

{
    if( dfInMeters <= 0.0 )
        return OGRERR_FAILURE;

    bNormInfoSet = FALSE;

    OGR_SRSNode *poCS = nullptr;
    if( pszTargetKey == nullptr )
    {
        poCS = GetAttrNode( "PROJCS" );

        if( poCS == nullptr )
            poCS = GetAttrNode( "LOCAL_CS" );
        if( poCS == nullptr )
            poCS = GetAttrNode( "GEOCCS" );
        if( poCS == nullptr && IsVertical() )
            poCS = GetAttrNode( "VERT_CS" );
    }
    else
        poCS = GetAttrNode( pszTargetKey );

    if( poCS == nullptr )
        return OGRERR_FAILURE;

    char szValue[128] = { '\0' };
    if( dfInMeters < std::numeric_limits<int>::max() &&
        dfInMeters > std::numeric_limits<int>::min() &&
        dfInMeters == static_cast<int>(dfInMeters) )
        snprintf( szValue, sizeof(szValue),
                  "%d", static_cast<int>(dfInMeters) );
    else
        OGRsnPrintDouble( szValue, sizeof(szValue), dfInMeters );

    OGR_SRSNode *poUnits = nullptr;
    if( poCS->FindChild( "UNIT" ) >= 0 )
    {
        poUnits = poCS->GetChild( poCS->FindChild( "UNIT" ) );
        if( poUnits->GetChildCount() < 2 )
            return OGRERR_FAILURE;
        poUnits->GetChild(0)->SetValue( pszUnitsName );
        poUnits->GetChild(1)->SetValue( szValue );
        if( poUnits->FindChild( "AUTHORITY" ) != -1 )
            poUnits->DestroyChild( poUnits->FindChild( "AUTHORITY" ) );
    }
    else
    {
        poUnits = new OGR_SRSNode( "UNIT" );
        poUnits->AddChild( new OGR_SRSNode( pszUnitsName ) );
        poUnits->AddChild( new OGR_SRSNode( szValue ) );

        poCS->AddChild( poUnits );
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                         OSRSetLinearUnits()                          */
/************************************************************************/

/**
 * \brief Set the linear units for the target node.
 *
 * This function is the same as OGRSpatialReference::SetTargetLinearUnits()
 *
 * @since OGR 1.9.0
 */
OGRErr OSRSetTargetLinearUnits( OGRSpatialReferenceH hSRS,
                                const char *pszTargetKey,
                                const char * pszUnits, double dfInMeters )

{
    VALIDATE_POINTER1( hSRS, "OSRSetTargetLinearUnits", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetTargetLinearUnits( pszTargetKey, pszUnits, dfInMeters );
}

/************************************************************************/
/*                           GetLinearUnits()                           */
/************************************************************************/

/**
 * \brief Fetch linear projection units.
 *
 * If no units are available, a value of "Meters" and 1.0 will be assumed.
 * This method only checks directly under the PROJCS, GEOCCS or LOCAL_CS node
 * for units.
 *
 * This method does the same thing as the C function OSRGetLinearUnits()
 *
 * @param ppszName a pointer to be updated with the pointer to the units name.
 * The returned value remains internal to the OGRSpatialReference and should
 * not be freed, or modified.  It may be invalidated on the next
 * OGRSpatialReference call.
 *
 * @return the value to multiply by linear distances to transform them to
 * meters.
 * @deprecated GDAL 2.3.0. Use GetLinearUnits(const char**) const.
 */

double OGRSpatialReference::GetLinearUnits( char ** ppszName ) const

{
    return GetTargetLinearUnits( nullptr, const_cast<const char**>(ppszName) );
}

/**
 * \brief Fetch linear projection units.
 *
 * If no units are available, a value of "Meters" and 1.0 will be assumed.
 * This method only checks directly under the PROJCS, GEOCCS or LOCAL_CS node
 * for units.
 *
 * This method does the same thing as the C function OSRGetLinearUnits()
 *
 * @param ppszName a pointer to be updated with the pointer to the units name.
 * The returned value remains internal to the OGRSpatialReference and should
 * not be freed, or modified.  It may be invalidated on the next
 * OGRSpatialReference call.
 *
 * @return the value to multiply by linear distances to transform them to
 * meters.
 * @since GDAL 2.3.0
 */

double OGRSpatialReference::GetLinearUnits( const char ** ppszName ) const

{
    return GetTargetLinearUnits( nullptr, ppszName );
}

/************************************************************************/
/*                         OSRGetLinearUnits()                          */
/************************************************************************/

/**
 * \brief Fetch linear projection units.
 *
 * This function is the same as OGRSpatialReference::GetLinearUnits()
 */
double OSRGetLinearUnits( OGRSpatialReferenceH hSRS, char ** ppszName )

{
    VALIDATE_POINTER1( hSRS, "OSRGetLinearUnits", 0 );

    return ToPointer(hSRS)->GetLinearUnits( const_cast<const char**>(ppszName) );
}

/************************************************************************/
/*                        GetTargetLinearUnits()                        */
/************************************************************************/

/**
 * \brief Fetch linear units for target.
 *
 * If no units are available, a value of "Meters" and 1.0 will be assumed.
 *
 * This method does the same thing as the C function OSRGetTargetLinearUnits()
 *
 * @param pszTargetKey the key to look on. i.e. "PROJCS" or "VERT_CS". Might be
 * NULL, in which case PROJCS will be implied (and if not found, LOCAL_CS,
 * GEOCCS and VERT_CS are looked up)
 * @param ppszName a pointer to be updated with the pointer to the units name.
 * The returned value remains internal to the OGRSpatialReference and should not
 * be freed, or modified.  It may be invalidated on the next
 * OGRSpatialReference call. ppszName can be set to NULL.
 *
 * @return the value to multiply by linear distances to transform them to
 * meters.
 *
 * @since OGR 1.9.0
 * @deprecated GDAL 2.3.0. Use GetTargetLinearUnits(const char*, const char**) const.
 */

double OGRSpatialReference::GetTargetLinearUnits( const char *pszTargetKey,
                                                  const char ** ppszName ) const

{
    const OGR_SRSNode *poCS = nullptr;

    if( pszTargetKey == nullptr )
    {
        poCS = GetAttrNode( "PROJCS" );

        if( poCS == nullptr )
            poCS = GetAttrNode( "LOCAL_CS" );
        if( poCS == nullptr )
            poCS = GetAttrNode( "GEOCCS" );
        if( poCS == nullptr && IsVertical() )
            poCS = GetAttrNode( "VERT_CS" );
    }
    else
        poCS = GetAttrNode( pszTargetKey );

    if( ppszName != nullptr )
        *ppszName = "unknown";

    if( poCS == nullptr )
        return 1.0;

    for( int iChild = 0; iChild < poCS->GetChildCount(); iChild++ )
    {
        const OGR_SRSNode *poChild = poCS->GetChild(iChild);

        if( EQUAL(poChild->GetValue(), "UNIT")
            && poChild->GetChildCount() >= 2 )
        {
            if( ppszName != nullptr )
              *ppszName = poChild->GetChild(0)->GetValue();

            return CPLAtof( poChild->GetChild(1)->GetValue() );
        }
    }

    return 1.0;
}

/**
 * \brief Fetch linear units for target.
 *
 * If no units are available, a value of "Meters" and 1.0 will be assumed.
 *
 * This method does the same thing as the C function OSRGetTargetLinearUnits()
 *
 * @param pszTargetKey the key to look on. i.e. "PROJCS" or "VERT_CS". Might be
 * NULL, in which case PROJCS will be implied (and if not found, LOCAL_CS,
 * GEOCCS and VERT_CS are looked up)
 * @param ppszName a pointer to be updated with the pointer to the units name.
 * The returned value remains internal to the OGRSpatialReference and should not
 * be freed, or modified.  It may be invalidated on the next
 * OGRSpatialReference call. ppszName can be set to NULL.
 *
 * @return the value to multiply by linear distances to transform them to
 * meters.
 *
 * @since GDAL 2.3.0
 */

double OGRSpatialReference::GetTargetLinearUnits( const char *pszTargetKey,
                                                  char ** ppszName ) const

{
    return GetTargetLinearUnits( pszTargetKey,
                                 const_cast<const char**>(ppszName) );
}

/************************************************************************/
/*                      OSRGetTargetLinearUnits()                       */
/************************************************************************/

/**
 * \brief Fetch linear projection units.
 *
 * This function is the same as OGRSpatialReference::GetTargetLinearUnits()
 *
 * @since OGR 1.9.0
 */
double OSRGetTargetLinearUnits( OGRSpatialReferenceH hSRS,
                                const char *pszTargetKey,
                                char ** ppszName )

{
    VALIDATE_POINTER1( hSRS, "OSRGetTargetLinearUnits", 0 );

    return ToPointer(hSRS)->
        GetTargetLinearUnits( pszTargetKey, const_cast<const char**>(ppszName) );
}

/************************************************************************/
/*                          GetPrimeMeridian()                          */
/************************************************************************/

/**
 * \brief Fetch prime meridian info.
 *
 * Returns the offset of the prime meridian from greenwich in degrees,
 * and the prime meridian name (if requested).   If no PRIMEM value exists
 * in the coordinate system definition a value of "Greenwich" and an
 * offset of 0.0 is assumed.
 *
 * If the prime meridian name is returned, the pointer is to an internal
 * copy of the name. It should not be freed, altered or depended on after
 * the next OGR call.
 *
 * This method is the same as the C function OSRGetPrimeMeridian().
 *
 * @param ppszName return location for prime meridian name.  If NULL, name
 * is not returned.
 *
 * @return the offset to the GEOGCS prime meridian from greenwich in decimal
 * degrees.
 * @deprecated GDAL 2.3.0. Use GetPrimeMeridian(const char**) const.
 */

double OGRSpatialReference::GetPrimeMeridian( const char **ppszName ) const

{
    const OGR_SRSNode *poPRIMEM = GetAttrNode( "PRIMEM" );

    if( poPRIMEM != nullptr && poPRIMEM->GetChildCount() >= 2
        && CPLAtof(poPRIMEM->GetChild(1)->GetValue()) != 0.0 )
    {
        if( ppszName != nullptr )
            *ppszName = poPRIMEM->GetChild(0)->GetValue();
        return CPLAtof(poPRIMEM->GetChild(1)->GetValue());
    }

    if( ppszName != nullptr )
        *ppszName = SRS_PM_GREENWICH;

    return 0.0;
}

/**
 * \brief Fetch prime meridian info.
 *
 * Returns the offset of the prime meridian from greenwich in degrees,
 * and the prime meridian name (if requested).   If no PRIMEM value exists
 * in the coordinate system definition a value of "Greenwich" and an
 * offset of 0.0 is assumed.
 *
 * If the prime meridian name is returned, the pointer is to an internal
 * copy of the name. It should not be freed, altered or depended on after
 * the next OGR call.
 *
 * This method is the same as the C function OSRGetPrimeMeridian().
 *
 * @param ppszName return location for prime meridian name.  If NULL, name
 * is not returned.
 *
 * @return the offset to the GEOGCS prime meridian from greenwich in decimal
 * degrees.
 * @since GDAL 2.3.0
 */

double OGRSpatialReference::GetPrimeMeridian( char **ppszName ) const

{
    return GetPrimeMeridian( const_cast<const char**>(ppszName) );
}

/************************************************************************/
/*                        OSRGetPrimeMeridian()                         */
/************************************************************************/

/**
 * \brief Fetch prime meridian info.
 *
 * This function is the same as OGRSpatialReference::GetPrimeMeridian()
 */
double OSRGetPrimeMeridian( OGRSpatialReferenceH hSRS, char **ppszName )

{
    VALIDATE_POINTER1( hSRS, "OSRGetPrimeMeridian", 0 );

    return ToPointer(hSRS)->
        GetPrimeMeridian( const_cast<const char**>(ppszName) );
}

/************************************************************************/
/*                             SetGeogCS()                              */
/************************************************************************/

/**
 * \brief Set geographic coordinate system.
 *
 * This method is used to set the datum, ellipsoid, prime meridian and
 * angular units for a geographic coordinate system.  It can be used on its
 * own to establish a geographic spatial reference, or applied to a
 * projected coordinate system to establish the underlying geographic
 * coordinate system.
 *
 * This method does the same as the C function OSRSetGeogCS().
 *
 * @param pszGeogName user visible name for the geographic coordinate system
 * (not to serve as a key).
 *
 * @param pszDatumName key name for this datum.  The OpenGIS specification
 * lists some known values, and otherwise EPSG datum names with a standard
 * transformation are considered legal keys.
 *
 * @param pszSpheroidName user visible spheroid name (not to serve as a key)
 *
 * @param dfSemiMajor the semi major axis of the spheroid.
 *
 * @param dfInvFlattening the inverse flattening for the spheroid.
 * This can be computed from the semi minor axis as
 * 1/f = 1.0 / (1.0 - semiminor/semimajor).
 *
 * @param pszPMName the name of the prime meridian (not to serve as a key)
 * If this is NULL a default value of "Greenwich" will be used.
 *
 * @param dfPMOffset the longitude of Greenwich relative to this prime
 * meridian.
 *
 * @param pszAngularUnits the angular units name (see ogr_srs_api.h for some
 * standard names).  If NULL a value of "degrees" will be assumed.
 *
 * @param dfConvertToRadians value to multiply angular units by to transform
 * them to radians.  A value of SRS_UA_DEGREE_CONV will be used if
 * pszAngularUnits is NULL.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr
OGRSpatialReference::SetGeogCS( const char * pszGeogName,
                                const char * pszDatumName,
                                const char * pszSpheroidName,
                                double dfSemiMajor, double dfInvFlattening,
                                const char * pszPMName, double dfPMOffset,
                                const char * pszAngularUnits,
                                double dfConvertToRadians )

{
    bNormInfoSet = FALSE;

/* -------------------------------------------------------------------- */
/*      For a geocentric coordinate system we want to set the datum     */
/*      and ellipsoid based on the GEOGCS.  Create the GEOGCS in a      */
/*      temporary srs and use the copy method which has special         */
/*      handling for GEOCCS.                                            */
/* -------------------------------------------------------------------- */
    if( IsGeocentric() )
    {
        OGRSpatialReference oGCS;

        oGCS.SetGeogCS( pszGeogName, pszDatumName, pszSpheroidName,
                        dfSemiMajor, dfInvFlattening,
                        pszPMName, dfPMOffset,
                        pszAngularUnits, dfConvertToRadians );
        return CopyGeogCSFrom( &oGCS );
    }

/* -------------------------------------------------------------------- */
/*      Do we already have a GEOGCS?  If so, blow it away so it can     */
/*      be properly replaced.                                           */
/* -------------------------------------------------------------------- */
    if( GetAttrNode( "GEOGCS" ) != nullptr )
    {
        OGR_SRSNode *poCS = nullptr;

        if( EQUAL(GetRoot()->GetValue(), "GEOGCS") )
            Clear();
        else if( (poCS = GetAttrNode( "PROJCS" )) != nullptr
                 && poCS->FindChild( "GEOGCS" ) != -1 )
            poCS->DestroyChild( poCS->FindChild( "GEOGCS" ) );
        else
            return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Set defaults for various parameters.                            */
/* -------------------------------------------------------------------- */
    if( pszGeogName == nullptr )
        pszGeogName = "unnamed";

    if( pszPMName == nullptr )
        pszPMName = SRS_PM_GREENWICH;

    if( pszDatumName == nullptr )
        pszDatumName = "unknown";

    if( pszSpheroidName == nullptr )
        pszSpheroidName = "unnamed";

    if( pszAngularUnits == nullptr )
    {
        pszAngularUnits = SRS_UA_DEGREE;
        dfConvertToRadians = CPLAtof(SRS_UA_DEGREE_CONV);
    }

/* -------------------------------------------------------------------- */
/*      Build the GEOGCS object.                                        */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poGeogCS = new OGR_SRSNode( "GEOGCS" );
    poGeogCS->AddChild( new OGR_SRSNode( pszGeogName ) );

/* -------------------------------------------------------------------- */
/*      Setup the spheroid.                                             */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poSpheroid = new OGR_SRSNode( "SPHEROID" );
    poSpheroid->AddChild( new OGR_SRSNode( pszSpheroidName ) );

    char szValue[128] = {};
    OGRsnPrintDouble( szValue, sizeof(szValue), dfSemiMajor );
    poSpheroid->AddChild( new OGR_SRSNode(szValue) );

    OGRsnPrintDouble( szValue, sizeof(szValue), dfInvFlattening );
    poSpheroid->AddChild( new OGR_SRSNode(szValue) );

/* -------------------------------------------------------------------- */
/*      Setup the Datum.                                                */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poDatum = new OGR_SRSNode( "DATUM" );
    poDatum->AddChild( new OGR_SRSNode(pszDatumName) );
    poDatum->AddChild( poSpheroid );

/* -------------------------------------------------------------------- */
/*      Setup the prime meridian.                                       */
/* -------------------------------------------------------------------- */
    if( dfPMOffset == 0.0 )
        strcpy( szValue, "0" );
    else
      OGRsnPrintDouble( szValue, sizeof(szValue), dfPMOffset );

    OGR_SRSNode *poPM = new OGR_SRSNode( "PRIMEM" );
    poPM->AddChild( new OGR_SRSNode( pszPMName ) );
    poPM->AddChild( new OGR_SRSNode( szValue ) );

/* -------------------------------------------------------------------- */
/*      Setup the rotational units.                                     */
/* -------------------------------------------------------------------- */
    OGRsnPrintDouble( szValue, sizeof(szValue), dfConvertToRadians );

    OGR_SRSNode *poUnits = new OGR_SRSNode( "UNIT" );
    poUnits->AddChild( new OGR_SRSNode(pszAngularUnits) );
    poUnits->AddChild( new OGR_SRSNode(szValue) );

/* -------------------------------------------------------------------- */
/*      Complete the GeogCS                                             */
/* -------------------------------------------------------------------- */
    poGeogCS->AddChild( poDatum );
    poGeogCS->AddChild( poPM );
    poGeogCS->AddChild( poUnits );

/* -------------------------------------------------------------------- */
/*      Attach below the PROJCS if there is one, or make this the root. */
/* -------------------------------------------------------------------- */
    if( GetRoot() != nullptr && EQUAL(GetRoot()->GetValue(), "PROJCS") )
        poRoot->InsertChild( poGeogCS, 1 );
    else
        SetRoot( poGeogCS );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetGeogCS()                            */
/************************************************************************/

/**
 * \brief Set geographic coordinate system.
 *
 * This function is the same as OGRSpatialReference::SetGeogCS()
 */
OGRErr OSRSetGeogCS( OGRSpatialReferenceH hSRS,
                     const char * pszGeogName,
                     const char * pszDatumName,
                     const char * pszSpheroidName,
                     double dfSemiMajor, double dfInvFlattening,
                     const char * pszPMName, double dfPMOffset,
                     const char * pszAngularUnits,
                     double dfConvertToRadians )

{
    VALIDATE_POINTER1( hSRS, "OSRSetGeogCS", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetGeogCS(
        pszGeogName, pszDatumName,
        pszSpheroidName, dfSemiMajor, dfInvFlattening,
        pszPMName, dfPMOffset, pszAngularUnits, dfConvertToRadians );
}

/************************************************************************/
/*                         SetWellKnownGeogCS()                         */
/************************************************************************/

/**
 * \brief Set a GeogCS based on well known name.
 *
 * This may be called on an empty OGRSpatialReference to make a geographic
 * coordinate system, or on something with an existing PROJCS node to
 * set the underlying geographic coordinate system of a projected coordinate
 * system.
 *
 * The following well known text values are currently supported:
 * <ul>
 * <li> "WGS84": same as "EPSG:4326" but has no dependence on EPSG data files.
 * <li> "WGS72": same as "EPSG:4322" but has no dependence on EPSG data files.
 * <li> "NAD27": same as "EPSG:4267" but has no dependence on EPSG data files.
 * <li> "NAD83": same as "EPSG:4269" but has no dependence on EPSG data files.
 * <li> "EPSG:n": same as doing an ImportFromEPSG(n).
 * </ul>
 *
 * @param pszName name of well known geographic coordinate system.
 * @return OGRERR_NONE on success, or OGRERR_FAILURE if the name isn't
 * recognised, the target object is already initialized, or an EPSG value
 * can't be successfully looked up.
 */

OGRErr OGRSpatialReference::SetWellKnownGeogCS( const char * pszName )

{
/* -------------------------------------------------------------------- */
/*      Check for EPSG authority numbers.                               */
/* -------------------------------------------------------------------- */
    if( STARTS_WITH_CI(pszName, "EPSG:") )
    {
        OGRSpatialReference oSRS2;
        const OGRErr eErr = oSRS2.importFromEPSG( atoi(pszName+5) );
        if( eErr != OGRERR_NONE )
            return eErr;

        if( !oSRS2.IsGeographic() )
            return OGRERR_FAILURE;

        return CopyGeogCSFrom( &oSRS2 );
    }

/* -------------------------------------------------------------------- */
/*      Check for EPSGA authority numbers.                               */
/* -------------------------------------------------------------------- */
    if( STARTS_WITH_CI(pszName, "EPSGA:") )
    {
        OGRSpatialReference oSRS2;
        const OGRErr eErr = oSRS2.importFromEPSGA( atoi(pszName+6) );
        if( eErr != OGRERR_NONE )
            return eErr;

        if( !oSRS2.IsGeographic() )
            return OGRERR_FAILURE;

        return CopyGeogCSFrom( &oSRS2 );
    }

/* -------------------------------------------------------------------- */
/*      Check for simple names.                                         */
/* -------------------------------------------------------------------- */
    const char *pszWKT = nullptr;

    if( EQUAL(pszName, "WGS84") || EQUAL(pszName, "CRS84") ||
        EQUAL(pszName, "CRS:84") )
        pszWKT = SRS_WKT_WGS84;

    else if( EQUAL(pszName, "WGS72") )
        pszWKT =
            "GEOGCS[\"WGS 72\",DATUM[\"WGS_1972\","
            "SPHEROID[\"WGS 72\",6378135,298.26,AUTHORITY[\"EPSG\",\"7043\"]],"
            "TOWGS84[0,0,4.5,0,0,0.554,0.2263],AUTHORITY[\"EPSG\",\"6322\"]],"
            "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
            "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
            "AUTHORITY[\"EPSG\",\"4322\"]]";

    else if( EQUAL(pszName, "NAD27") || EQUAL(pszName, "CRS27") ||
             EQUAL(pszName, "CRS:27") )
        pszWKT =
            "GEOGCS[\"NAD27\",DATUM[\"North_American_Datum_1927\","
            "SPHEROID[\"Clarke 1866\",6378206.4,294.9786982138982,"
            "AUTHORITY[\"EPSG\",\"7008\"]],AUTHORITY[\"EPSG\",\"6267\"]],"
            "PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
            "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
            "AUTHORITY[\"EPSG\",\"4267\"]]";

    else if( EQUAL(pszName, "NAD83") || EQUAL(pszName, "CRS83") ||
             EQUAL(pszName, "CRS:83") )
        pszWKT =
            "GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\","
            "SPHEROID[\"GRS 1980\",6378137,298.257222101,"
            "AUTHORITY[\"EPSG\",\"7019\"]],TOWGS84[0,0,0,0,0,0,0],"
            "AUTHORITY[\"EPSG\",\"6269\"]],PRIMEM[\"Greenwich\",0,"
            "AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,"
            "AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4269\"]]";

    else
        return OGRERR_FAILURE;

/* -------------------------------------------------------------------- */
/*      Import the WKT                                                  */
/* -------------------------------------------------------------------- */
    OGRSpatialReference oSRS2;
    const OGRErr eErr = oSRS2.importFromWkt( pszWKT );
    if( eErr != OGRERR_NONE )
        return eErr;

/* -------------------------------------------------------------------- */
/*      Copy over.                                                      */
/* -------------------------------------------------------------------- */
    return CopyGeogCSFrom( &oSRS2 );
}

/************************************************************************/
/*                       OSRSetWellKnownGeogCS()                        */
/************************************************************************/

/**
 * \brief Set a GeogCS based on well known name.
 *
 * This function is the same as OGRSpatialReference::SetWellKnownGeogCS()
 */
OGRErr OSRSetWellKnownGeogCS( OGRSpatialReferenceH hSRS, const char *pszName )

{
    VALIDATE_POINTER1( hSRS, "OSRSetWellKnownGeogCS", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetWellKnownGeogCS( pszName );
}

/************************************************************************/
/*                           CopyGeogCSFrom()                           */
/************************************************************************/

/**
 * \brief Copy GEOGCS from another OGRSpatialReference.
 *
 * The GEOGCS information is copied into this OGRSpatialReference from another.
 * If this object has a PROJCS root already, the GEOGCS is installed within
 * it, otherwise it is installed as the root.
 *
 * @param poSrcSRS the spatial reference to copy the GEOGCS information from.
 *
 * @return OGRERR_NONE on success or an error code.
 */

OGRErr OGRSpatialReference::CopyGeogCSFrom(
    const OGRSpatialReference * poSrcSRS )

{
    bNormInfoSet = FALSE;

/* -------------------------------------------------------------------- */
/*      Handle geocentric coordinate systems specially.  We just        */
/*      want to copy the DATUM and PRIMEM nodes.                        */
/* -------------------------------------------------------------------- */
    if( IsGeocentric() )
    {
        if( GetRoot()->FindChild( "DATUM" ) != -1 )
            GetRoot()->DestroyChild( GetRoot()->FindChild( "DATUM" ) );
        if( GetRoot()->FindChild( "PRIMEM" ) != -1 )
            GetRoot()->DestroyChild( GetRoot()->FindChild( "PRIMEM" ) );

        const OGR_SRSNode *poDatum = poSrcSRS->GetAttrNode( "DATUM" );
        const OGR_SRSNode *poPrimeM = poSrcSRS->GetAttrNode( "PRIMEM" );

        if( poDatum == nullptr || poPrimeM == nullptr )
            return OGRERR_FAILURE;

        poRoot->InsertChild( poDatum->Clone(), 1 );
        poRoot->InsertChild( poPrimeM->Clone(), 2 );

        return OGRERR_NONE;
    }

/* -------------------------------------------------------------------- */
/*      Do we already have a GEOGCS?  If so, blow it away so it can     */
/*      be properly replaced.                                           */
/* -------------------------------------------------------------------- */
    if( GetAttrNode( "GEOGCS" ) != nullptr )
    {
        OGR_SRSNode *poPROJCS = nullptr;

        if( EQUAL(GetRoot()->GetValue(), "GEOGCS") )
            Clear();
        else if( (poPROJCS = GetAttrNode( "PROJCS" )) != nullptr
                 && poPROJCS->FindChild( "GEOGCS" ) != -1 )
            poPROJCS->DestroyChild( poPROJCS->FindChild( "GEOGCS" ) );
        else
            return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Find the GEOGCS node on the source.                             */
/* -------------------------------------------------------------------- */
    const OGR_SRSNode *poGeogCS = poSrcSRS->GetAttrNode( "GEOGCS" );
    if( poGeogCS == nullptr )
        return OGRERR_FAILURE;

/* -------------------------------------------------------------------- */
/*      Attach below the PROJCS if there is one, or make this the root. */
/* -------------------------------------------------------------------- */
    if( GetRoot() != nullptr && EQUAL(GetRoot()->GetValue(), "PROJCS") )
        poRoot->InsertChild( poGeogCS->Clone(), 1 );
    else
        SetRoot( poGeogCS->Clone() );

    return OGRERR_NONE;
}

/************************************************************************/
/*                         OSRCopyGeogCSFrom()                          */
/************************************************************************/

/**
 * \brief Copy GEOGCS from another OGRSpatialReference.
 *
 * This function is the same as OGRSpatialReference::CopyGeogCSFrom()
 */
OGRErr OSRCopyGeogCSFrom( OGRSpatialReferenceH hSRS,
                          const OGRSpatialReferenceH hSrcSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE );
    VALIDATE_POINTER1( hSrcSRS, "OSRCopyGeogCSFrom", OGRERR_FAILURE );

    return ToPointer(hSRS)->CopyGeogCSFrom(ToPointer(hSrcSRS) );
}

/************************************************************************/
/*                          SetFromUserInput()                          */
/************************************************************************/

/**
 * \brief Set spatial reference from various text formats.
 *
 * This method will examine the provided input, and try to deduce the
 * format, and then use it to initialize the spatial reference system.  It
 * may take the following forms:
 *
 * <ol>
 * <li> Well Known Text definition - passed on to importFromWkt().
 * <li> "EPSG:n" - number passed on to importFromEPSG().
 * <li> "EPSGA:n" - number passed on to importFromEPSGA().
 * <li> "AUTO:proj_id,unit_id,lon0,lat0" - WMS auto projections.
 * <li> "urn:ogc:def:crs:EPSG::n" - ogc urns
 * <li> PROJ.4 definitions - passed on to importFromProj4().
 * <li> filename - file read for WKT, XML or PROJ.4 definition.
 * <li> well known name accepted by SetWellKnownGeogCS(), such as NAD27, NAD83,
 * WGS84 or WGS72.
 * <li> WKT (directly or in a file) in ESRI format should be prefixed with
 * ESRI:: to trigger an automatic morphFromESRI().
 * <li> "IGNF:xxx" - "+init=IGNF:xxx" passed on to importFromProj4().
 * </ol>
 *
 * It is expected that this method will be extended in the future to support
 * XML and perhaps a simplified "minilanguage" for indicating common UTM and
 * State Plane definitions.
 *
 * This method is intended to be flexible, but by its nature it is
 * imprecise as it must guess information about the format intended.  When
 * possible applications should call the specific method appropriate if the
 * input is known to be in a particular format.
 *
 * This method does the same thing as the OSRSetFromUserInput() function.
 *
 * @param pszDefinition text definition to try to deduce SRS from.
 *
 * @return OGRERR_NONE on success, or an error code if the name isn't
 * recognised, the definition is corrupt, or an EPSG value can't be
 * successfully looked up.
 */

OGRErr OGRSpatialReference::SetFromUserInput( const char * pszDefinition )

{
    bool bESRI = false;

    if( STARTS_WITH_CI(pszDefinition, "ESRI::") )
    {
        bESRI = true;
        pszDefinition += 6;
    }

/* -------------------------------------------------------------------- */
/*      Is it a recognised syntax?                                      */
/* -------------------------------------------------------------------- */
    if( STARTS_WITH_CI(pszDefinition, "PROJCS")
        || STARTS_WITH_CI(pszDefinition, "GEOGCS")
        || STARTS_WITH_CI(pszDefinition, "COMPD_CS")
        || STARTS_WITH_CI(pszDefinition, "GEOCCS")
        || STARTS_WITH_CI(pszDefinition, "VERT_CS")
        || STARTS_WITH_CI(pszDefinition, "LOCAL_CS") )
    {
        OGRErr err = importFromWkt( pszDefinition );
        if( err == OGRERR_NONE && bESRI )
            err = morphFromESRI();

        return err;
    }

    if( STARTS_WITH_CI(pszDefinition, "EPSG:")
        || STARTS_WITH_CI(pszDefinition, "EPSGA:") )
    {
        OGRErr eStatus = OGRERR_NONE;

        if( STARTS_WITH_CI(pszDefinition, "EPSG:") )
            eStatus = importFromEPSG( atoi(pszDefinition+5) );

        else // if( STARTS_WITH_CI(pszDefinition, "EPSGA:") )
            eStatus = importFromEPSGA( atoi(pszDefinition+6) );

        // Do we want to turn this into a compound definition
        // with a vertical datum?
        if( eStatus == OGRERR_NONE && strchr( pszDefinition, '+' ) != nullptr )
        {
            OGRSpatialReference oVertSRS;

            eStatus = oVertSRS.importFromEPSG(
                atoi(strchr(pszDefinition, '+') + 1) );
            if( eStatus == OGRERR_NONE )
            {
                OGR_SRSNode *poHorizSRS = GetRoot()->Clone();

                Clear();

                CPLString osName = poHorizSRS->GetChild(0)->GetValue();
                osName += " + ";
                osName += oVertSRS.GetRoot()->GetChild(0)->GetValue();

                SetNode( "COMPD_CS", osName );
                GetRoot()->AddChild( poHorizSRS );
                GetRoot()->AddChild( oVertSRS.GetRoot()->Clone() );
            }
        }

        return eStatus;
    }

    if( STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs:")
        || STARTS_WITH_CI(pszDefinition, "urn:ogc:def:crs,crs:")
        || STARTS_WITH_CI(pszDefinition, "urn:x-ogc:def:crs:")
        || STARTS_WITH_CI(pszDefinition, "urn:opengis:crs:")
        || STARTS_WITH_CI(pszDefinition, "urn:opengis:def:crs:"))
        return importFromURN( pszDefinition );

    if( STARTS_WITH_CI(pszDefinition, "http://opengis.net/def/crs")
        || STARTS_WITH_CI(pszDefinition, "http://www.opengis.net/def/crs")
        || STARTS_WITH_CI(pszDefinition, "www.opengis.net/def/crs"))
        return importFromCRSURL( pszDefinition );

    if( STARTS_WITH_CI(pszDefinition, "AUTO:") )
        return importFromWMSAUTO( pszDefinition );

    // WMS/WCS OGC codes like OGC:CRS84.
    if( STARTS_WITH_CI(pszDefinition, "OGC:") )
        return SetWellKnownGeogCS( pszDefinition+4 );

    if( STARTS_WITH_CI(pszDefinition, "CRS:") )
        return SetWellKnownGeogCS( pszDefinition );

    if( STARTS_WITH_CI(pszDefinition, "DICT:")
        && strstr(pszDefinition, ",") )
    {
        char *pszFile = CPLStrdup(pszDefinition+5);
        char *pszCode = strstr(pszFile, ",") + 1;

        pszCode[-1] = '\0';

        OGRErr err = importFromDict( pszFile, pszCode );
        CPLFree( pszFile );

        if( err == OGRERR_NONE && bESRI )
            err = morphFromESRI();

        return err;
    }

    if( EQUAL(pszDefinition, "NAD27")
        || EQUAL(pszDefinition,"NAD83")
        || EQUAL(pszDefinition,"WGS84")
        || EQUAL(pszDefinition,"WGS72") )
    {
        Clear();
        return SetWellKnownGeogCS( pszDefinition );
    }

    if( strstr(pszDefinition, "+proj") != nullptr
             || strstr(pszDefinition, "+init") != nullptr )
        return importFromProj4( pszDefinition );

    if( STARTS_WITH_CI(pszDefinition, "IGNF:") )
    {
        char* pszProj4Str =
            static_cast<char *>(CPLMalloc(6 + strlen(pszDefinition) + 1));
        strcpy(pszProj4Str, "+init=");
        strcat(pszProj4Str, pszDefinition);
        const OGRErr err = importFromProj4( pszProj4Str );
        CPLFree(pszProj4Str);

        return err;
    }

    if( STARTS_WITH_CI(pszDefinition, "http://") )
    {
        return importFromUrl (pszDefinition);
    }

    if( EQUAL(pszDefinition, "osgb:BNG") )
    {
        return importFromEPSG(27700);
    }

/* -------------------------------------------------------------------- */
/*      Try to open it as a file.                                       */
/* -------------------------------------------------------------------- */
    CPLConfigOptionSetter oSetter("CPL_ALLOW_VSISTDIN", "NO", true);
    VSILFILE * const fp = VSIFOpenL( pszDefinition, "rt" );
    if( fp == nullptr )
        return OGRERR_CORRUPT_DATA;

    const size_t nBufMax = 100000;
    char * const pszBuffer = static_cast<char *>( CPLMalloc(nBufMax) );
    const size_t nBytes = VSIFReadL( pszBuffer, 1, nBufMax-1, fp );
    VSIFCloseL( fp );

    if( nBytes == nBufMax-1 )
    {
        CPLDebug( "OGR",
                  "OGRSpatialReference::SetFromUserInput(%s), opened file "
                  "but it is to large for our generous buffer.  Is it really "
                  "just a WKT definition?", pszDefinition );
        CPLFree( pszBuffer );
        return OGRERR_FAILURE;
    }

    pszBuffer[nBytes] = '\0';

    char *pszBufPtr = pszBuffer;
    while( pszBufPtr[0] == ' ' || pszBufPtr[0] == '\n' )
        pszBufPtr++;

    OGRErr err = OGRERR_NONE;
    if( pszBufPtr[0] == '<' )
        err = importFromXML( pszBufPtr );
    else if( (strstr(pszBuffer, "+proj") != nullptr
              || strstr(pszBuffer, "+init") != nullptr)
             && (strstr(pszBuffer, "EXTENSION") == nullptr
                 && strstr(pszBuffer, "extension") == nullptr) )
        err = importFromProj4( pszBufPtr );
    else
    {
        if( STARTS_WITH_CI(pszBufPtr, "ESRI::") )
        {
            bESRI = true;
            pszBufPtr += 6;
        }

        // coverity[tainted_data]
        err = importFromWkt( pszBufPtr );
        if( err == OGRERR_NONE && bESRI )
            err = morphFromESRI();
    }

    CPLFree( pszBuffer );

    return err;
}

/************************************************************************/
/*                        OSRSetFromUserInput()                         */
/************************************************************************/

/**
 * \brief Set spatial reference from various text formats.
 *
 * This function is the same as OGRSpatialReference::SetFromUserInput()
 */
OGRErr CPL_STDCALL OSRSetFromUserInput( OGRSpatialReferenceH hSRS,
                                        const char *pszDef )

{
    VALIDATE_POINTER1( hSRS, "OSRSetFromUserInput", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetFromUserInput( pszDef );
}

/************************************************************************/
/*                          ImportFromUrl()                             */
/************************************************************************/

/**
 * \brief Set spatial reference from a URL.
 *
 * This method will download the spatial reference at a given URL and
 * feed it into SetFromUserInput for you.
 *
 * This method does the same thing as the OSRImportFromUrl() function.
 *
 * @param pszUrl text definition to try to deduce SRS from.
 *
 * @return OGRERR_NONE on success, or an error code with the curl
 * error message if it is unable to dowload data.
 */

OGRErr OGRSpatialReference::importFromUrl( const char * pszUrl )

{
    if( !STARTS_WITH_CI(pszUrl, "http://") )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "The given string is not recognized as a URL"
                  "starting with 'http://' -- %s", pszUrl );
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Fetch the result.                                               */
/* -------------------------------------------------------------------- */
    CPLErrorReset();

    const char* pszHeaders = "HEADERS=Accept: application/x-ogcwkt";
    const char* pszTimeout = "TIMEOUT=10";
    char *apszOptions[] = {
        const_cast<char *>(pszHeaders),
        const_cast<char *>(pszTimeout),
        nullptr
    };

    CPLHTTPResult *psResult = CPLHTTPFetch( pszUrl, apszOptions );

/* -------------------------------------------------------------------- */
/*      Try to handle errors.                                           */
/* -------------------------------------------------------------------- */

    if( psResult == nullptr )
        return OGRERR_FAILURE;
    if( psResult->nDataLen == 0
        || CPLGetLastErrorNo() != 0 || psResult->pabyData == nullptr )
    {
        if( CPLGetLastErrorNo() == 0 )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                    "No data was returned from the given URL" );
        }
        CPLHTTPDestroyResult( psResult );
        return OGRERR_FAILURE;
    }

    if( psResult->nStatus != 0 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Curl reports error: %d: %s",
                  psResult->nStatus, psResult->pszErrBuf );
        CPLHTTPDestroyResult( psResult );
        return OGRERR_FAILURE;
    }

    const char* pszData = reinterpret_cast<const char*>(psResult->pabyData);
    if( STARTS_WITH_CI(pszData, "http://") )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "The data that was downloaded also starts with 'http://' "
                  "and cannot be passed into SetFromUserInput.  Is this "
                  "really a spatial reference definition? ");
        CPLHTTPDestroyResult( psResult );
        return OGRERR_FAILURE;
    }
    if( OGRERR_NONE != SetFromUserInput(pszData)) {
        CPLHTTPDestroyResult( psResult );
        return OGRERR_FAILURE;
    }

    CPLHTTPDestroyResult( psResult );
    return OGRERR_NONE;
}

/************************************************************************/
/*                        OSRimportFromUrl()                            */
/************************************************************************/

/**
 * \brief Set spatial reference from a URL.
 *
 * This function is the same as OGRSpatialReference::importFromUrl()
 */
OGRErr OSRImportFromUrl( OGRSpatialReferenceH hSRS, const char *pszUrl )

{
    VALIDATE_POINTER1( hSRS, "OSRImportFromUrl", OGRERR_FAILURE );

    return
        ToPointer(hSRS)->importFromUrl( pszUrl );
}

/************************************************************************/
/*                         importFromURNPart()                          */
/************************************************************************/
OGRErr OGRSpatialReference::importFromURNPart(const char* pszAuthority,
                                              const char* pszCode,
                                              const char* pszURN)
{
/* -------------------------------------------------------------------- */
/*      Is this an EPSG code? Note that we import it with EPSG          */
/*      preferred axis ordering for geographic coordinate systems.      */
/* -------------------------------------------------------------------- */
    if( STARTS_WITH_CI(pszAuthority, "EPSG") )
        return importFromEPSGA( atoi(pszCode) );

/* -------------------------------------------------------------------- */
/*      Is this an IAU code?  Lets try for the IAU2000 dictionary.      */
/* -------------------------------------------------------------------- */
    if( STARTS_WITH_CI(pszAuthority, "IAU") )
        return importFromDict( "IAU2000.wkt", pszCode );

/* -------------------------------------------------------------------- */
/*      Is this an OGC code?                                            */
/* -------------------------------------------------------------------- */
    if( !STARTS_WITH_CI(pszAuthority, "OGC") )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "URN %s has unrecognized authority.",
                  pszURN );
        return OGRERR_FAILURE;
    }

    if( STARTS_WITH_CI(pszCode, "CRS84") )
        return SetWellKnownGeogCS( pszCode );
    else if( STARTS_WITH_CI(pszCode, "CRS83") )
        return SetWellKnownGeogCS( pszCode );
    else if( STARTS_WITH_CI(pszCode, "CRS27") )
        return SetWellKnownGeogCS( pszCode );
    else if( STARTS_WITH_CI(pszCode, "84") )  // urn:ogc:def:crs:OGC:2:84
        return SetWellKnownGeogCS( "CRS84" );

/* -------------------------------------------------------------------- */
/*      Handle auto codes.  We need to convert from format              */
/*      AUTO42001:99:8888 to format AUTO:42001,99,8888.                 */
/* -------------------------------------------------------------------- */
    else if( STARTS_WITH_CI(pszCode, "AUTO") )
    {
        char szWMSAuto[100] = { '\0' };

        if( strlen(pszCode) > sizeof(szWMSAuto)-2 )
            return OGRERR_FAILURE;

        snprintf( szWMSAuto, sizeof(szWMSAuto), "AUTO:%s", pszCode + 4 );
        for( int i = 5; szWMSAuto[i] != '\0'; i++ )
        {
            if( szWMSAuto[i] == ':' )
                szWMSAuto[i] = ',';
        }

        return importFromWMSAUTO( szWMSAuto );
    }

/* -------------------------------------------------------------------- */
/*      Not a recognise OGC item.                                       */
/* -------------------------------------------------------------------- */
    CPLError( CE_Failure, CPLE_AppDefined,
              "URN %s value not supported.",
              pszURN );

    return OGRERR_FAILURE;
}

/************************************************************************/
/*                           importFromURN()                            */
/*                                                                      */
/*      See OGC recommendation paper 06-023r1 or later for details.     */
/************************************************************************/

/**
 * \brief Initialize from OGC URN.
 *
 * Initializes this spatial reference from a coordinate system defined
 * by an OGC URN prefixed with "urn:ogc:def:crs:" per recommendation
 * paper 06-023r1.  Currently EPSG and OGC authority values are supported,
 * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
 *
 * This method is also support through SetFromUserInput() which can
 * normally be used for URNs.
 *
 * @param pszURN the urn string.
 *
 * @return OGRERR_NONE on success or an error code.
 */

OGRErr OGRSpatialReference::importFromURN( const char *pszURN )

{
    const char *pszCur = nullptr;

    if( STARTS_WITH_CI(pszURN, "urn:ogc:def:crs:") )
        pszCur = pszURN + 16;
    else if( STARTS_WITH_CI(pszURN, "urn:ogc:def:crs,crs:") )
        pszCur = pszURN + 20;
    else if( STARTS_WITH_CI(pszURN, "urn:x-ogc:def:crs:") )
        pszCur = pszURN + 18;
    else if( STARTS_WITH_CI(pszURN, "urn:opengis:crs:") )
        pszCur = pszURN + 16;
    else if( STARTS_WITH_CI(pszURN, "urn:opengis:def:crs:") )
        pszCur = pszURN + 20;
    else
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "URN %s not a supported format.", pszURN );
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Clear any existing definition.                                  */
/* -------------------------------------------------------------------- */
    Clear();

/* -------------------------------------------------------------------- */
/*      Find code (ignoring version) out of string like:                */
/*                                                                      */
/*      authority:[version]:code                                        */
/* -------------------------------------------------------------------- */
    const char *pszAuthority = pszCur;

    // skip authority
    while( *pszCur != ':' && *pszCur )
        pszCur++;
    if( *pszCur == ':' )
        pszCur++;

    // skip version
    const char* pszBeforeVersion = pszCur;
    while( *pszCur != ':' && *pszCur )
        pszCur++;
    if( *pszCur == ':' )
        pszCur++;
    else
        // We come here in the case, the content to parse is authority:code
        // (instead of authority::code) which is probably illegal according to
        // http://www.opengeospatial.org/ogcUrnPolicy but such content is found
        // for example in what is returned by GeoServer.
        pszCur = pszBeforeVersion;

    const char *pszCode = pszCur;

    const char* pszComma = strchr(pszCur, ',');
    if( pszComma == nullptr )
        return importFromURNPart(pszAuthority, pszCode, pszURN);

    // There's a second part with the vertical SRS.
    pszCur = pszComma + 1;
    if( !STARTS_WITH(pszCur, "crs:") )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "URN %s not a supported format.", pszURN );
        return OGRERR_FAILURE;
    }

    pszCur += 4;

    char* pszFirstCode = CPLStrdup(pszCode);
    pszFirstCode[pszComma - pszCode] = '\0';
    OGRErr eStatus = importFromURNPart(pszAuthority, pszFirstCode, pszURN);
    CPLFree(pszFirstCode);

    // Do we want to turn this into a compound definition
    // with a vertical datum?
    if( eStatus != OGRERR_NONE )
        return eStatus;

    /* -------------------------------------------------------------------- */
    /*      Find code (ignoring version) out of string like:                */
    /*                                                                      */
    /*      authority:[version]:code                                        */
    /* -------------------------------------------------------------------- */
    pszAuthority = pszCur;

    // skip authority
    while( *pszCur != ':' && *pszCur )
        pszCur++;
    if( *pszCur == ':' )
        pszCur++;

    // skip version
    pszBeforeVersion = pszCur;
    while( *pszCur != ':' && *pszCur )
        pszCur++;
    if( *pszCur == ':' )
        pszCur++;
    else
        pszCur = pszBeforeVersion;

    pszCode = pszCur;

    OGRSpatialReference oVertSRS;
    eStatus = oVertSRS.importFromURNPart(pszAuthority, pszCode, pszURN);
    if( eStatus == OGRERR_NONE )
    {
        OGR_SRSNode *poHorizSRS = GetRoot()->Clone();

        Clear();

        CPLString osName = poHorizSRS->GetChild(0)->GetValue();
        osName += " + ";
        osName += oVertSRS.GetRoot()->GetChild(0)->GetValue();

        SetNode( "COMPD_CS", osName );
        GetRoot()->AddChild( poHorizSRS );
        GetRoot()->AddChild( oVertSRS.GetRoot()->Clone() );
    }

    return eStatus;
}

/************************************************************************/
/*                           importFromCRSURL()                         */
/*                                                                      */
/*      See OGC Best Practice document 11-135 for details.              */
/************************************************************************/

/**
 * \brief Initialize from OGC URL.
 *
 * Initializes this spatial reference from a coordinate system defined
 * by an OGC URL prefixed with "http://opengis.net/def/crs" per best practice
 * paper 11-135.  Currently EPSG and OGC authority values are supported,
 * including OGC auto codes, but not including CRS1 or CRS88 (NAVD88).
 *
 * This method is also supported through SetFromUserInput() which can
 * normally be used for URLs.
 *
 * @param pszURL the URL string.
 *
 * @return OGRERR_NONE on success or an error code.
 */

OGRErr OGRSpatialReference::importFromCRSURL( const char *pszURL )

{
    const char *pszCur = nullptr;

    if( STARTS_WITH_CI(pszURL, "http://opengis.net/def/crs") )
        pszCur = pszURL + 26;
    else if( STARTS_WITH_CI(pszURL, "http://www.opengis.net/def/crs") )
        pszCur = pszURL + 30;
    else if( STARTS_WITH_CI(pszURL, "www.opengis.net/def/crs") )
        pszCur = pszURL + 23;
    else
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "URL %s not a supported format.", pszURL );
        return OGRERR_FAILURE;
    }

    if( *pszCur == '\0' )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "URL %s malformed.", pszURL);
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Clear any existing definition.                                  */
/* -------------------------------------------------------------------- */
    Clear();

    if( STARTS_WITH_CI(pszCur, "-compound?1=") )
    {
/* -------------------------------------------------------------------- */
/*      It's a compound CRS, of the form:                               */
/*                                                                      */
/*      http://opengis.net/def/crs-compound?1=URL1&2=URL2&3=URL3&..     */
/* -------------------------------------------------------------------- */
        pszCur += 12;

        // Extract each component CRS URL.
        int iComponentUrl = 2;

        CPLString osName = "";
        Clear();

        while( iComponentUrl != -1 )
        {
            char searchStr[15] = {};
            snprintf(searchStr, sizeof(searchStr), "&%d=", iComponentUrl);

            const char* pszUrlEnd = strstr(pszCur, searchStr);

            // Figure out the next component URL.
            char* pszComponentUrl = nullptr;

            if( pszUrlEnd )
            {
                size_t nLen = pszUrlEnd - pszCur;
                pszComponentUrl = static_cast<char *>(CPLMalloc(nLen + 1));
                strncpy(pszComponentUrl, pszCur, nLen);
                pszComponentUrl[nLen] = '\0';

                ++iComponentUrl;
                pszCur += nLen + strlen(searchStr);
            }
            else
            {
                if( iComponentUrl == 2 )
                {
                    CPLError( CE_Failure, CPLE_AppDefined,
                              "Compound CRS URLs must have at least two component CRSs." );
                    return OGRERR_FAILURE;
                }
                else
                {
                    pszComponentUrl = CPLStrdup(pszCur);
                    // no more components
                    iComponentUrl = -1;
                }
            }

            OGRSpatialReference oComponentSRS;
            OGRErr eStatus = oComponentSRS.importFromCRSURL( pszComponentUrl );

            CPLFree(pszComponentUrl);
            pszComponentUrl = nullptr;

            if( eStatus == OGRERR_NONE )
            {
                if( osName.length() != 0 )
                {
                  osName += " + ";
                }
                osName += oComponentSRS.GetRoot()->GetValue();
                SetNode( "COMPD_CS", osName );
                GetRoot()->AddChild( oComponentSRS.GetRoot()->Clone() );
            }
            else
                return eStatus;
        }

        return OGRERR_NONE;
    }

/* -------------------------------------------------------------------- */
/*      It's a normal CRS URL, of the form:                             */
/*                                                                      */
/*      http://opengis.net/def/crs/AUTHORITY/VERSION/CODE               */
/* -------------------------------------------------------------------- */
    ++pszCur;
    const char *pszAuthority = pszCur;

    // skip authority
    while( *pszCur != '/' && *pszCur )
        pszCur++;
    if( *pszCur == '/' )
        pszCur++;

    // skip version
    while( *pszCur != '/' && *pszCur )
        pszCur++;
    if( *pszCur == '/' )
        pszCur++;

    const char *pszCode = pszCur;

    return importFromURNPart( pszAuthority, pszCode, pszURL );
}

/************************************************************************/
/*                         importFromWMSAUTO()                          */
/************************************************************************/

/**
 * \brief Initialize from WMSAUTO string.
 *
 * Note that the WMS 1.3 specification does not include the
 * units code, while apparently earlier specs do.  We try to
 * guess around this.
 *
 * @param pszDefinition the WMSAUTO string
 *
 * @return OGRERR_NONE on success or an error code.
 */
OGRErr OGRSpatialReference::importFromWMSAUTO( const char * pszDefinition )

{
    int nProjId, nUnitsId;
    double dfRefLong, dfRefLat = 0.0;

/* -------------------------------------------------------------------- */
/*      Tokenize                                                        */
/* -------------------------------------------------------------------- */
    if( STARTS_WITH_CI(pszDefinition, "AUTO:") )
        pszDefinition += 5;

    char **papszTokens =
        CSLTokenizeStringComplex( pszDefinition, ",", FALSE, TRUE );

    if( CSLCount(papszTokens) == 4 )
    {
        nProjId = atoi(papszTokens[0]);
        nUnitsId = atoi(papszTokens[1]);
        dfRefLong = CPLAtof(papszTokens[2]);
        dfRefLat = CPLAtof(papszTokens[3]);
    }
    else if( CSLCount(papszTokens) == 3 && atoi(papszTokens[0]) == 42005 )
    {
        nProjId = atoi(papszTokens[0]);
        nUnitsId = atoi(papszTokens[1]);
        dfRefLong = CPLAtof(papszTokens[2]);
        dfRefLat = 0.0;
    }
    else if( CSLCount(papszTokens) == 3 )
    {
        nProjId = atoi(papszTokens[0]);
        nUnitsId = 9001;
        dfRefLong = CPLAtof(papszTokens[1]);
        dfRefLat = CPLAtof(papszTokens[2]);
    }
    else if( CSLCount(papszTokens) == 2 && atoi(papszTokens[0]) == 42005 )
    {
        nProjId = atoi(papszTokens[0]);
        nUnitsId = 9001;
        dfRefLong = CPLAtof(papszTokens[1]);
    }
    else
    {
        CSLDestroy( papszTokens );
        CPLError( CE_Failure, CPLE_AppDefined,
                  "AUTO projection has wrong number of arguments, expected\n"
                  "AUTO:proj_id,units_id,ref_long,ref_lat or"
                  "AUTO:proj_id,ref_long,ref_lat" );
        return OGRERR_FAILURE;
    }

    CSLDestroy( papszTokens );
    papszTokens = nullptr;

/* -------------------------------------------------------------------- */
/*      Build coordsys.                                                 */
/* -------------------------------------------------------------------- */
    Clear();

    switch( nProjId )
    {
      case 42001: // Auto UTM
        SetUTM( static_cast<int>(floor( (dfRefLong + 180.0) / 6.0 )) + 1,
                dfRefLat >= 0.0 );
        break;

      case 42002: // Auto TM (strangely very UTM-like).
        SetTM( 0, dfRefLong, 0.9996,
               500000.0, (dfRefLat >= 0.0) ? 0.0 : 10000000.0 );
        break;

      case 42003: // Auto Orthographic.
        SetOrthographic( dfRefLat, dfRefLong, 0.0, 0.0 );
        break;

      case 42004: // Auto Equirectangular
        SetEquirectangular( dfRefLat, dfRefLong, 0.0, 0.0 );
        break;

      case 42005:
        SetMollweide( dfRefLong, 0.0, 0.0 );
        break;

      default:
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Unsupported projection id in importFromWMSAUTO(): %d",
                  nProjId );
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Set units.                                                      */
/* -------------------------------------------------------------------- */

    switch( nUnitsId )
    {
      case 9001:
        SetLinearUnits( SRS_UL_METER, 1.0 );
        break;

      case 9002:
        SetLinearUnits( "Foot", 0.3048 );
        break;

      case 9003:
        SetLinearUnits( "US survey foot", CPLAtof(SRS_UL_US_FOOT_CONV) );
        break;

      default:
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Unsupported units code (%d).",
                  nUnitsId );
        return OGRERR_FAILURE;
        break;
    }

    SetAuthority( "PROJCS|UNIT", "EPSG", nUnitsId );

/* -------------------------------------------------------------------- */
/*      Set WGS84.                                                      */
/* -------------------------------------------------------------------- */
    SetWellKnownGeogCS( "WGS84" );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            GetSemiMajor()                            */
/************************************************************************/

/**
 * \brief Get spheroid semi major axis.
 *
 * This method does the same thing as the C function OSRGetSemiMajor().
 *
 * @param pnErr if non-NULL set to OGRERR_FAILURE if semi major axis
 * can be found.
 *
 * @return semi-major axis, or SRS_WGS84_SEMIMAJOR if it can't be found.
 */

double OGRSpatialReference::GetSemiMajor( OGRErr * pnErr ) const

{
    const OGR_SRSNode *poSpheroid = GetAttrNode( "SPHEROID" );

    if( pnErr != nullptr )
        *pnErr = OGRERR_NONE;

    if( poSpheroid != nullptr && poSpheroid->GetChildCount() >= 3 )
    {
        return CPLAtof( poSpheroid->GetChild(1)->GetValue() );
    }

    if( pnErr != nullptr )
        *pnErr = OGRERR_FAILURE;

    return SRS_WGS84_SEMIMAJOR;
}

/************************************************************************/
/*                          OSRGetSemiMajor()                           */
/************************************************************************/

/**
 * \brief Get spheroid semi major axis.
 *
 * This function is the same as OGRSpatialReference::GetSemiMajor()
 */
double OSRGetSemiMajor( OGRSpatialReferenceH hSRS, OGRErr *pnErr )

{
    VALIDATE_POINTER1( hSRS, "OSRGetSemiMajor", 0 );

    return ToPointer(hSRS)->GetSemiMajor( pnErr );
}

/************************************************************************/
/*                          GetInvFlattening()                          */
/************************************************************************/

/**
 * \brief Get spheroid inverse flattening.
 *
 * This method does the same thing as the C function OSRGetInvFlattening().
 *
 * @param pnErr if non-NULL set to OGRERR_FAILURE if no inverse flattening
 * can be found.
 *
 * @return inverse flattening, or SRS_WGS84_INVFLATTENING if it can't be found.
 */

double OGRSpatialReference::GetInvFlattening( OGRErr * pnErr ) const

{
    const OGR_SRSNode *poSpheroid = GetAttrNode( "SPHEROID" );

    if( pnErr != nullptr )
        *pnErr = OGRERR_NONE;

    if( poSpheroid != nullptr && poSpheroid->GetChildCount() >= 3 )
    {
        return CPLAtof( poSpheroid->GetChild(2)->GetValue() );
    }

    if( pnErr != nullptr )
        *pnErr = OGRERR_FAILURE;

    return SRS_WGS84_INVFLATTENING;
}

/************************************************************************/
/*                        OSRGetInvFlattening()                         */
/************************************************************************/

/**
 * \brief Get spheroid inverse flattening.
 *
 * This function is the same as OGRSpatialReference::GetInvFlattening()
 */
double OSRGetInvFlattening( OGRSpatialReferenceH hSRS, OGRErr *pnErr )

{
    VALIDATE_POINTER1( hSRS, "OSRGetInvFlattening", 0 );

    return ToPointer(hSRS)->GetInvFlattening( pnErr );
}

/************************************************************************/
/*                           GetEccentricity()                          */
/************************************************************************/

/**
 * \brief Get spheroid eccentricity
 *
 * @return eccentricity (or -1 in case of error)
 * @since GDAL 2.3
 */

double OGRSpatialReference::GetEccentricity() const

{
    OGRErr eErr = OGRERR_NONE;
    const double dfInvFlattening = GetInvFlattening(&eErr);
    if( eErr != OGRERR_NONE )
    {
        return -1.0;
    }
    if( dfInvFlattening == 0.0 )
        return 0.0;
    if( dfInvFlattening < 0.5 )
        return -1.0;
    return sqrt(2.0 / dfInvFlattening -
                    1.0 / (dfInvFlattening * dfInvFlattening));
}

/************************************************************************/
/*                      GetSquaredEccentricity()                        */
/************************************************************************/

/**
 * \brief Get spheroid squared eccentricity
 *
 * @return squared eccentricity (or -1 in case of error)
 * @since GDAL 2.3
 */

double OGRSpatialReference::GetSquaredEccentricity() const

{
    OGRErr eErr = OGRERR_NONE;
    const double dfInvFlattening = GetInvFlattening(&eErr);
    if( eErr != OGRERR_NONE )
    {
        return -1.0;
    }
    if( dfInvFlattening == 0.0 )
        return 0.0;
    if( dfInvFlattening < 0.5 )
        return -1.0;
    return 2.0 / dfInvFlattening -
                    1.0 / (dfInvFlattening * dfInvFlattening);
}

/************************************************************************/
/*                            GetSemiMinor()                            */
/************************************************************************/

/**
 * \brief Get spheroid semi minor axis.
 *
 * This method does the same thing as the C function OSRGetSemiMinor().
 *
 * @param pnErr if non-NULL set to OGRERR_FAILURE if semi minor axis
 * can be found.
 *
 * @return semi-minor axis, or WGS84 semi minor if it can't be found.
 */

double OGRSpatialReference::GetSemiMinor( OGRErr * pnErr ) const

{
    const double dfSemiMajor = GetSemiMajor( pnErr );
    const double dfInvFlattening = GetInvFlattening( pnErr );

    return OSRCalcSemiMinorFromInvFlattening(dfSemiMajor, dfInvFlattening);
}

/************************************************************************/
/*                          OSRGetSemiMinor()                           */
/************************************************************************/

/**
 * \brief Get spheroid semi minor axis.
 *
 * This function is the same as OGRSpatialReference::GetSemiMinor()
 */
double OSRGetSemiMinor( OGRSpatialReferenceH hSRS, OGRErr *pnErr )

{
    VALIDATE_POINTER1( hSRS, "OSRGetSemiMinor", 0 );

    return ToPointer(hSRS)->GetSemiMinor( pnErr );
}

/************************************************************************/
/*                             SetLocalCS()                             */
/************************************************************************/

/**
 * \brief Set the user visible LOCAL_CS name.
 *
 * This method is the same as the C function OSRSetLocalCS().
 *
 * This method will ensure a LOCAL_CS node is created as the root,
 * and set the provided name on it.  It must be used before SetLinearUnits().
 *
 * @param pszName the user visible name to assign.  Not used as a key.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetLocalCS( const char * pszName )

{
    const OGR_SRSNode * const poCS = GetAttrNode( "LOCAL_CS" );

    if( poCS == nullptr && GetRoot() != nullptr )
    {
        CPLDebug( "OGR",
                  "OGRSpatialReference::SetLocalCS(%s) failed.  "
                  "It appears an incompatible root node (%s) already exists.",
                  pszName, GetRoot()->GetValue() );
        return OGRERR_FAILURE;
    }

    SetNode( "LOCAL_CS", pszName );
    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetLocalCS()                            */
/************************************************************************/

/**
 * \brief Set the user visible LOCAL_CS name.
 *
 * This function is the same as OGRSpatialReference::SetLocalCS()
 */
OGRErr OSRSetLocalCS( OGRSpatialReferenceH hSRS, const char * pszName )

{
    VALIDATE_POINTER1( hSRS, "OSRSetLocalCS", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetLocalCS( pszName );
}

/************************************************************************/
/*                             SetGeocCS()                              */
/************************************************************************/

/**
 * \brief Set the user visible GEOCCS name.
 *
 * This method is the same as the C function OSRSetGeocCS().

 * This method will ensure a GEOCCS node is created as the root,
 * and set the provided name on it.  If used on a GEOGCS coordinate system,
 * the DATUM and PRIMEM nodes from the GEOGCS will be transferred over to
 * the GEOGCS.
 *
 * @param pszName the user visible name to assign.  Not used as a key.
 *
 * @return OGRERR_NONE on success.
 *
 * @since OGR 1.9.0
 */

OGRErr OGRSpatialReference::SetGeocCS( const char * pszName )

{
    OGR_SRSNode *poGeogCS = nullptr;
    OGR_SRSNode *poGeocCS = GetAttrNode( "GEOCCS" );

    if( poRoot != nullptr && EQUAL(poRoot->GetValue(),"GEOGCS") )
    {
        poGeogCS = poRoot;
        poRoot = nullptr;
    }

    if( poGeocCS == nullptr && GetRoot() != nullptr )
    {
        CPLDebug( "OGR",
                  "OGRSpatialReference::SetGeocCS(%s) failed.  "
                  "It appears an incompatible root node (%s) already exists.",
                  pszName, GetRoot()->GetValue() );
        return OGRERR_FAILURE;
    }

    SetNode( "GEOCCS", pszName );

    if( poGeogCS != nullptr )
    {
        OGR_SRSNode *poDatum = poGeogCS->GetNode( "DATUM" );
        OGR_SRSNode *poPRIMEM = poGeogCS->GetNode( "PRIMEM" );
        if( poRoot != nullptr && poDatum != nullptr && poPRIMEM != nullptr )
        {
            poRoot->InsertChild( poDatum->Clone(), 1 );
            poRoot->InsertChild( poPRIMEM->Clone(), 2 );
        }
        delete poGeogCS;
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetGeocCS()                            */
/************************************************************************/

/**
 * \brief Set the user visible PROJCS name.
 *
 * This function is the same as OGRSpatialReference::SetGeocCS()
 *
 * @since OGR 1.9.0
 */
OGRErr OSRSetGeocCS( OGRSpatialReferenceH hSRS, const char * pszName )

{
    VALIDATE_POINTER1( hSRS, "OSRSetGeocCS", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetGeocCS( pszName );
}

/************************************************************************/
/*                             SetVertCS()                              */
/************************************************************************/

/**
 * \brief Set the user visible VERT_CS name.
 *
 * This method is the same as the C function OSRSetVertCS().

 * This method will ensure a VERT_CS node is created if needed.  If the
 * existing coordinate system is GEOGCS or PROJCS rooted, then it will be
 * turned into a COMPD_CS.
 *
 * @param pszVertCSName the user visible name of the vertical coordinate
 * system. Not used as a key.
 *
 * @param pszVertDatumName the user visible name of the vertical datum.  It
 * is helpful if this matches the EPSG name.
 *
 * @param nVertDatumType the OGC vertical datum type, usually 2005.
 *
 * @return OGRERR_NONE on success.
 *
 * @since OGR 1.9.0
 */

OGRErr OGRSpatialReference::SetVertCS( const char * pszVertCSName,
                                       const char * pszVertDatumName,
                                       int nVertDatumType )

{
/* -------------------------------------------------------------------- */
/*      Handle the case where we want to make a compound coordinate     */
/*      system.                                                         */
/* -------------------------------------------------------------------- */
    if( IsProjected() || IsGeographic() )
    {
        OGR_SRSNode *poNewRoot = new OGR_SRSNode( "COMPD_CS" );
        poNewRoot->AddChild( poRoot );
        poRoot = poNewRoot;
    }

    else if( GetAttrNode( "VERT_CS" ) == nullptr )
        Clear();

/* -------------------------------------------------------------------- */
/*      If we already have a VERT_CS, wipe and recreate the root        */
/*      otherwise create the VERT_CS now.                               */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poVertCS = GetAttrNode( "VERT_CS" );

    if( poVertCS != nullptr )
    {
        poVertCS->ClearChildren();
    }
    else
    {
        poVertCS = new OGR_SRSNode( "VERT_CS" );
        if( poRoot != nullptr && EQUAL(poRoot->GetValue(), "COMPD_CS") )
        {
            poRoot->AddChild( poVertCS );
        }
        else
            SetRoot( poVertCS );
    }

/* -------------------------------------------------------------------- */
/*      Set the name, datumname, and type.                              */
/* -------------------------------------------------------------------- */

    poVertCS->AddChild( new OGR_SRSNode( pszVertCSName ) );

    OGR_SRSNode *poVertDatum = new OGR_SRSNode( "VERT_DATUM" );
    poVertCS->AddChild( poVertDatum );

    poVertDatum->AddChild( new OGR_SRSNode( pszVertDatumName ) );

    CPLString osVertDatumType;
    osVertDatumType.Printf( "%d", nVertDatumType );
    poVertDatum->AddChild( new OGR_SRSNode( osVertDatumType ) );

    // Add default axis node.
    OGR_SRSNode *poAxis = new OGR_SRSNode( "AXIS" );

    poAxis->AddChild( new OGR_SRSNode( "Up" ) );
    poAxis->AddChild( new OGR_SRSNode( "UP" ) );

    poVertCS->AddChild( poAxis );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetVertCS()                            */
/************************************************************************/

/**
 * \brief Setup the vertical coordinate system.
 *
 * This function is the same as OGRSpatialReference::SetVertCS()
 *
 * @since OGR 1.9.0
 */
OGRErr OSRSetVertCS( OGRSpatialReferenceH hSRS,
                     const char * pszVertCSName,
                     const char * pszVertDatumName,
                     int nVertDatumType )

{
    VALIDATE_POINTER1( hSRS, "OSRSetVertCS", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetVertCS( pszVertCSName, pszVertDatumName, nVertDatumType );
}

/************************************************************************/
/*                           SetCompoundCS()                            */
/************************************************************************/

/**
 * \brief Setup a compound coordinate system.
 *
 * This method is the same as the C function OSRSetCompoundCS().

 * This method is replace the current SRS with a COMPD_CS coordinate system
 * consisting of the passed in horizontal and vertical coordinate systems.
 *
 * @param pszName the name of the compound coordinate system.
 *
 * @param poHorizSRS the horizontal SRS (PROJCS or GEOGCS).
 *
 * @param poVertSRS the vertical SRS (VERT_CS).
 *
 * @return OGRERR_NONE on success.
 */

OGRErr
OGRSpatialReference::SetCompoundCS( const char *pszName,
                                    const OGRSpatialReference *poHorizSRS,
                                    const OGRSpatialReference *poVertSRS )

{
/* -------------------------------------------------------------------- */
/*      Verify these are legal horizontal and vertical coordinate       */
/*      systems.                                                        */
/* -------------------------------------------------------------------- */
    if( !poVertSRS->IsVertical() )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "SetCompoundCS() fails, vertical component is not VERT_CS." );
        return OGRERR_FAILURE;
    }
    if( !poHorizSRS->IsProjected()
        && !poHorizSRS->IsGeographic() )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "SetCompoundCS() fails, horizontal component is not PROJCS or GEOGCS." );
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Replace with compound srs.                                      */
/* -------------------------------------------------------------------- */
    Clear();

    poRoot = new OGR_SRSNode( "COMPD_CS" );
    poRoot->AddChild( new OGR_SRSNode( pszName ) );
    poRoot->AddChild( poHorizSRS->GetRoot()->Clone() );
    poRoot->AddChild( poVertSRS->GetRoot()->Clone() );

    return OGRERR_NONE;
}

/************************************************************************/
/*                          OSRSetCompoundCS()                          */
/************************************************************************/

/**
 * \brief Setup a compound coordinate system.
 *
 * This function is the same as OGRSpatialReference::SetCompoundCS()
 */
OGRErr OSRSetCompoundCS( OGRSpatialReferenceH hSRS,
                         const char *pszName,
                         OGRSpatialReferenceH hHorizSRS,
                         OGRSpatialReferenceH hVertSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRSetCompoundCS", OGRERR_FAILURE );
    VALIDATE_POINTER1( hHorizSRS, "OSRSetCompoundCS", OGRERR_FAILURE );
    VALIDATE_POINTER1( hVertSRS, "OSRSetCompoundCS", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetCompoundCS( pszName,
                       ToPointer(hHorizSRS),
                       ToPointer(hVertSRS) );
}

/************************************************************************/
/*                             SetProjCS()                              */
/************************************************************************/

/**
 * \brief Set the user visible PROJCS name.
 *
 * This method is the same as the C function OSRSetProjCS().
 *
 * This method will ensure a PROJCS node is created as the root,
 * and set the provided name on it.  If used on a GEOGCS coordinate system,
 * the GEOGCS node will be demoted to be a child of the new PROJCS root.
 *
 * @param pszName the user visible name to assign.  Not used as a key.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetProjCS( const char * pszName )

{
    OGR_SRSNode *poGeogCS = nullptr;
    OGR_SRSNode *poProjCS = GetAttrNode( "PROJCS" );

    if( poRoot != nullptr && EQUAL(poRoot->GetValue(), "GEOGCS") )
    {
        poGeogCS = poRoot;
        poRoot = nullptr;
    }

    if( poProjCS == nullptr && GetRoot() != nullptr )
    {
        CPLDebug( "OGR",
                  "OGRSpatialReference::SetProjCS(%s) failed.  "
                  "It appears an incompatible root node (%s) already exists.",
                  pszName, GetRoot()->GetValue() );
        return OGRERR_FAILURE;
    }

    SetNode( "PROJCS", pszName );

    if( poRoot != nullptr && poGeogCS != nullptr )
        poRoot->InsertChild( poGeogCS, 1 );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetProjCS()                            */
/************************************************************************/

/**
 * \brief Set the user visible PROJCS name.
 *
 * This function is the same as OGRSpatialReference::SetProjCS()
 */
OGRErr OSRSetProjCS( OGRSpatialReferenceH hSRS, const char * pszName )

{
    VALIDATE_POINTER1( hSRS, "OSRSetProjCS", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetProjCS( pszName );
}

/************************************************************************/
/*                           SetProjection()                            */
/************************************************************************/

/**
 * \brief Set a projection name.
 *
 * This method is the same as the C function OSRSetProjection().
 *
 * @param pszProjection the projection name, which should be selected from
 * the macros in ogr_srs_api.h, such as SRS_PT_TRANSVERSE_MERCATOR.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetProjection( const char * pszProjection )

{
    OGR_SRSNode *poGeogCS = nullptr;

    if( poRoot != nullptr && EQUAL(poRoot->GetValue(), "GEOGCS") )
    {
        poGeogCS = poRoot;
        poRoot = nullptr;
    }

    if( !GetAttrNode( "PROJCS" ) )
    {
        SetNode( "PROJCS", "unnamed" );
    }

    const OGRErr eErr = SetNode( "PROJCS|PROJECTION", pszProjection );
    if( eErr != OGRERR_NONE )
        return eErr;

    if( poGeogCS != nullptr )
        poRoot->InsertChild( poGeogCS, 1 );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetProjection()                        */
/************************************************************************/

/**
 * \brief Set a projection name.
 *
 * This function is the same as OGRSpatialReference::SetProjection()
 */
OGRErr OSRSetProjection( OGRSpatialReferenceH hSRS,
                         const char * pszProjection )

{
    VALIDATE_POINTER1( hSRS, "OSRSetProjection", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetProjection( pszProjection );
}

/************************************************************************/
/*                            SetProjParm()                             */
/************************************************************************/

/**
 * \brief Set a projection parameter value.
 *
 * Adds a new PARAMETER under the PROJCS with the indicated name and value.
 *
 * This method is the same as the C function OSRSetProjParm().
 *
 * Please check http://www.remotesensing.org/geotiff/proj_list pages for
 * legal parameter names for specific projections.
 *
 *
 * @param pszParmName the parameter name, which should be selected from
 * the macros in ogr_srs_api.h, such as SRS_PP_CENTRAL_MERIDIAN.
 *
 * @param dfValue value to assign.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetProjParm( const char * pszParmName,
                                         double dfValue )

{
    OGR_SRSNode *poPROJCS = GetAttrNode( "PROJCS" );

    if( poPROJCS == nullptr )
        return OGRERR_FAILURE;

    char szValue[64] = { '\0' };
    OGRsnPrintDouble( szValue, sizeof(szValue), dfValue );

/* -------------------------------------------------------------------- */
/*      Try to find existing parameter with this name.                  */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poParm = nullptr;
    for( int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++ )
    {
        poParm = poPROJCS->GetChild( iChild );

        if( EQUAL(poParm->GetValue(), "PARAMETER")
            && poParm->GetChildCount() == 2
            && EQUAL(poParm->GetChild(0)->GetValue(), pszParmName) )
        {
            poParm->GetChild(1)->SetValue( szValue );
            return OGRERR_NONE;
        }
    }

/* -------------------------------------------------------------------- */
/*      Otherwise create a new parameter and append.                    */
/* -------------------------------------------------------------------- */
    poParm = new OGR_SRSNode( "PARAMETER" );
    poParm->AddChild( new OGR_SRSNode( pszParmName ) );
    poParm->AddChild( new OGR_SRSNode( szValue ) );

    poPROJCS->AddChild( poParm );

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetProjParm()                           */
/************************************************************************/

/**
 * \brief Set a projection parameter value.
 *
 * This function is the same as OGRSpatialReference::SetProjParm()
 */
OGRErr OSRSetProjParm( OGRSpatialReferenceH hSRS,
                       const char * pszParmName, double dfValue )

{
    VALIDATE_POINTER1( hSRS, "OSRSetProjParm", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetProjParm( pszParmName, dfValue );
}

/************************************************************************/
/*                            FindProjParm()                            */
/************************************************************************/

/**
  * \brief Return the child index of the named projection parameter on
  * its parent PROJCS node.
  *
  * @param pszParameter projection parameter to look for
  * @param poPROJCS projection CS node to look in. If NULL is passed,
  *        the PROJCS node of the SpatialReference object will be searched.
  *
  * @return the child index of the named projection parameter. -1 on failure
  */
int OGRSpatialReference::FindProjParm( const char *pszParameter,
                                       const OGR_SRSNode *poPROJCS ) const

{
    if( poPROJCS == nullptr )
        poPROJCS = GetAttrNode( "PROJCS" );

    if( poPROJCS == nullptr )
        return -1;

/* -------------------------------------------------------------------- */
/*      Search for requested parameter.                                 */
/* -------------------------------------------------------------------- */
    for( int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++ )
    {
        const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);

        if( EQUAL(poParameter->GetValue(), "PARAMETER")
            && poParameter->GetChildCount() == 2
            && EQUAL(poPROJCS->GetChild(iChild)->GetChild(0)->GetValue(),
                     pszParameter) )
        {
            return iChild;
        }
    }

/* -------------------------------------------------------------------- */
/*      Try similar names, for selected parameters.                     */
/* -------------------------------------------------------------------- */
    if( EQUAL(pszParameter, SRS_PP_LATITUDE_OF_ORIGIN) )
    {
        return FindProjParm( SRS_PP_LATITUDE_OF_CENTER, poPROJCS );
    }

    if( EQUAL(pszParameter, SRS_PP_CENTRAL_MERIDIAN) )
    {
        int iChild = FindProjParm(SRS_PP_LONGITUDE_OF_CENTER, poPROJCS );
        if( iChild == -1 )
            iChild = FindProjParm(SRS_PP_LONGITUDE_OF_ORIGIN, poPROJCS );
        return iChild;
    }

    return -1;
}

/************************************************************************/
/*                            GetProjParm()                             */
/************************************************************************/

/**
 * \brief Fetch a projection parameter value.
 *
 * NOTE: This code should be modified to translate non degree angles into
 * degrees based on the GEOGCS unit.  This has not yet been done.
 *
 * This method is the same as the C function OSRGetProjParm().
 *
 * @param pszName the name of the parameter to fetch, from the set of
 * SRS_PP codes in ogr_srs_api.h.
 *
 * @param dfDefaultValue the value to return if this parameter doesn't exist.
 *
 * @param pnErr place to put error code on failure.  Ignored if NULL.
 *
 * @return value of parameter.
 */

double OGRSpatialReference::GetProjParm( const char * pszName,
                                         double dfDefaultValue,
                                         OGRErr *pnErr ) const

{
    if( pnErr != nullptr )
        *pnErr = OGRERR_NONE;

/* -------------------------------------------------------------------- */
/*      Find the desired parameter.                                     */
/* -------------------------------------------------------------------- */
    const OGR_SRSNode *poPROJCS = GetAttrNode( "PROJCS" );
    if( poPROJCS == nullptr )
    {
        if( pnErr != nullptr )
            *pnErr = OGRERR_FAILURE;
        return dfDefaultValue;
    }

    const int iChild = FindProjParm( pszName, poPROJCS );
    if( iChild == -1 )
    {
        if( pnErr != nullptr )
            *pnErr = OGRERR_FAILURE;
        return dfDefaultValue;
    }

    const OGR_SRSNode *poParameter = poPROJCS->GetChild(iChild);
    return CPLAtof(poParameter->GetChild(1)->GetValue());
}

/************************************************************************/
/*                           OSRGetProjParm()                           */
/************************************************************************/

/**
 * \brief Fetch a projection parameter value.
 *
 * This function is the same as OGRSpatialReference::GetProjParm()
 */
double OSRGetProjParm( OGRSpatialReferenceH hSRS, const char *pszName,
                       double dfDefaultValue, OGRErr *pnErr )

{
    VALIDATE_POINTER1( hSRS, "OSRGetProjParm", 0 );

    return ToPointer(hSRS)->
        GetProjParm(pszName, dfDefaultValue, pnErr);
}

/************************************************************************/
/*                          GetNormProjParm()                           */
/************************************************************************/

/**
 * \brief Fetch a normalized projection parameter value.
 *
 * This method is the same as GetProjParm() except that the value of
 * the parameter is "normalized" into degrees or meters depending on
 * whether it is linear or angular.
 *
 * This method is the same as the C function OSRGetNormProjParm().
 *
 * @param pszName the name of the parameter to fetch, from the set of
 * SRS_PP codes in ogr_srs_api.h.
 *
 * @param dfDefaultValue the value to return if this parameter doesn't exist.
 *
 * @param pnErr place to put error code on failure.  Ignored if NULL.
 *
 * @return value of parameter.
 */

double OGRSpatialReference::GetNormProjParm( const char * pszName,
                                             double dfDefaultValue,
                                             OGRErr *pnErr ) const

{
    GetNormInfo();

    OGRErr nError = OGRERR_NONE;
    double dfRawResult = GetProjParm( pszName, dfDefaultValue, &nError );
    if( pnErr != nullptr )
        *pnErr = nError;

    // If we got the default just return it unadjusted.
    if( nError != OGRERR_NONE )
        return dfRawResult;

    if( dfToDegrees != 1.0 && IsAngularParameter(pszName) )
        dfRawResult *= dfToDegrees;

    if( dfToMeter != 1.0 && IsLinearParameter( pszName ) )
        return dfRawResult * dfToMeter;
#ifdef WKT_LONGITUDE_RELATIVE_TO_PM
    else if( dfFromGreenwich != 0.0 && IsLongitudeParameter( pszName ) )
        return dfRawResult + dfFromGreenwich;
#endif

    return dfRawResult;
}

/************************************************************************/
/*                         OSRGetNormProjParm()                         */
/************************************************************************/

/**
 * \brief This function is the same as OGRSpatialReference::
 *
 * This function is the same as OGRSpatialReference::GetNormProjParm()
 */
double OSRGetNormProjParm( OGRSpatialReferenceH hSRS, const char *pszName,
                           double dfDefaultValue, OGRErr *pnErr )

{
    VALIDATE_POINTER1( hSRS, "OSRGetNormProjParm", 0 );

    return ToPointer(hSRS)->
        GetNormProjParm(pszName, dfDefaultValue, pnErr);
}

/************************************************************************/
/*                          SetNormProjParm()                           */
/************************************************************************/

/**
 * \brief Set a projection parameter with a normalized value.
 *
 * This method is the same as SetProjParm() except that the value of
 * the parameter passed in is assumed to be in "normalized" form (decimal
 * degrees for angular values, meters for linear values.  The values are
 * converted in a form suitable for the GEOGCS and linear units in effect.
 *
 * This method is the same as the C function OSRSetNormProjParm().
 *
 * @param pszName the parameter name, which should be selected from
 * the macros in ogr_srs_api.h, such as SRS_PP_CENTRAL_MERIDIAN.
 *
 * @param dfValue value to assign.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetNormProjParm( const char * pszName,
                                             double dfValue )

{
    GetNormInfo();

    if( (dfToDegrees != 1.0 || dfFromGreenwich != 0.0)
        && IsAngularParameter(pszName) )
    {
#ifdef WKT_LONGITUDE_RELATIVE_TO_PM
        if( dfFromGreenwich != 0.0 && IsLongitudeParameter( pszName ) )
            dfValue -= dfFromGreenwich;
#endif

        dfValue /= dfToDegrees;
    }
    else if( dfToMeter != 1.0 && IsLinearParameter( pszName ) )
        dfValue /= dfToMeter;

    return SetProjParm( pszName, dfValue );
}

/************************************************************************/
/*                         OSRSetNormProjParm()                         */
/************************************************************************/

/**
 * \brief Set a projection parameter with a normalized value.
 *
 * This function is the same as OGRSpatialReference::SetNormProjParm()
 */
OGRErr OSRSetNormProjParm( OGRSpatialReferenceH hSRS,
                           const char * pszParmName, double dfValue )

{
    VALIDATE_POINTER1( hSRS, "OSRSetNormProjParm", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetNormProjParm( pszParmName, dfValue );
}

/************************************************************************/
/*                               SetTM()                                */
/************************************************************************/

OGRErr OGRSpatialReference::SetTM( double dfCenterLat, double dfCenterLong,
                                   double dfScale,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_TRANSVERSE_MERCATOR );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetTM()                              */
/************************************************************************/

OGRErr OSRSetTM( OGRSpatialReferenceH hSRS,
                 double dfCenterLat, double dfCenterLong,
                 double dfScale,
                 double dfFalseEasting,
                 double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetTM", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetTM(
        dfCenterLat, dfCenterLong,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                            SetTMVariant()                            */
/************************************************************************/

OGRErr OGRSpatialReference::SetTMVariant(
    const char *pszVariantName,
    double dfCenterLat, double dfCenterLong,
    double dfScale,
    double dfFalseEasting,
    double dfFalseNorthing )

{
    SetProjection( pszVariantName );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                          OSRSetTMVariant()                           */
/************************************************************************/

OGRErr OSRSetTMVariant( OGRSpatialReferenceH hSRS,
                        const char *pszVariantName,
                        double dfCenterLat, double dfCenterLong,
                        double dfScale,
                        double dfFalseEasting,
                        double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetTMVariant", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetTMVariant(
        pszVariantName,
        dfCenterLat, dfCenterLong,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                              SetTMSO()                               */
/************************************************************************/

OGRErr OGRSpatialReference::SetTMSO( double dfCenterLat, double dfCenterLong,
                                     double dfScale,
                                     double dfFalseEasting,
                                     double dfFalseNorthing )

{
    SetProjection( SRS_PT_TRANSVERSE_MERCATOR_SOUTH_ORIENTED );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              SetTPED()                               */
/************************************************************************/

OGRErr OGRSpatialReference::SetTPED( double dfLat1, double dfLong1,
                                     double dfLat2, double dfLong2,
                                     double dfFalseEasting,
                                     double dfFalseNorthing )

{
    SetProjection( SRS_PT_TWO_POINT_EQUIDISTANT );
    SetNormProjParm( SRS_PP_LATITUDE_OF_1ST_POINT, dfLat1 );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_1ST_POINT, dfLong1 );
    SetNormProjParm( SRS_PP_LATITUDE_OF_2ND_POINT, dfLat2 );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_2ND_POINT, dfLong2 );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetTPED()                             */
/************************************************************************/

OGRErr OSRSetTPED( OGRSpatialReferenceH hSRS,
                   double dfLat1, double dfLong1,
                   double dfLat2, double dfLong2,
                   double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetTPED", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetTPED(
        dfLat1, dfLong1, dfLat2, dfLong2,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                             OSRSetTMSO()                             */
/************************************************************************/

OGRErr OSRSetTMSO( OGRSpatialReferenceH hSRS,
                 double dfCenterLat, double dfCenterLong,
                 double dfScale,
                 double dfFalseEasting,
                 double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetTMSO", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetTMSO(
        dfCenterLat, dfCenterLong,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetTMG()                               */
/************************************************************************/

OGRErr
OGRSpatialReference::SetTMG( double dfCenterLat, double dfCenterLong,
                             double dfFalseEasting, double dfFalseNorthing )

{
    SetProjection( SRS_PT_TUNISIA_MINING_GRID );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetTMG()                              */
/************************************************************************/

OGRErr OSRSetTMG( OGRSpatialReferenceH hSRS,
                 double dfCenterLat, double dfCenterLong,
                 double dfFalseEasting,
                 double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetTMG", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetTMG(
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                              SetACEA()                               */
/************************************************************************/

OGRErr OGRSpatialReference::SetACEA( double dfStdP1, double dfStdP2,
                                     double dfCenterLat, double dfCenterLong,
                                     double dfFalseEasting,
                                     double dfFalseNorthing )

{
    SetProjection( SRS_PT_ALBERS_CONIC_EQUAL_AREA );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_1, dfStdP1 );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_2, dfStdP2 );
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfCenterLat );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_CENTER, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetACEA()                             */
/************************************************************************/

OGRErr OSRSetACEA( OGRSpatialReferenceH hSRS,
                   double dfStdP1, double dfStdP2,
                   double dfCenterLat, double dfCenterLong,
                   double dfFalseEasting,
                   double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetACEA", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetACEA(
        dfStdP1, dfStdP2,
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetAE()                                */
/************************************************************************/

OGRErr OGRSpatialReference::SetAE( double dfCenterLat, double dfCenterLong,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_AZIMUTHAL_EQUIDISTANT );
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfCenterLat );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_CENTER, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetAE()                              */
/************************************************************************/

OGRErr OSRSetAE( OGRSpatialReferenceH hSRS,
                 double dfCenterLat, double dfCenterLong,
                 double dfFalseEasting,
                 double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetACEA", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetAE(
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                              SetBonne()                              */
/************************************************************************/

OGRErr OGRSpatialReference::SetBonne(
    double dfStdP1, double dfCentralMeridian,
    double dfFalseEasting, double dfFalseNorthing )

{
    SetProjection( SRS_PT_BONNE );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_1, dfStdP1 );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetBonne()                             */
/************************************************************************/

OGRErr OSRSetBonne( OGRSpatialReferenceH hSRS,
                    double dfStdP1, double dfCentralMeridian,
                    double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetBonne", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetBonne(
        dfStdP1, dfCentralMeridian,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetCEA()                               */
/************************************************************************/

OGRErr OGRSpatialReference::SetCEA( double dfStdP1, double dfCentralMeridian,
                                    double dfFalseEasting,
                                    double dfFalseNorthing )

{
    SetProjection( SRS_PT_CYLINDRICAL_EQUAL_AREA );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_1, dfStdP1 );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetCEA()                              */
/************************************************************************/

OGRErr OSRSetCEA( OGRSpatialReferenceH hSRS,
                  double dfStdP1, double dfCentralMeridian,
                  double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetCEA", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetCEA(
        dfStdP1, dfCentralMeridian,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetCS()                                */
/************************************************************************/

OGRErr OGRSpatialReference::SetCS( double dfCenterLat, double dfCenterLong,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_CASSINI_SOLDNER );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetCS()                              */
/************************************************************************/

OGRErr OSRSetCS( OGRSpatialReferenceH hSRS,
                 double dfCenterLat, double dfCenterLong,
                 double dfFalseEasting,
                 double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetCS", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetCS(
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetEC()                                */
/************************************************************************/

OGRErr OGRSpatialReference::SetEC( double dfStdP1, double dfStdP2,
                                   double dfCenterLat, double dfCenterLong,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_EQUIDISTANT_CONIC );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_1, dfStdP1 );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_2, dfStdP2 );
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfCenterLat );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_CENTER, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetEC()                              */
/************************************************************************/

OGRErr OSRSetEC( OGRSpatialReferenceH hSRS,
                 double dfStdP1, double dfStdP2,
                 double dfCenterLat, double dfCenterLong,
                 double dfFalseEasting,
                 double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetEC", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetEC(
        dfStdP1, dfStdP2,
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                             SetEckert()                              */
/************************************************************************/

OGRErr OGRSpatialReference::SetEckert( int nVariation,  // 1-6.
                                       double dfCentralMeridian,
                                       double dfFalseEasting,
                                       double dfFalseNorthing )

{
    if( nVariation == 1 )
        SetProjection( SRS_PT_ECKERT_I );
    else if( nVariation == 2 )
        SetProjection( SRS_PT_ECKERT_II );
    else if( nVariation == 3 )
        SetProjection( SRS_PT_ECKERT_III );
    else if( nVariation == 4 )
        SetProjection( SRS_PT_ECKERT_IV );
    else if( nVariation == 5 )
        SetProjection( SRS_PT_ECKERT_V );
    else if( nVariation == 6 )
        SetProjection( SRS_PT_ECKERT_VI );
    else
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Unsupported Eckert variation (%d).",
                  nVariation );
        return OGRERR_UNSUPPORTED_SRS;
    }

    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetEckert()                            */
/************************************************************************/

OGRErr OSRSetEckert( OGRSpatialReferenceH hSRS,
                     int nVariation,
                     double dfCentralMeridian,
                     double dfFalseEasting,
                     double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetEckert", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetEckert(
        nVariation, dfCentralMeridian,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                            SetEckertIV()                             */
/*                                                                      */
/*      Deprecated                                                      */
/************************************************************************/

OGRErr OGRSpatialReference::SetEckertIV( double dfCentralMeridian,
                                         double dfFalseEasting,
                                         double dfFalseNorthing )

{
    SetProjection( SRS_PT_ECKERT_IV );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetEckertIV()                           */
/************************************************************************/

OGRErr OSRSetEckertIV( OGRSpatialReferenceH hSRS,
                       double dfCentralMeridian,
                       double dfFalseEasting,
                       double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetEckertIV", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetEckertIV(
        dfCentralMeridian,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                            SetEckertVI()                             */
/*                                                                      */
/*      Deprecated                                                      */
/************************************************************************/

OGRErr OGRSpatialReference::SetEckertVI( double dfCentralMeridian,
                                         double dfFalseEasting,
                                         double dfFalseNorthing )

{
    SetProjection( SRS_PT_ECKERT_VI );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetEckertVI()                           */
/************************************************************************/

OGRErr OSRSetEckertVI( OGRSpatialReferenceH hSRS,
                       double dfCentralMeridian,
                       double dfFalseEasting,
                       double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetEckertVI", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetEckertVI(
        dfCentralMeridian,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                         SetEquirectangular()                         */
/************************************************************************/

OGRErr OGRSpatialReference::SetEquirectangular(
                                   double dfCenterLat, double dfCenterLong,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_EQUIRECTANGULAR );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                       OSRSetEquirectangular()                        */
/************************************************************************/

OGRErr OSRSetEquirectangular( OGRSpatialReferenceH hSRS,
                              double dfCenterLat, double dfCenterLong,
                              double dfFalseEasting,
                              double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetEquirectangular", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetEquirectangular(
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                         SetEquirectangular2()                        */
/* Generalized form                                                     */
/************************************************************************/

OGRErr OGRSpatialReference::SetEquirectangular2(
                                   double dfCenterLat, double dfCenterLong,
                                   double dfStdParallel1,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_EQUIRECTANGULAR );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_1, dfStdParallel1 );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                       OSRSetEquirectangular2()                       */
/************************************************************************/

OGRErr OSRSetEquirectangular2( OGRSpatialReferenceH hSRS,
                               double dfCenterLat, double dfCenterLong,
                               double dfStdParallel1,
                               double dfFalseEasting,
                               double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetEquirectangular2", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetEquirectangular2(
        dfCenterLat, dfCenterLong,
        dfStdParallel1,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetGS()                                */
/************************************************************************/

OGRErr OGRSpatialReference::SetGS( double dfCentralMeridian,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_GALL_STEREOGRAPHIC );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetGS()                              */
/************************************************************************/

OGRErr OSRSetGS( OGRSpatialReferenceH hSRS,
                 double dfCentralMeridian,
                 double dfFalseEasting,
                 double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetGS", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetGS(
        dfCentralMeridian,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetGH()                                */
/************************************************************************/

OGRErr OGRSpatialReference::SetGH( double dfCentralMeridian,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_GOODE_HOMOLOSINE );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetGH()                              */
/************************************************************************/

OGRErr OSRSetGH( OGRSpatialReferenceH hSRS,
                 double dfCentralMeridian,
                 double dfFalseEasting,
                 double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetGH", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetGH(
        dfCentralMeridian,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                              SetIGH()                                */
/************************************************************************/

OGRErr OGRSpatialReference::SetIGH()

{
    SetProjection( SRS_PT_IGH );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetIGH()                             */
/************************************************************************/

OGRErr OSRSetIGH( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRSetIGH", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetIGH();
}

/************************************************************************/
/*                              SetGEOS()                               */
/************************************************************************/

OGRErr OGRSpatialReference::SetGEOS( double dfCentralMeridian,
                                     double dfSatelliteHeight,
                                     double dfFalseEasting,
                                     double dfFalseNorthing )

{
    SetProjection( SRS_PT_GEOSTATIONARY_SATELLITE );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    SetNormProjParm( SRS_PP_SATELLITE_HEIGHT, dfSatelliteHeight );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetGEOS()                             */
/************************************************************************/

OGRErr OSRSetGEOS( OGRSpatialReferenceH hSRS,
                   double dfCentralMeridian,
                   double dfSatelliteHeight,
                   double dfFalseEasting,
                   double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetGEOS", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetGEOS(
        dfCentralMeridian, dfSatelliteHeight,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                       SetGaussSchreiberTMercator()                   */
/************************************************************************/

OGRErr OGRSpatialReference::SetGaussSchreiberTMercator(
                                   double dfCenterLat, double dfCenterLong,
                                   double dfScale,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_GAUSSSCHREIBERTMERCATOR );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                     OSRSetGaussSchreiberTMercator()                  */
/************************************************************************/

OGRErr OSRSetGaussSchreiberTMercator( OGRSpatialReferenceH hSRS,
                                      double dfCenterLat, double dfCenterLong,
                                      double dfScale,
                                      double dfFalseEasting,
                                      double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetGaussSchreiberTMercator", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetGaussSchreiberTMercator(
        dfCenterLat, dfCenterLong, dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                            SetGnomonic()                             */
/************************************************************************/

OGRErr OGRSpatialReference::SetGnomonic(
                                   double dfCenterLat, double dfCenterLong,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_GNOMONIC );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetGnomonic()                           */
/************************************************************************/

OGRErr OSRSetGnomonic( OGRSpatialReferenceH hSRS,
                       double dfCenterLat, double dfCenterLong,
                       double dfFalseEasting,
                       double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetGnomonic", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetGnomonic(
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                              SetHOMAC()                              */
/************************************************************************/

/**
 * \brief Set an Hotine Oblique Mercator Azimuth Center projection using
 * azimuth angle.
 *
 * This projection corresponds to EPSG projection method 9815, also
 * sometimes known as hotine oblique mercator (variant B).
 *
 * This method does the same thing as the C function OSRSetHOMAC().
 *
 * @param dfCenterLat Latitude of the projection origin.
 * @param dfCenterLong Longitude of the projection origin.
 * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
 * centerline.
 * @param dfRectToSkew ?.
 * @param dfScale Scale factor applies to the projection origin.
 * @param dfFalseEasting False easting.
 * @param dfFalseNorthing False northing.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetHOMAC( double dfCenterLat, double dfCenterLong,
                                      double dfAzimuth, double dfRectToSkew,
                                      double dfScale,
                                      double dfFalseEasting,
                                      double dfFalseNorthing )

{
    SetProjection( SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER );
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfCenterLat );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_CENTER, dfCenterLong );
    SetNormProjParm( SRS_PP_AZIMUTH, dfAzimuth );
    SetNormProjParm( SRS_PP_RECTIFIED_GRID_ANGLE, dfRectToSkew );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetHOMAC()                             */
/************************************************************************/

/**
 * \brief Set an Oblique Mercator projection using azimuth angle.
 *
 * This is the same as the C++ method OGRSpatialReference::SetHOMAC()
 */
OGRErr OSRSetHOMAC( OGRSpatialReferenceH hSRS,
                    double dfCenterLat, double dfCenterLong,
                    double dfAzimuth, double dfRectToSkew,
                    double dfScale,
                    double dfFalseEasting,
                    double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetHOMAC", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetHOMAC(
        dfCenterLat, dfCenterLong,
        dfAzimuth, dfRectToSkew,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetHOM()                               */
/************************************************************************/

/**
 * \brief Set a Hotine Oblique Mercator projection using azimuth angle.
 *
 * This projection corresponds to EPSG projection method 9812, also
 * sometimes known as hotine oblique mercator (variant A)..
 *
 * This method does the same thing as the C function OSRSetHOM().
 *
 * @param dfCenterLat Latitude of the projection origin.
 * @param dfCenterLong Longitude of the projection origin.
 * @param dfAzimuth Azimuth, measured clockwise from North, of the projection
 * centerline.
 * @param dfRectToSkew ?.
 * @param dfScale Scale factor applies to the projection origin.
 * @param dfFalseEasting False easting.
 * @param dfFalseNorthing False northing.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetHOM( double dfCenterLat, double dfCenterLong,
                                    double dfAzimuth, double dfRectToSkew,
                                    double dfScale,
                                    double dfFalseEasting,
                                    double dfFalseNorthing )

{
    SetProjection( SRS_PT_HOTINE_OBLIQUE_MERCATOR );
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfCenterLat );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_CENTER, dfCenterLong );
    SetNormProjParm( SRS_PP_AZIMUTH, dfAzimuth );
    SetNormProjParm( SRS_PP_RECTIFIED_GRID_ANGLE, dfRectToSkew );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetHOM()                              */
/************************************************************************/
/**
 * \brief Set a Hotine Oblique Mercator projection using azimuth angle.
 *
 * This is the same as the C++ method OGRSpatialReference::SetHOM()
 */
OGRErr OSRSetHOM( OGRSpatialReferenceH hSRS,
                  double dfCenterLat, double dfCenterLong,
                  double dfAzimuth, double dfRectToSkew,
                  double dfScale,
                  double dfFalseEasting,
                  double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetHOM", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetHOM(
        dfCenterLat, dfCenterLong,
        dfAzimuth, dfRectToSkew,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                             SetHOM2PNO()                             */
/************************************************************************/

/**
 * \brief Set a Hotine Oblique Mercator projection using two points on projection
 * centerline.
 *
 * This method does the same thing as the C function OSRSetHOM2PNO().
 *
 * @param dfCenterLat Latitude of the projection origin.
 * @param dfLat1 Latitude of the first point on center line.
 * @param dfLong1 Longitude of the first point on center line.
 * @param dfLat2 Latitude of the second point on center line.
 * @param dfLong2 Longitude of the second point on center line.
 * @param dfScale Scale factor applies to the projection origin.
 * @param dfFalseEasting False easting.
 * @param dfFalseNorthing False northing.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetHOM2PNO( double dfCenterLat,
                                        double dfLat1, double dfLong1,
                                        double dfLat2, double dfLong2,
                                        double dfScale,
                                        double dfFalseEasting,
                                        double dfFalseNorthing )

{
    SetProjection( SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN );
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfCenterLat );
    SetNormProjParm( SRS_PP_LATITUDE_OF_POINT_1, dfLat1 );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_POINT_1, dfLong1 );
    SetNormProjParm( SRS_PP_LATITUDE_OF_POINT_2, dfLat2 );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_POINT_2, dfLong2 );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetHOM2PNO()                            */
/************************************************************************/
/**
 * \brief Set a Hotine Oblique Mercator projection using two points on
 *  projection centerline.
 *
 * This is the same as the C++ method OGRSpatialReference::SetHOM2PNO()
 */
OGRErr OSRSetHOM2PNO( OGRSpatialReferenceH hSRS,
                      double dfCenterLat,
                      double dfLat1, double dfLong1,
                      double dfLat2, double dfLong2,
                      double dfScale,
                      double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetHOM2PNO", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetHOM2PNO(
        dfCenterLat,
        dfLat1, dfLong1,
        dfLat2, dfLong2,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                            SetIWMPolyconic()                         */
/************************************************************************/

OGRErr OGRSpatialReference::SetIWMPolyconic(
                                double dfLat1, double dfLat2,
                                double dfCenterLong,
                                double dfFalseEasting, double dfFalseNorthing )

{
    SetProjection( SRS_PT_IMW_POLYCONIC );
    SetNormProjParm( SRS_PP_LATITUDE_OF_1ST_POINT, dfLat1 );
    SetNormProjParm( SRS_PP_LATITUDE_OF_2ND_POINT, dfLat2 );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                          OSRSetIWMPolyconic()                        */
/************************************************************************/

OGRErr OSRSetIWMPolyconic( OGRSpatialReferenceH hSRS,
                           double dfLat1, double dfLat2,
                           double dfCenterLong,
                           double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetIWMPolyconic", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetIWMPolyconic(
        dfLat1, dfLat2, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                             SetKrovak()                              */
/************************************************************************/

OGRErr OGRSpatialReference::SetKrovak( double dfCenterLat, double dfCenterLong,
                                       double dfAzimuth,
                                       double dfPseudoStdParallel1,
                                       double dfScale,
                                       double dfFalseEasting,
                                       double dfFalseNorthing )

{
    SetProjection( SRS_PT_KROVAK );
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfCenterLat );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_CENTER, dfCenterLong );
    SetNormProjParm( SRS_PP_AZIMUTH, dfAzimuth );
    SetNormProjParm( SRS_PP_PSEUDO_STD_PARALLEL_1, dfPseudoStdParallel1 );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetKrovak()                            */
/************************************************************************/

OGRErr OSRSetKrovak( OGRSpatialReferenceH hSRS,
                     double dfCenterLat, double dfCenterLong,
                     double dfAzimuth, double dfPseudoStdParallel1,
                     double dfScale,
                     double dfFalseEasting,
                     double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetKrovak", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetKrovak(
        dfCenterLat, dfCenterLong,
        dfAzimuth, dfPseudoStdParallel1,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                              SetLAEA()                               */
/************************************************************************/

OGRErr OGRSpatialReference::SetLAEA( double dfCenterLat, double dfCenterLong,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA );
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfCenterLat );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_CENTER, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetLAEA()                             */
/************************************************************************/

OGRErr OSRSetLAEA( OGRSpatialReferenceH hSRS,
                   double dfCenterLat, double dfCenterLong,
                   double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetLAEA", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetLAEA(
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetLCC()                               */
/************************************************************************/

OGRErr OGRSpatialReference::SetLCC( double dfStdP1, double dfStdP2,
                                    double dfCenterLat, double dfCenterLong,
                                    double dfFalseEasting,
                                    double dfFalseNorthing )

{
    SetProjection( SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_1, dfStdP1 );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_2, dfStdP2 );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetLCC()                              */
/************************************************************************/

OGRErr OSRSetLCC( OGRSpatialReferenceH hSRS,
                  double dfStdP1, double dfStdP2,
                  double dfCenterLat, double dfCenterLong,
                  double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetLCC", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetLCC(
        dfStdP1, dfStdP2,
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                             SetLCC1SP()                              */
/************************************************************************/

OGRErr OGRSpatialReference::SetLCC1SP( double dfCenterLat, double dfCenterLong,
                                       double dfScale,
                                       double dfFalseEasting,
                                       double dfFalseNorthing )

{
    SetProjection( SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetLCC1SP()                            */
/************************************************************************/

OGRErr OSRSetLCC1SP( OGRSpatialReferenceH hSRS,
                     double dfCenterLat, double dfCenterLong,
                     double dfScale,
                     double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetLCC1SP", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetLCC1SP(
        dfCenterLat, dfCenterLong,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                              SetLCCB()                               */
/************************************************************************/

OGRErr OGRSpatialReference::SetLCCB( double dfStdP1, double dfStdP2,
                                     double dfCenterLat, double dfCenterLong,
                                     double dfFalseEasting,
                                     double dfFalseNorthing )

{
    SetProjection( SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP_BELGIUM );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_1, dfStdP1 );
    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_2, dfStdP2 );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetLCCB()                             */
/************************************************************************/

OGRErr OSRSetLCCB( OGRSpatialReferenceH hSRS,
                   double dfStdP1, double dfStdP2,
                   double dfCenterLat, double dfCenterLong,
                   double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetLCCB", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetLCCB(
        dfStdP1, dfStdP2,
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetMC()                                */
/************************************************************************/

OGRErr OGRSpatialReference::SetMC( double dfCenterLat, double dfCenterLong,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_MILLER_CYLINDRICAL );
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfCenterLat );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_CENTER, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetMC()                              */
/************************************************************************/

OGRErr OSRSetMC( OGRSpatialReferenceH hSRS,
                 double dfCenterLat, double dfCenterLong,
                 double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetMC", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetMC(
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                            SetMercator()                             */
/************************************************************************/

OGRErr OGRSpatialReference::SetMercator( double dfCenterLat, double dfCenterLong,
                                         double dfScale,
                                         double dfFalseEasting,
                                         double dfFalseNorthing )

{
    SetProjection( SRS_PT_MERCATOR_1SP );

    if( dfCenterLat != 0.0 )
        SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );

    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetMercator()                           */
/************************************************************************/

OGRErr OSRSetMercator( OGRSpatialReferenceH hSRS,
                       double dfCenterLat, double dfCenterLong,
                       double dfScale,
                       double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetMercator", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetMercator(
        dfCenterLat, dfCenterLong,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                           SetMercator2SP()                           */
/************************************************************************/

OGRErr OGRSpatialReference::SetMercator2SP(
    double dfStdP1,
    double dfCenterLat, double dfCenterLong,
    double dfFalseEasting,
    double dfFalseNorthing )

{
    SetProjection( SRS_PT_MERCATOR_2SP );

    SetNormProjParm( SRS_PP_STANDARD_PARALLEL_1, dfStdP1 );
    if( dfCenterLat != 0.0 )
        SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );

    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                         OSRSetMercator2SP()                          */
/************************************************************************/

OGRErr OSRSetMercator2SP( OGRSpatialReferenceH hSRS,
                          double dfStdP1,
                          double dfCenterLat, double dfCenterLong,
                          double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetMercator2SP", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetMercator2SP(
        dfStdP1,
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                            SetMollweide()                            */
/************************************************************************/

OGRErr OGRSpatialReference::SetMollweide( double dfCentralMeridian,
                                          double dfFalseEasting,
                                          double dfFalseNorthing )

{
    SetProjection( SRS_PT_MOLLWEIDE );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                          OSRSetMollweide()                           */
/************************************************************************/

OGRErr OSRSetMollweide( OGRSpatialReferenceH hSRS,
                        double dfCentralMeridian,
                        double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetMollweide", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetMollweide(
        dfCentralMeridian,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                              SetNZMG()                               */
/************************************************************************/

OGRErr OGRSpatialReference::SetNZMG( double dfCenterLat, double dfCenterLong,
                                     double dfFalseEasting,
                                     double dfFalseNorthing )

{
    SetProjection( SRS_PT_NEW_ZEALAND_MAP_GRID );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetNZMG()                             */
/************************************************************************/

OGRErr OSRSetNZMG( OGRSpatialReferenceH hSRS,
                   double dfCenterLat, double dfCenterLong,
                   double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetNZMG", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetNZMG(
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetOS()                                */
/************************************************************************/

OGRErr OGRSpatialReference::SetOS( double dfOriginLat, double dfCMeridian,
                                   double dfScale,
                                   double dfFalseEasting,
                                   double dfFalseNorthing )

{
    SetProjection( SRS_PT_OBLIQUE_STEREOGRAPHIC );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfOriginLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCMeridian );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetOS()                              */
/************************************************************************/

OGRErr OSRSetOS( OGRSpatialReferenceH hSRS,
                 double dfOriginLat, double dfCMeridian,
                 double dfScale,
                 double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetOS", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetOS(
        dfOriginLat, dfCMeridian,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                          SetOrthographic()                           */
/************************************************************************/

OGRErr OGRSpatialReference::SetOrthographic(
                                double dfCenterLat, double dfCenterLong,
                                double dfFalseEasting, double dfFalseNorthing )

{
    SetProjection( SRS_PT_ORTHOGRAPHIC );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                         OSRSetOrthographic()                         */
/************************************************************************/

OGRErr OSRSetOrthographic( OGRSpatialReferenceH hSRS,
                           double dfCenterLat, double dfCenterLong,
                           double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetOrthographic", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetOrthographic(
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                            SetPolyconic()                            */
/************************************************************************/

OGRErr OGRSpatialReference::SetPolyconic(
                                double dfCenterLat, double dfCenterLong,
                                double dfFalseEasting, double dfFalseNorthing )

{
    // note: it seems that by some definitions this should include a
    //       scale_factor parameter.

    SetProjection( SRS_PT_POLYCONIC );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                          OSRSetPolyconic()                           */
/************************************************************************/

OGRErr OSRSetPolyconic( OGRSpatialReferenceH hSRS,
                        double dfCenterLat, double dfCenterLong,
                        double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetPolyconic", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetPolyconic(
        dfCenterLat, dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetPS()                                */
/************************************************************************/

OGRErr OGRSpatialReference::SetPS(
                                double dfCenterLat, double dfCenterLong,
                                double dfScale,
                                double dfFalseEasting, double dfFalseNorthing )

{
    SetProjection( SRS_PT_POLAR_STEREOGRAPHIC );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                              OSRSetPS()                              */
/************************************************************************/

OGRErr OSRSetPS( OGRSpatialReferenceH hSRS,
                 double dfCenterLat, double dfCenterLong,
                 double dfScale,
                 double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetPS", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetPS(
        dfCenterLat, dfCenterLong,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                            SetRobinson()                             */
/************************************************************************/

OGRErr OGRSpatialReference::SetRobinson( double dfCenterLong,
                                         double dfFalseEasting,
                                         double dfFalseNorthing )

{
    SetProjection( SRS_PT_ROBINSON );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_CENTER, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetRobinson()                           */
/************************************************************************/

OGRErr OSRSetRobinson( OGRSpatialReferenceH hSRS,
                        double dfCenterLong,
                        double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetRobinson", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetRobinson(
        dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                           SetSinusoidal()                            */
/************************************************************************/

OGRErr OGRSpatialReference::SetSinusoidal( double dfCenterLong,
                                           double dfFalseEasting,
                                           double dfFalseNorthing )

{
    SetProjection( SRS_PT_SINUSOIDAL );
    SetNormProjParm( SRS_PP_LONGITUDE_OF_CENTER, dfCenterLong );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                          OSRSetSinusoidal()                          */
/************************************************************************/

OGRErr OSRSetSinusoidal( OGRSpatialReferenceH hSRS,
                         double dfCenterLong,
                         double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetSinusoidal", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetSinusoidal(
        dfCenterLong,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                          SetStereographic()                          */
/************************************************************************/

OGRErr OGRSpatialReference::SetStereographic(
                            double dfOriginLat, double dfCMeridian,
                            double dfScale,
                            double dfFalseEasting,
                            double dfFalseNorthing )

{
    SetProjection( SRS_PT_STEREOGRAPHIC );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfOriginLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCMeridian );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, dfScale );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                        OSRSetStereographic()                         */
/************************************************************************/

OGRErr OSRSetStereographic( OGRSpatialReferenceH hSRS,
                            double dfOriginLat, double dfCMeridian,
                            double dfScale,
                            double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetStereographic", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetStereographic(
        dfOriginLat, dfCMeridian,
        dfScale,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetSOC()                               */
/*                                                                      */
/*      NOTE: This definition isn't really used in practice any more    */
/*      and should be considered deprecated.  It seems that swiss       */
/*      oblique mercator is now define as Hotine_Oblique_Mercator       */
/*      with an azimuth of 90 and a rectified_grid_angle of 90.  See    */
/*      EPSG:2056 and Bug 423.                                          */
/************************************************************************/

OGRErr OGRSpatialReference::SetSOC( double dfLatitudeOfOrigin,
                                    double dfCentralMeridian,
                                    double dfFalseEasting,
                                    double dfFalseNorthing )

{
    SetProjection( SRS_PT_SWISS_OBLIQUE_CYLINDRICAL );
    SetNormProjParm( SRS_PP_LATITUDE_OF_CENTER, dfLatitudeOfOrigin );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCentralMeridian );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetSOC()                              */
/************************************************************************/

OGRErr OSRSetSOC( OGRSpatialReferenceH hSRS,
                  double dfLatitudeOfOrigin, double dfCentralMeridian,
                  double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetSOC", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetSOC(
        dfLatitudeOfOrigin, dfCentralMeridian,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetVDG()                               */
/************************************************************************/

OGRErr OGRSpatialReference::SetVDG( double dfCMeridian,
                                    double dfFalseEasting,
                                    double dfFalseNorthing )

{
    SetProjection( SRS_PT_VANDERGRINTEN );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCMeridian );
    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetVDG()                              */
/************************************************************************/

OGRErr OSRSetVDG( OGRSpatialReferenceH hSRS,
                  double dfCentralMeridian,
                  double dfFalseEasting, double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetVDG", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetVDG(
        dfCentralMeridian,
        dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                               SetUTM()                               */
/************************************************************************/

/**
 * \brief Set UTM projection definition.
 *
 * This will generate a projection definition with the full set of
 * transverse mercator projection parameters for the given UTM zone.
 * If no PROJCS[] description is set yet, one will be set to look
 * like "UTM Zone %d, {Northern, Southern} Hemisphere".
 *
 * This method is the same as the C function OSRSetUTM().
 *
 * @param nZone UTM zone.
 *
 * @param bNorth TRUE for northern hemisphere, or FALSE for southern
 * hemisphere.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetUTM( int nZone, int bNorth )

{
    if( nZone < 0 || nZone > 60 )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid zone: %d", nZone);
        return OGRERR_FAILURE;
    }
    SetProjection( SRS_PT_TRANSVERSE_MERCATOR );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, 0 );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, nZone * 6 - 183 );
    SetNormProjParm( SRS_PP_SCALE_FACTOR, 0.9996 );
    SetNormProjParm( SRS_PP_FALSE_EASTING, 500000.0 );

    if( bNorth )
        SetNormProjParm( SRS_PP_FALSE_NORTHING, 0 );
    else
        SetNormProjParm( SRS_PP_FALSE_NORTHING, 10000000 );

    if( EQUAL(GetAttrValue("PROJCS"), "unnamed") )
    {
        char szUTMName[128] = {};

        if( bNorth )
            snprintf(szUTMName, sizeof(szUTMName),
                     "UTM Zone %d, Northern Hemisphere", nZone);
        else
            snprintf(szUTMName, sizeof(szUTMName),
                     "UTM Zone %d, Southern Hemisphere", nZone);

        SetNode( "PROJCS", szUTMName );
    }

    SetLinearUnits( SRS_UL_METER, 1.0 );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetUTM()                              */
/************************************************************************/

/**
 * \brief Set UTM projection definition.
 *
 * This is the same as the C++ method OGRSpatialReference::SetUTM()
 */
OGRErr OSRSetUTM( OGRSpatialReferenceH hSRS, int nZone, int bNorth )

{
    VALIDATE_POINTER1( hSRS, "OSRSetUTM", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetUTM( nZone, bNorth );
}

/************************************************************************/
/*                             GetUTMZone()                             */
/*                                                                      */
/*      Returns zero if it isn't UTM.                                   */
/************************************************************************/

/**
 * \brief Get utm zone information.
 *
 * This is the same as the C function OSRGetUTMZone().
 *
 * In SWIG bindings (Python, Java, etc) the GetUTMZone() method returns a
 * zone which is negative in the southern hemisphere instead of having the
 * pbNorth flag used in the C and C++ interface.
 *
 * @param pbNorth pointer to in to set to TRUE if northern hemisphere, or
 * FALSE if southern.
 *
 * @return UTM zone number or zero if this isn't a UTM definition.
 */

int OGRSpatialReference::GetUTMZone( int * pbNorth ) const

{
    const char *pszProjection = GetAttrValue( "PROJECTION" );

    if( pszProjection == nullptr
        || !EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR) )
        return 0;

    if( GetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, 0.0 ) != 0.0 )
        return 0;

    if( GetProjParm( SRS_PP_SCALE_FACTOR, 1.0 ) != 0.9996 )
        return 0;

    if( fabs(GetNormProjParm( SRS_PP_FALSE_EASTING, 0.0 )-500000.0) > 0.001 )
        return 0;

    const double dfFalseNorthing = GetNormProjParm( SRS_PP_FALSE_NORTHING, 0.0);

    if( dfFalseNorthing != 0.0
        && fabs(dfFalseNorthing-10000000.0) > 0.001 )
        return 0;

    if( pbNorth != nullptr )
        *pbNorth = (dfFalseNorthing == 0);

    const double dfCentralMeridian =
        GetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, 0.0);
    const double dfZone = (dfCentralMeridian + 186.0) / 6.0;

    if( dfCentralMeridian < -177.00001 ||
        dfCentralMeridian > 177.000001 ||
        CPLIsNan(dfZone) ||
        std::abs(dfZone - static_cast<int>(dfZone) - 0.5 ) > 0.00001 )
      return 0;

    return static_cast<int>(dfZone);
}

/************************************************************************/
/*                           OSRGetUTMZone()                            */
/************************************************************************/

/**
 * \brief Get utm zone information.
 *
 * This is the same as the C++ method OGRSpatialReference::GetUTMZone()
 */
int OSRGetUTMZone( OGRSpatialReferenceH hSRS, int *pbNorth )

{
    VALIDATE_POINTER1( hSRS, "OSRGetUTMZone", 0 );

    return ToPointer(hSRS)->GetUTMZone( pbNorth );
}

/************************************************************************/
/*                             SetWagner()                              */
/************************************************************************/

OGRErr OGRSpatialReference::SetWagner( int nVariation,  // 1--7.
                                       double dfCenterLat,
                                       double dfFalseEasting,
                                       double dfFalseNorthing )

{
    if( nVariation == 1 )
        SetProjection( SRS_PT_WAGNER_I );
    else if( nVariation == 2 )
        SetProjection( SRS_PT_WAGNER_II );
    else if( nVariation == 3 )
    {
        SetProjection( SRS_PT_WAGNER_III );
        SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    }
    else if( nVariation == 4 )
        SetProjection( SRS_PT_WAGNER_IV );
    else if( nVariation == 5 )
        SetProjection( SRS_PT_WAGNER_V );
    else if( nVariation == 6 )
        SetProjection( SRS_PT_WAGNER_VI );
    else if( nVariation == 7 )
        SetProjection( SRS_PT_WAGNER_VII );
    else
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Unsupported Wagner variation (%d).", nVariation );
        return OGRERR_UNSUPPORTED_SRS;
    }

    SetNormProjParm( SRS_PP_FALSE_EASTING, dfFalseEasting );
    SetNormProjParm( SRS_PP_FALSE_NORTHING, dfFalseNorthing );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRSetWagner()                            */
/************************************************************************/

OGRErr OSRSetWagner( OGRSpatialReferenceH hSRS,
                     int nVariation, double dfCenterLat,
                     double dfFalseEasting,
                     double dfFalseNorthing )

{
    VALIDATE_POINTER1( hSRS, "OSRSetWagner", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetWagner(
        nVariation, dfCenterLat, dfFalseEasting, dfFalseNorthing );
}

/************************************************************************/
/*                            SetQSC()                     */
/************************************************************************/

OGRErr OGRSpatialReference::SetQSC( double dfCenterLat, double dfCenterLong )

{
    SetProjection( SRS_PT_QSC );
    SetNormProjParm( SRS_PP_LATITUDE_OF_ORIGIN, dfCenterLat );
    SetNormProjParm( SRS_PP_CENTRAL_MERIDIAN, dfCenterLong );

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetQSC()                   */
/************************************************************************/

OGRErr OSRSetQSC( OGRSpatialReferenceH hSRS,
                       double dfCenterLat, double dfCenterLong )

{
    VALIDATE_POINTER1( hSRS, "OSRSetQSC", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetQSC(
        dfCenterLat, dfCenterLong );
}

/************************************************************************/
/*                            SetSCH()                     */
/************************************************************************/

OGRErr OGRSpatialReference::SetSCH( double dfPegLat, double dfPegLong,
                                    double dfPegHeading, double dfPegHgt)

{
    SetProjection( SRS_PT_SCH );
    SetNormProjParm( SRS_PP_PEG_POINT_LATITUDE, dfPegLat );
    SetNormProjParm( SRS_PP_PEG_POINT_LONGITUDE, dfPegLong );
    SetNormProjParm( SRS_PP_PEG_POINT_HEADING, dfPegHeading );
    SetNormProjParm( SRS_PP_PEG_POINT_HEIGHT, dfPegHgt);

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetSCH()                   */
/************************************************************************/

OGRErr OSRSetSCH( OGRSpatialReferenceH hSRS,
                       double dfPegLat, double dfPegLong,
                       double dfPegHeading, double dfPegHgt)

{
    VALIDATE_POINTER1( hSRS, "OSRSetSCH", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetSCH(
        dfPegLat, dfPegLong, dfPegHeading, dfPegHgt );
}

/************************************************************************/
/*                            SetAuthority()                            */
/************************************************************************/

/**
 * \brief Set the authority for a node.
 *
 * This method is the same as the C function OSRSetAuthority().
 *
 * @param pszTargetKey the partial or complete path to the node to
 * set an authority on.  i.e. "PROJCS", "GEOGCS" or "GEOGCS|UNIT".
 *
 * @param pszAuthority authority name, such as "EPSG".
 *
 * @param nCode code for value with this authority.
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetAuthority( const char *pszTargetKey,
                                          const char * pszAuthority,
                                          int nCode )

{
/* -------------------------------------------------------------------- */
/*      Find the node below which the authority should be put.          */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poNode = GetAttrNode( pszTargetKey );

    if( poNode == nullptr )
        return OGRERR_FAILURE;

/* -------------------------------------------------------------------- */
/*      If there is an existing AUTHORITY child blow it away before     */
/*      trying to set a new one.                                        */
/* -------------------------------------------------------------------- */
    int iOldChild = poNode->FindChild( "AUTHORITY" );
    if( iOldChild != -1 )
        poNode->DestroyChild( iOldChild );

/* -------------------------------------------------------------------- */
/*      Create a new authority node.                                    */
/* -------------------------------------------------------------------- */
    char szCode[32] = {};

    snprintf( szCode, sizeof(szCode), "%d", nCode );

    OGR_SRSNode *poAuthNode = new OGR_SRSNode( "AUTHORITY" );
    poAuthNode->AddChild( new OGR_SRSNode( pszAuthority ) );
    poAuthNode->AddChild( new OGR_SRSNode( szCode ) );

    poNode->AddChild( poAuthNode );

    return OGRERR_NONE;
}

/************************************************************************/
/*                          OSRSetAuthority()                           */
/************************************************************************/

/**
 * \brief Set the authority for a node.
 *
 * This function is the same as OGRSpatialReference::SetAuthority().
 */
OGRErr OSRSetAuthority( OGRSpatialReferenceH hSRS,
                        const char *pszTargetKey,
                        const char * pszAuthority,
                        int nCode )

{
    VALIDATE_POINTER1( hSRS, "OSRSetAuthority", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetAuthority( pszTargetKey,
                                                         pszAuthority,
                                                         nCode );
}

/************************************************************************/
/*                          GetAuthorityCode()                          */
/************************************************************************/

/**
 * \brief Get the authority code for a node.
 *
 * This method is used to query an AUTHORITY[] node from within the
 * WKT tree, and fetch the code value.
 *
 * While in theory values may be non-numeric, for the EPSG authority all
 * code values should be integral.
 *
 * This method is the same as the C function OSRGetAuthorityCode().
 *
 * @param pszTargetKey the partial or complete path to the node to
 * get an authority from.  i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT" or NULL to
 * search for an authority node on the root element.
 *
 * @return value code from authority node, or NULL on failure.  The value
 * returned is internal and should not be freed or modified.
 */

const char *
OGRSpatialReference::GetAuthorityCode( const char *pszTargetKey ) const

{
/* -------------------------------------------------------------------- */
/*      Find the node below which the authority should be put.          */
/* -------------------------------------------------------------------- */
    const OGR_SRSNode *poNode = pszTargetKey == nullptr
        ? poRoot
        : GetAttrNode( pszTargetKey );

    if( poNode == nullptr )
        return nullptr;

/* -------------------------------------------------------------------- */
/*      Fetch AUTHORITY child if there is one.                          */
/* -------------------------------------------------------------------- */
    if( poNode->FindChild("AUTHORITY") == -1 )
        return nullptr;

    poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));

/* -------------------------------------------------------------------- */
/*      Create a new authority node.                                    */
/* -------------------------------------------------------------------- */
    if( poNode->GetChildCount() < 2 )
        return nullptr;

    return poNode->GetChild(1)->GetValue();
}

/************************************************************************/
/*                          OSRGetAuthorityCode()                       */
/************************************************************************/

/**
 * \brief Get the authority code for a node.
 *
 * This function is the same as OGRSpatialReference::GetAuthorityCode().
 */
const char *OSRGetAuthorityCode( OGRSpatialReferenceH hSRS,
                                 const char *pszTargetKey )

{
    VALIDATE_POINTER1( hSRS, "OSRGetAuthorityCode", nullptr );

    return ToPointer(hSRS)->
        GetAuthorityCode( pszTargetKey );
}

/************************************************************************/
/*                          GetAuthorityName()                          */
/************************************************************************/

/**
 * \brief Get the authority name for a node.
 *
 * This method is used to query an AUTHORITY[] node from within the
 * WKT tree, and fetch the authority name value.
 *
 * The most common authority is "EPSG".
 *
 * This method is the same as the C function OSRGetAuthorityName().
 *
 * @param pszTargetKey the partial or complete path to the node to
 * get an authority from.  i.e. "PROJCS", "GEOGCS", "GEOGCS|UNIT" or NULL to
 * search for an authority node on the root element.
 *
 * @return value code from authority node, or NULL on failure. The value
 * returned is internal and should not be freed or modified.
 */

const char *
OGRSpatialReference::GetAuthorityName( const char *pszTargetKey ) const

{
/* -------------------------------------------------------------------- */
/*      Find the node below which the authority should be put.          */
/* -------------------------------------------------------------------- */
    const OGR_SRSNode *poNode = nullptr;

    if( pszTargetKey == nullptr )
        poNode = poRoot;
    else
        poNode = GetAttrNode( pszTargetKey );

    if( poNode == nullptr )
        return nullptr;

/* -------------------------------------------------------------------- */
/*      Fetch AUTHORITY child if there is one.                          */
/* -------------------------------------------------------------------- */
    if( poNode->FindChild("AUTHORITY") == -1 )
        return nullptr;

    poNode = poNode->GetChild(poNode->FindChild("AUTHORITY"));

/* -------------------------------------------------------------------- */
/*      Create a new authority node.                                    */
/* -------------------------------------------------------------------- */
    if( poNode->GetChildCount() < 2 )
        return nullptr;

    return poNode->GetChild(0)->GetValue();
}

/************************************************************************/
/*                        OSRGetAuthorityName()                         */
/************************************************************************/

/**
 * \brief Get the authority name for a node.
 *
 * This function is the same as OGRSpatialReference::GetAuthorityName().
 */
const char *OSRGetAuthorityName( OGRSpatialReferenceH hSRS,
                                 const char *pszTargetKey )

{
    VALIDATE_POINTER1( hSRS, "OSRGetAuthorityName", nullptr );

    return ToPointer(hSRS)->
        GetAuthorityName( pszTargetKey );
}

/************************************************************************/
/*                           StripVertical()                            */
/************************************************************************/

/**
 * \brief Convert a compound cs into a horizontal CS.
 *
 * If this SRS is of type COMPD_CS[] then the vertical CS and the root COMPD_CS
 * nodes are stripped resulting and only the horizontal coordinate system
 * portion remains (normally PROJCS, GEOGCS or LOCAL_CS).
 *
 * If this is not a compound coordinate system then nothing is changed.
 *
 * @since OGR 1.8.0
 */

OGRErr OGRSpatialReference::StripVertical()

{
    if( GetRoot() == nullptr
        || !EQUAL(GetRoot()->GetValue(), "COMPD_CS") )
        return OGRERR_NONE;

    OGR_SRSNode *poHorizontalCS = GetRoot()->GetChild( 1 );
    if( poHorizontalCS != nullptr )
        poHorizontalCS = poHorizontalCS->Clone();
    SetRoot( poHorizontalCS );

    return OGRERR_NONE;
}

/************************************************************************/
/*                            StripCTParms()                            */
/************************************************************************/

/**
 * \brief Strip OGC CT Parameters.
 *
 * This method will remove all components of the coordinate system
 * that are specific to the OGC CT Specification.  That is it will attempt
 * to strip it down to being compatible with the Simple Features 1.0
 * specification.
 *
 * This method is the same as the C function OSRStripCTParms().
 *
 * @param poCurrent node to operate on.  NULL to operate on whole tree.
 *
 * @return OGRERR_NONE on success or an error code.
 */

OGRErr OGRSpatialReference::StripCTParms( OGR_SRSNode * poCurrent )

{
    if( poCurrent == nullptr )
    {
        StripVertical();
        poCurrent = GetRoot();
    }

    if( poCurrent == nullptr )
        return OGRERR_NONE;

    if( poCurrent == GetRoot() && EQUAL(poCurrent->GetValue(), "LOCAL_CS") )
    {
        delete poCurrent;
        poRoot = nullptr;

        return OGRERR_NONE;
    }

    poCurrent->StripNodes( "AUTHORITY" );
    poCurrent->StripNodes( "TOWGS84" );
    poCurrent->StripNodes( "AXIS" );
    poCurrent->StripNodes( "EXTENSION" );

    return OGRERR_NONE;
}

/************************************************************************/
/*                          OSRStripCTParms()                           */
/************************************************************************/

/**
 * \brief Strip OGC CT Parameters.
 *
 * This function is the same as OGRSpatialReference::StripCTParms().
 */
OGRErr OSRStripCTParms( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRStripCTParms", OGRERR_FAILURE );

    return ToPointer(hSRS)->StripCTParms( nullptr );
}

/************************************************************************/
/*                             IsCompound()                             */
/************************************************************************/

/**
 * \brief Check if coordinate system is compound.
 *
 * This method is the same as the C function OSRIsCompound().
 *
 * @return TRUE if this is rooted with a COMPD_CS node.
 */

int OGRSpatialReference::IsCompound() const

{
    if( poRoot == nullptr )
        return FALSE;

    return EQUAL(poRoot->GetValue(), "COMPD_CS");
}

/************************************************************************/
/*                           OSRIsCompound()                            */
/************************************************************************/

/**
 * \brief Check if the coordinate system is compound.
 *
 * This function is the same as OGRSpatialReference::IsCompound().
 */
int OSRIsCompound( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRIsCompound", 0 );

    return ToPointer(hSRS)->IsCompound();
}

/************************************************************************/
/*                            IsProjected()                             */
/************************************************************************/

/**
 * \brief Check if projected coordinate system.
 *
 * This method is the same as the C function OSRIsProjected().
 *
 * @return TRUE if this contains a PROJCS node indicating a it is a
 * projected coordinate system.
 */

int OGRSpatialReference::IsProjected() const

{
    if( poRoot == nullptr )
        return FALSE;


    if( EQUAL(poRoot->GetValue(), "PROJCS") )
        return TRUE;
    else if( EQUAL(poRoot->GetValue(), "COMPD_CS") )
        return GetAttrNode( "PROJCS" ) != nullptr;
    else
        return FALSE;
}

/************************************************************************/
/*                           OSRIsProjected()                           */
/************************************************************************/
/**
 * \brief Check if projected coordinate system.
 *
 * This function is the same as OGRSpatialReference::IsProjected().
 */
int OSRIsProjected( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRIsProjected", 0 );

    return ToPointer(hSRS)->IsProjected();
}

/************************************************************************/
/*                            IsGeocentric()                            */
/************************************************************************/

/**
 * \brief Check if geocentric coordinate system.
 *
 * This method is the same as the C function OSRIsGeocentric().
 *
 * @return TRUE if this contains a GEOCCS node indicating a it is a
 * geocentric coordinate system.
 *
 * @since OGR 1.9.0
 */

int OGRSpatialReference::IsGeocentric() const

{
    if( poRoot == nullptr )
        return FALSE;

    if( EQUAL(poRoot->GetValue(), "GEOCCS") )
        return TRUE;

    return FALSE;
}

/************************************************************************/
/*                           OSRIsGeocentric()                          */
/************************************************************************/
/**
 * \brief Check if geocentric coordinate system.
 *
 * This function is the same as OGRSpatialReference::IsGeocentric().
 *
 * @since OGR 1.9.0
 */
int OSRIsGeocentric( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRIsGeocentric", 0 );

    return ToPointer(hSRS)->IsGeocentric();
}

/************************************************************************/
/*                            IsGeographic()                            */
/************************************************************************/

/**
 * \brief Check if geographic coordinate system.
 *
 * This method is the same as the C function OSRIsGeographic().
 *
 * @return TRUE if this spatial reference is geographic ... that is the
 * root is a GEOGCS node.
 */

int OGRSpatialReference::IsGeographic() const

{
    if( GetRoot() == nullptr )
        return FALSE;

    if( EQUAL(poRoot->GetValue(), "GEOGCS") )
        return TRUE;

    if( EQUAL(poRoot->GetValue(), "COMPD_CS") )
        return GetAttrNode( "GEOGCS" ) != nullptr
            && GetAttrNode( "PROJCS" ) == nullptr;

    return FALSE;
}

/************************************************************************/
/*                          OSRIsGeographic()                           */
/************************************************************************/
/**
 * \brief Check if geographic coordinate system.
 *
 * This function is the same as OGRSpatialReference::IsGeographic().
 */
int OSRIsGeographic( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRIsGeographic", 0 );

    return ToPointer(hSRS)->IsGeographic();
}

/************************************************************************/
/*                              IsLocal()                               */
/************************************************************************/

/**
 * \brief Check if local coordinate system.
 *
 * This method is the same as the C function OSRIsLocal().
 *
 * @return TRUE if this spatial reference is local ... that is the
 * root is a LOCAL_CS node.
 */

int OGRSpatialReference::IsLocal() const

{
    if( GetRoot() == nullptr )
        return FALSE;

    return EQUAL(GetRoot()->GetValue(), "LOCAL_CS");
}

/************************************************************************/
/*                          OSRIsLocal()                                */
/************************************************************************/
/**
 * \brief Check if local coordinate system.
 *
 * This function is the same as OGRSpatialReference::IsLocal().
 */
int OSRIsLocal( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRIsLocal", 0 );

    return ToPointer(hSRS)->IsLocal();
}

/************************************************************************/
/*                            IsVertical()                              */
/************************************************************************/

/**
 * \brief Check if vertical coordinate system.
 *
 * This method is the same as the C function OSRIsVertical().
 *
 * @return TRUE if this contains a VERT_CS node indicating a it is a
 * vertical coordinate system.
 *
 * @since OGR 1.8.0
 */

int OGRSpatialReference::IsVertical() const

{
    if( poRoot == nullptr )
        return FALSE;

    if( EQUAL(poRoot->GetValue(), "VERT_CS") )
        return TRUE;

    if( EQUAL(poRoot->GetValue(), "COMPD_CS") )
        return GetAttrNode( "VERT_CS" ) != nullptr;

    return FALSE;
}

/************************************************************************/
/*                           OSRIsVertical()                            */
/************************************************************************/
/**
 * \brief Check if vertical coordinate system.
 *
 * This function is the same as OGRSpatialReference::IsVertical().
 *
 * @since OGR 1.8.0
 */
int OSRIsVertical( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRIsVertical", 0 );

    return ToPointer(hSRS)->IsVertical();
}

/************************************************************************/
/*                            CloneGeogCS()                             */
/************************************************************************/

/**
 * \brief Make a duplicate of the GEOGCS node of this OGRSpatialReference object.
 *
 * @return a new SRS, which becomes the responsibility of the caller.
 */
OGRSpatialReference *OGRSpatialReference::CloneGeogCS() const

{
    OGRSpatialReference * poNewSRS = nullptr;

/* -------------------------------------------------------------------- */
/*      We have to reconstruct the GEOGCS node for geocentric           */
/*      coordinate systems.                                             */
/* -------------------------------------------------------------------- */
    if( IsGeocentric() )
    {
        const OGR_SRSNode *poDatum = GetAttrNode( "DATUM" );
        const OGR_SRSNode *poPRIMEM = GetAttrNode( "PRIMEM" );
        OGR_SRSNode *poGeogCS = nullptr;

        if( poDatum == nullptr || poPRIMEM == nullptr )
            return nullptr;

        poGeogCS = new OGR_SRSNode( "GEOGCS" );
        poGeogCS->AddChild( new OGR_SRSNode( "unnamed" ) );
        poGeogCS->AddChild( poDatum->Clone() );
        poGeogCS->AddChild( poPRIMEM->Clone() );

        poNewSRS = new OGRSpatialReference();
        poNewSRS->SetRoot( poGeogCS );

        poNewSRS->SetAngularUnits( "degree", CPLAtof(SRS_UA_DEGREE_CONV) );

        return poNewSRS;
    }

/* -------------------------------------------------------------------- */
/*      For all others we just search the tree, and duplicate.          */
/* -------------------------------------------------------------------- */
    const OGR_SRSNode *poGeogCS = GetAttrNode( "GEOGCS" );
    if( poGeogCS == nullptr )
        return nullptr;

    poNewSRS = new OGRSpatialReference();
    poNewSRS->SetRoot( poGeogCS->Clone() );

    return poNewSRS;
}

/************************************************************************/
/*                           OSRCloneGeogCS()                           */
/************************************************************************/
/**
 * \brief Make a duplicate of the GEOGCS node of this OGRSpatialReference object.
 *
 * This function is the same as OGRSpatialReference::CloneGeogCS().
 */
OGRSpatialReferenceH CPL_STDCALL OSRCloneGeogCS( OGRSpatialReferenceH hSource )

{
    VALIDATE_POINTER1( hSource, "OSRCloneGeogCS", nullptr );

    return ToHandle(
        ToPointer(hSource)->CloneGeogCS() );
}

/************************************************************************/
/*                      IsRelativeErrorSmaller()                        */
/************************************************************************/

static bool IsRelativeErrorSmaller(double dfA, double dfB, double dfRelError)
{
    return fabs(dfA - dfB) <= dfRelError * fabs(dfA);
}

/************************************************************************/
/*                            IsSameGeogCS()                            */
/************************************************************************/

/**
 * \brief Do the GeogCS'es match?
 *
 * This method is the same as the C function OSRIsSameGeogCS().
 *
 * @param poOther the SRS being compared against.
 *
 * @return TRUE if they are the same or FALSE otherwise.
 */

int OGRSpatialReference::IsSameGeogCS( const OGRSpatialReference *poOther ) const

{
    return IsSameGeogCS( poOther, nullptr );
}

/**
 * \brief Do the GeogCS'es match?
 *
 * This method is the same as the C function OSRIsSameGeogCS().
 *
 * @param poOther the SRS being compared against.
 * @param papszOptions options. DATUM=STRICT/IGNORE. TOWGS84=STRICT/ONLY_IF_IN_BOTH/IGNORE
 *
 * @return TRUE if they are the same or FALSE otherwise.
 */

int OGRSpatialReference::IsSameGeogCS( const OGRSpatialReference *poOther,
                                       const char* const * papszOptions ) const

{

    const char *pszThisValue, *pszOtherValue;

/* -------------------------------------------------------------------- */
/*      Does the datum name match?  Note that we assume                 */
/*      compatibility if either is missing a datum.                     */
/* -------------------------------------------------------------------- */
    pszThisValue = this->GetAttrValue( "DATUM" );
    pszOtherValue = poOther->GetAttrValue( "DATUM" );

    const char* pszDatumRule =
        CSLFetchNameValueDef( papszOptions, "DATUM", "STRICT");
    if( pszThisValue != nullptr && pszOtherValue != nullptr
        && !EQUAL(pszThisValue, pszOtherValue)
        && EQUAL(pszDatumRule, "STRICT") )
    {
#if DEBUG_VERBOSE
        CPLDebug("OSR", "DATUM names do not match");
#endif
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Do the datum TOWGS84 values match if present?                   */
/* -------------------------------------------------------------------- */
    const char* pszTOWGS84Rule =
        CSLFetchNameValueDef( papszOptions, "TOWGS84", "STRICT" );
    double adfTOWGS84[7] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    double adfOtherTOWGS84[7] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

    bool bThisHasTOWGS84 = this->GetTOWGS84( adfTOWGS84, 7 ) == OGRERR_NONE;
    bool bOtherHasTOWGS84 = poOther->GetTOWGS84( adfOtherTOWGS84, 7 ) == OGRERR_NONE;
    if( EQUAL(pszTOWGS84Rule, "STRICT" ) ||
        (bThisHasTOWGS84 && bOtherHasTOWGS84 &&
         EQUAL(pszTOWGS84Rule, "ONLY_IF_IN_BOTH")) )
    {
        for( int i = 0; i < 7; i++ )
        {
            if( fabs(adfTOWGS84[i] - adfOtherTOWGS84[i]) > 0.00001 )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("OSR", "TOWGS84 do not match");
#endif
                return FALSE;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*    Do the prime meridians match?  If missing assume a value of zero. */
/* -------------------------------------------------------------------- */
    pszThisValue = this->GetAttrValue( "PRIMEM", 1 );
    if( pszThisValue == nullptr )
        pszThisValue = "0.0";

    pszOtherValue = poOther->GetAttrValue( "PRIMEM", 1 );
    if( pszOtherValue == nullptr )
        pszOtherValue = "0.0";

    if( !IsRelativeErrorSmaller(CPLAtof(pszOtherValue),
                                CPLAtof(pszThisValue), 1e-8) )
    {
#ifdef DEBUG_VERBOSE
        CPLDebug("OSR", "PRIMEM do not match");
#endif
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Do the units match?                                             */
/* -------------------------------------------------------------------- */
    pszThisValue = this->GetAttrValue( "GEOGCS|UNIT", 1 );
    if( pszThisValue == nullptr )
        pszThisValue = SRS_UA_DEGREE_CONV;

    pszOtherValue = poOther->GetAttrValue( "GEOGCS|UNIT", 1 );
    if( pszOtherValue == nullptr )
        pszOtherValue = SRS_UA_DEGREE_CONV;

    if( !IsRelativeErrorSmaller(CPLAtof(pszOtherValue),
                                CPLAtof(pszThisValue), 1e-8) )
    {
#ifdef DEBUG_VERBOSE
        CPLDebug("OSR", "GEOGCS|UNIT do not match");
#endif
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Does the spheroid match.  Check semi major, and inverse         */
/*      flattening.                                                     */
/* -------------------------------------------------------------------- */
    pszThisValue = this->GetAttrValue( "SPHEROID", 1 );
    pszOtherValue = poOther->GetAttrValue( "SPHEROID", 1 );
    if( pszThisValue != nullptr && pszOtherValue != nullptr
        && std::abs(CPLAtof(pszThisValue) - CPLAtof(pszOtherValue)) > 0.01 )
    {
#ifdef DEBUG_VERBOSE
        CPLDebug("OSR", "SPHEROID major do not match");
#endif
        return FALSE;
    }

    pszThisValue = this->GetAttrValue( "SPHEROID", 2 );
    pszOtherValue = poOther->GetAttrValue( "SPHEROID", 2 );
    if( pszThisValue != nullptr && pszOtherValue != nullptr
        && std::abs(CPLAtof(pszThisValue) - CPLAtof(pszOtherValue)) > 0.0001 )
    {
#ifdef DEBUG_VERBOSE
        CPLDebug("OSR", "SPHEROID inverse flattening do not match");
#endif
        return FALSE;
    }

    return TRUE;
}

/************************************************************************/
/*                          OSRIsSameGeogCS()                           */
/************************************************************************/

/**
 * \brief Do the GeogCS'es match?
 *
 * This function is the same as OGRSpatialReference::IsSameGeogCS().
 */
int OSRIsSameGeogCS( OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2 )

{
    VALIDATE_POINTER1( hSRS1, "OSRIsSameGeogCS", 0 );
    VALIDATE_POINTER1( hSRS2, "OSRIsSameGeogCS", 0 );

    return ToPointer(hSRS1)->IsSameGeogCS(
        ToPointer(hSRS2) );
}

/************************************************************************/
/*                            IsSameVertCS()                            */
/************************************************************************/

/**
 * \brief Do the VertCS'es match?
 *
 * This method is the same as the C function OSRIsSameVertCS().
 *
 * @param poOther the SRS being compared against.
 *
 * @return TRUE if they are the same or FALSE otherwise.
 */

int OGRSpatialReference::IsSameVertCS( const OGRSpatialReference *poOther ) const

{
/* -------------------------------------------------------------------- */
/*      Does the datum name match?                                      */
/* -------------------------------------------------------------------- */
    const char *pszThisValue = this->GetAttrValue( "VERT_DATUM" );
    const char *pszOtherValue = poOther->GetAttrValue( "VERT_DATUM" );

    if( pszThisValue == nullptr || pszOtherValue == nullptr
        || !EQUAL(pszThisValue, pszOtherValue) )
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Do the units match?                                             */
/* -------------------------------------------------------------------- */
    pszThisValue = this->GetAttrValue( "VERT_CS|UNIT", 1 );
    if( pszThisValue == nullptr )
        pszThisValue = "1.0";

    pszOtherValue = poOther->GetAttrValue( "VERT_CS|UNIT", 1 );
    if( pszOtherValue == nullptr )
        pszOtherValue = "1.0";

    if( std::abs(CPLAtof(pszOtherValue) - CPLAtof(pszThisValue)) > 0.00000001 )
        return FALSE;

    return TRUE;
}

/************************************************************************/
/*                          OSRIsSameVertCS()                           */
/************************************************************************/

/**
 * \brief Do the VertCS'es match?
 *
 * This function is the same as OGRSpatialReference::IsSameVertCS().
 */
int OSRIsSameVertCS( OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2 )

{
    VALIDATE_POINTER1( hSRS1, "OSRIsSameVertCS", 0 );
    VALIDATE_POINTER1( hSRS2, "OSRIsSameVertCS", 0 );

    return ToPointer(hSRS1)->IsSameVertCS(
        ToPointer(hSRS2) );
}

/************************************************************************/
/*                        IsDefaultParameter()                          */
/************************************************************************/

static bool IsDefaultParameter(const char* pszParamName, double dfVal)
{
    if( STARTS_WITH_CI(pszParamName, "scale") )
        return dfVal == 1.0;
    else
        return dfVal == 0.0;
}

/************************************************************************/
/*                               IsSame()                               */
/************************************************************************/

/**
 * \brief Do these two spatial references describe the same system ?
 *
 * @param poOtherSRS the SRS being compared to.
 *
 * @return TRUE if equivalent or FALSE otherwise.
 */

int OGRSpatialReference::IsSame( const OGRSpatialReference * poOtherSRS ) const

{
    return IsSame(poOtherSRS, nullptr);
}


/**
 * \brief Do these two spatial references describe the same system ?
 *
 * @param poOtherSRS the SRS being compared to.
 * @param papszOptions options. DATUM=STRICT/IGNORE. TOWGS84=STRICT/ONLY_IF_IN_BOTH/IGNORE
 *
 * @return TRUE if equivalent or FALSE otherwise.
 */

int OGRSpatialReference::IsSame( const OGRSpatialReference * poOtherSRS,
                                 const char* const * papszOptions ) const

{
    if( GetRoot() == nullptr && poOtherSRS->GetRoot() == nullptr )
        return TRUE;

    if( GetRoot() == nullptr || poOtherSRS->GetRoot() == nullptr )
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Do the have the same root types?  i.e. is one PROJCS and one    */
/*      GEOGCS or perhaps LOCALCS?                                      */
/* -------------------------------------------------------------------- */
    if( !EQUAL(GetRoot()->GetValue(), poOtherSRS->GetRoot()->GetValue()) )
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Compare proj.4 extensions.                                      */
/* -------------------------------------------------------------------- */
    const char* pszThisProj4Ext = GetExtension(nullptr, "PROJ4", nullptr);
    const char* pszOtherProj4Ext = poOtherSRS->GetExtension(nullptr, "PROJ4", nullptr);
    if( (pszThisProj4Ext == nullptr && pszOtherProj4Ext != nullptr) ||
        (pszThisProj4Ext != nullptr && pszOtherProj4Ext == nullptr) ||
        (pszThisProj4Ext != nullptr && pszOtherProj4Ext != nullptr &&
         !EQUAL(CPLString(pszThisProj4Ext).Trim().replaceAll("  "," "),
                CPLString(pszOtherProj4Ext).Trim().replaceAll("  "," "))) )
    {
#ifdef DEBUG_VERBOSE
        CPLDebug("OSR", "Different EXTENSION");
#endif
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Compare geographic coordinate system.                           */
/* -------------------------------------------------------------------- */
    if( !IsSameGeogCS( poOtherSRS, papszOptions ) )
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Compare projected coordinate system.                            */
/* -------------------------------------------------------------------- */
    const OGR_SRSNode *poPROJCS = GetAttrNode( "PROJCS" );
    if( poPROJCS != nullptr )
    {
        const char *pszValue1 = this->GetAttrValue( "PROJECTION" );
        const char *pszValue2 = poOtherSRS->GetAttrValue( "PROJECTION" );

        if( pszValue1 == nullptr || pszValue2 == nullptr )
        {
#ifdef DEBUG_VERBOSE
            CPLDebug("OSR", "Different PROJECTION");
#endif
            return FALSE;
        }

        if( !EQUAL(pszValue1, pszValue2) )
        {
            OGRSpatialReference* poThisInOtherProj =
                convertToOtherProjection(pszValue2);
            if( poThisInOtherProj )
            {
                int bRet = poThisInOtherProj->IsSame(poOtherSRS);
                delete poThisInOtherProj;
                return bRet;
            }
            else
            {
                OGRSpatialReference* poOtherSRSInThisProj =
                    poOtherSRS->convertToOtherProjection(pszValue1);
                if( poOtherSRSInThisProj )
                {
                    int bRet = IsSame(poOtherSRSInThisProj);
                    delete poOtherSRSInThisProj;
                    return bRet;
                }
            }

#ifdef DEBUG_VERBOSE
            CPLDebug("OSR", "Different PROJECTION");
#endif
            return FALSE;
        }

        bool bIgnoreStdParallel12 = false;
        bool bIgnoreRectifiedGridAngle = false;
        if( EQUAL(pszValue1, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP) )
        {
            double dfThis1 = GetProjParm(SRS_PP_STANDARD_PARALLEL_1);
            double dfOther1 = poOtherSRS->GetProjParm(SRS_PP_STANDARD_PARALLEL_1);
            double dfThis2 = GetProjParm(SRS_PP_STANDARD_PARALLEL_2);
            double dfOther2 = poOtherSRS->GetProjParm(SRS_PP_STANDARD_PARALLEL_2);
            if( !((IsRelativeErrorSmaller(dfThis1, dfOther1, 1e-8) &&
                   IsRelativeErrorSmaller(dfThis2, dfOther2, 1e-8)) ||
                  (IsRelativeErrorSmaller(dfThis1, dfOther2, 1e-8) &&
                   IsRelativeErrorSmaller(dfThis2, dfOther1, 1e-8))) )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("OSR", "Relative error for StdParallel1/2 too big");
#endif
                return FALSE;
            }
            bIgnoreStdParallel12 = true;
        }
        else if( EQUAL(pszValue1, SRS_PT_HOTINE_OBLIQUE_MERCATOR) )
        {
            double dfAz = fmod(GetProjParm(SRS_PP_AZIMUTH) + 360.0, 360.0);
            double dfRectToSkew = fmod(GetProjParm(SRS_PP_RECTIFIED_GRID_ANGLE, dfAz) + 360.0, 360.0);
            double dfAz2 = fmod(poOtherSRS->GetProjParm(SRS_PP_AZIMUTH) + 360.0, 360.0 );
            double dfRectToSkew2 = fmod(poOtherSRS->GetProjParm(SRS_PP_RECTIFIED_GRID_ANGLE, dfAz2) + 360.0, 360.0);
            if( !IsRelativeErrorSmaller(dfRectToSkew, dfRectToSkew2, 1e-8) )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("OSR", "Relative error for rectified_grid_angle too big");
#endif
                return FALSE;
            }
            bIgnoreRectifiedGridAngle = true;
        }

        int nCountNonDefaultParameters = 0;
        for( int iChild = 0; iChild < poPROJCS->GetChildCount(); iChild++ )
        {
            const OGR_SRSNode *poNode = poPROJCS->GetChild( iChild );
            if( !EQUAL(poNode->GetValue(), "PARAMETER")
                || poNode->GetChildCount() != 2 )
                continue;

            const char* pszParamName = poNode->GetChild(0)->GetValue();
            if( bIgnoreStdParallel12 &&
                (EQUAL( pszParamName, SRS_PP_STANDARD_PARALLEL_1 ) ||
                 EQUAL( pszParamName, SRS_PP_STANDARD_PARALLEL_2 )) )
            {
                continue;
            }

            if( bIgnoreRectifiedGridAngle &&
                EQUAL( pszParamName, SRS_PP_RECTIFIED_GRID_ANGLE ) )
            {
                continue;
            }

            double dfVal = GetProjParm( pszParamName );
            if( !IsDefaultParameter(pszParamName, dfVal) )
            {
                nCountNonDefaultParameters ++;
            }

            double dfOtherVal = poOtherSRS->GetProjParm( pszParamName,
                STARTS_WITH_CI(pszParamName, "Scale") ? 1.0 : 0.0 );

            if( EQUAL(pszParamName, SRS_PP_AZIMUTH) )
            {
                dfVal = fmod(dfVal + 360.0, 360.0);
                dfOtherVal = fmod(dfOtherVal + 360.0, 360.0);
            }

            if( !IsRelativeErrorSmaller(dfVal, dfOtherVal, 1e-8) )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("OSR", "Relative error for %s too big",
                         pszParamName);
#endif
                return FALSE;
            }
        }

        const OGR_SRSNode *poOtherPROJCS = poOtherSRS->GetAttrNode( "PROJCS" );
        if( poOtherPROJCS == nullptr )
        {
            return FALSE;
        }
        int nCounterOtherNonDefaultParameters = 0;
        for( int iChild = 0; iChild < poOtherPROJCS->GetChildCount(); iChild++ )
        {
            const OGR_SRSNode *poNode = poOtherPROJCS->GetChild( iChild );
            if( !EQUAL(poNode->GetValue(), "PARAMETER")
                || poNode->GetChildCount() != 2 )
                continue;

            const char* pszParamName = poNode->GetChild(0)->GetValue();
            if( bIgnoreStdParallel12 &&
                (EQUAL( pszParamName, SRS_PP_STANDARD_PARALLEL_1 ) ||
                 EQUAL( pszParamName, SRS_PP_STANDARD_PARALLEL_2 )) )
            {
                continue;
            }

            if( bIgnoreRectifiedGridAngle &&
                EQUAL( pszParamName, SRS_PP_RECTIFIED_GRID_ANGLE ) )
            {
                continue;
            }

            double dfVal = poOtherSRS->GetProjParm( pszParamName );
            if( !IsDefaultParameter(pszParamName, dfVal) )
            {
                nCounterOtherNonDefaultParameters ++;
            }
        }

        if( nCountNonDefaultParameters != nCounterOtherNonDefaultParameters )
        {
#ifdef DEBUG_VERBOSE
            CPLDebug("OSR", "Different number of non default PARAMETER");
#endif
            return FALSE;
        }
    }

/* -------------------------------------------------------------------- */
/*      If they are LOCALCS/PROJCS, do they have the same units?        */
/* -------------------------------------------------------------------- */
    if( IsLocal() || IsProjected() )
    {
        if( GetLinearUnits() != 0.0 )
        {
            // EPSG uses 0.201166195164 for Clarke's link, ESRI 0.2011661949 --> 1.3e-9 relative error
            if( !IsRelativeErrorSmaller(poOtherSRS->GetLinearUnits(),
                                        GetLinearUnits(), 1e-8 ) )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("OSR", "Relative error for linear unit too big");
#endif
                return FALSE;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Compare vertical coordinate system.                             */
/* -------------------------------------------------------------------- */
    if( IsVertical() && !IsSameVertCS( poOtherSRS ) )
        return FALSE;

    return TRUE;
}

/************************************************************************/
/*                             OSRIsSame()                              */
/************************************************************************/

/**
 * \brief Do these two spatial references describe the same system ?
 *
 * This function is the same as OGRSpatialReference::IsSame().
 */
int OSRIsSame( OGRSpatialReferenceH hSRS1, OGRSpatialReferenceH hSRS2 )

{
    VALIDATE_POINTER1( hSRS1, "OSRIsSame", 0 );
    VALIDATE_POINTER1( hSRS2, "OSRIsSame", 0 );

    return ToPointer(hSRS1)->IsSame(
        ToPointer(hSRS2) );
}

/************************************************************************/
/*                               tsfn()                                 */
/************************************************************************/

static double tsfn(double phi, double ec)
{
    const double sinphi = sin(phi);
    const double sinphi_ec = sinphi * ec;
    return tan(0.5 * (M_PI/2 - phi)) /
               pow( (1.0 - sinphi_ec) / (1.0 + sinphi_ec), 0.5 * ec);
}

/************************************************************************/
/*                               msfn()                                 */
/************************************************************************/

static double msfn(double phi, double ec)
{
    const double sinphi = sin(phi);
    const double cosphi = cos(phi);
    const double sinphi_ec = sinphi * ec;
    return cosphi / sqrt(1.0 - sinphi_ec * sinphi_ec);
}

/************************************************************************/
/*                         lcc_1sp_to_2sp_f()                           */
/************************************************************************/

// Function whose zeroes are the sin of the standard parallels of LCC_2SP
static double lcc_1sp_to_2sp_f(double sinphi, double K, double ec, double n)
{
    const double x = sinphi;
    const double ecx = ec * x;
    return ( 1 - x* x ) / (1 - ecx * ecx) -
        K * K * pow((1.0 - x ) / (1.0 + x) *
                    pow( (1.0 + ecx)/(1.0 - ecx), ec), n);
}

/************************************************************************/
/*                    find_zero_lcc_1sp_to_2sp_f()                      */
/************************************************************************/

// Find the sin of the standard parallels of LCC_2SP
static double find_zero_lcc_1sp_to_2sp_f(double sinphi0, bool bNorth,
                                         double K, double ec)
{
    double a, b;
    double f_a;
    if( bNorth )
    {
        // Look for zero above phi0
        a = sinphi0;
        b = 1.0; // sin(North pole)
        f_a = 1.0; // some positive value, but we only care about the sign
    }
    else
    {
        // Look for zero below phi0
        a = -1.0; // sin(South pole)
        b = sinphi0;
        f_a = -1.0; // minus infinity in fact, but we only care about the sign
    }
    // We use dichotomy search. lcc_1sp_to_2sp_f() is positive at sinphi_init,
    // has a zero in ]-1,sinphi0[ and ]sinphi0,1[ ranges
    for( int N=0; N<100; N++ )
    {
        double c = (a + b) / 2;
        double f_c = lcc_1sp_to_2sp_f(c, K, ec, sinphi0);
        if ( f_c == 0.0 || (b-a) < 1e-18 )
        {
            return c;
        }
        if( (f_c > 0 && f_a > 0) || (f_c < 0 && f_a < 0) )
        {
            a = c;
            f_a = f_c;
        }
        else
        {
            b = c;
        }
    }
    return (a + b) / 2;
}

static double DegToRad(double x) { return x / 180.0 * M_PI; }
static double RadToDeg(double x) { return x / M_PI * 180.0; }

/************************************************************************/
/*                    convertToOtherProjection()                        */
/************************************************************************/

/**
 * \brief Convert to another equivalent projection
 * 
 * Currently implemented:
 * <ul>
 * <li>SRS_PT_MERCATOR_1SP to SRS_PT_MERCATOR_2SP</li>
 * <li>SRS_PT_MERCATOR_2SP to SRS_PT_MERCATOR_1SP</li>
 * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP to SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP</li>
 * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP to SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP</li>
 * </ul>
 *
 * @param pszTargetProjection target projection.
 * @param papszOptions lists of options. None supported currently.
 * @return a new SRS, or NULL in case of error.
 *
 * @since GDAL 2.3
 */
OGRSpatialReference* OGRSpatialReference::convertToOtherProjection(
                            const char* pszTargetProjection,
                            CPL_UNUSED const char* const* papszOptions ) const
{
    const char *pszProjection = GetAttrValue( "PROJECTION" );
    if( pszProjection == nullptr || pszTargetProjection == nullptr )
        return nullptr;

    if( EQUAL(pszProjection, pszTargetProjection) )
        return Clone();

    if( EQUAL(pszProjection, SRS_PT_MERCATOR_1SP) &&
        EQUAL(pszTargetProjection, SRS_PT_MERCATOR_2SP) &&
        GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0) == 0.0 )
    {
        const double k0 = GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        if( !(k0 > 0 && k0 <= 1.0+ 1e-10) )
            return nullptr;
        const double e2 = GetSquaredEccentricity();
        if( e2 < 0 )
            return nullptr;
        const double dfStdP1Lat = ( k0 >= 1.0 ) ? 0.0 :
            RadToDeg(acos( sqrt( (1.0 - e2) / ((1.0 / (k0 * k0)) - e2)) ));
        OGRSpatialReference* poMerc2SP = new OGRSpatialReference();
        poMerc2SP->CopyGeogCSFrom(this);
        poMerc2SP->SetMercator2SP(
                              dfStdP1Lat,
                              GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
                              GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
                              GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
                              GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0) );
        return poMerc2SP;
    }

    if( EQUAL(pszProjection, SRS_PT_MERCATOR_2SP) &&
        EQUAL(pszTargetProjection, SRS_PT_MERCATOR_1SP) &&
        GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0) == 0.0 )
    {
        const double dfStdP1Lat =
            GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
        const double phi1 = DegToRad(dfStdP1Lat);
        if( !(fabs(phi1) < M_PI / 2) )
            return nullptr;
        const double ec = GetEccentricity();
        if( ec < 0 )
            return nullptr;
        const double k0 = msfn(phi1, ec);
        OGRSpatialReference* poMerc1SP = new OGRSpatialReference();
        poMerc1SP->CopyGeogCSFrom(this);
        poMerc1SP->SetMercator(
                              GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
                              GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
                              k0,
                              GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
                              GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0) );
        return poMerc1SP;
    }

    if( EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP) &&
        EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP) )
    {
        // Notations m0, t0, n, m1, t1, F are those of the EPSG guidance
        // "1.3.1.1 Lambert Conic Conformal (2SP)" and
        // "1.3.1.2 Lambert Conic Conformal (1SP)" and
        // or Snyder pages 106-109
        const double dfLatitudeOfOrigin =
            GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        const double phi0 = DegToRad(dfLatitudeOfOrigin);
        const double k0 = GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        if( !(fabs(phi0) < M_PI / 2) )
            return nullptr;
        if( !(k0 > 0 && k0 <= 1.0+ 1e-10) )
            return nullptr;
        const double ec = GetEccentricity();
        if( ec < 0 )
            return nullptr;
        const double m0 = msfn(phi0, ec);
        const double t0 = tsfn(phi0, ec);
        const double n = sin(phi0);
        if( fabs(n) < 1e-10 )
            return nullptr;
        OGRSpatialReference* poLCC2SP = new OGRSpatialReference();
        poLCC2SP->CopyGeogCSFrom(this);
        if( fabs(k0 - 1.0) <= 1e-10 )
        {
            poLCC2SP->SetLCC( dfLatitudeOfOrigin,
                              dfLatitudeOfOrigin,
                              dfLatitudeOfOrigin,
                              GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
                              GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
                              GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0) );
        }
        else
        {
            const double K = k0 * m0 / pow(t0, n);
            const double phi1 =
                asin(find_zero_lcc_1sp_to_2sp_f(n, true, K, ec));
            const double phi2 =
                asin(find_zero_lcc_1sp_to_2sp_f(n, false, K, ec));
            double phi1Deg = RadToDeg(phi1);
            double phi2Deg = RadToDeg(phi2);

            // Try to round to hundreth of degree if very close to it
            if( fabs(phi1Deg * 1000 - floor(phi1Deg * 1000 + 0.5)) < 1e-8 )
                phi1Deg = floor(phi1Deg * 1000 + 0.5) / 1000;
            if( fabs(phi2Deg * 1000 - floor(phi2Deg * 1000 + 0.5)) < 1e-8 )
                phi2Deg = floor(phi2Deg * 1000 + 0.5) / 1000;

            // The following improvement is too turn the LCC1SP equivalent of
            // EPSG:2154 to the real LCC2SP
            // If the computed latitude of origin is close to .0 or .5 degrees
            // then check if rounding it to it will get a false northing
            // close to an integer
            const double FN = GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
            if( fabs(dfLatitudeOfOrigin * 2 -
                     floor(dfLatitudeOfOrigin * 2 + 0.5)) < 0.2 )
            {
                const double dfRoundedLatOfOrig =
                    floor(dfLatitudeOfOrigin * 2 + 0.5) / 2;
                const double m1 = msfn(phi1, ec);
                const double t1 = tsfn(phi1, ec);
                const double F = m1 / (n * pow(t1, n));
                const double a = GetSemiMajor();
                const double tRoundedLatOfOrig =
                    tsfn(DegToRad(dfRoundedLatOfOrig), ec);
                const double FN_correction =
                    a * F * (pow(tRoundedLatOfOrig, n) - pow(t0, n));
                const double FN_corrected = FN - FN_correction;
                const double FN_corrected_rounded = floor(FN_corrected + 0.5);
                if( fabs(FN_corrected - FN_corrected_rounded) < 1e-8 )
                {
                    poLCC2SP->SetLCC(
                              phi1Deg,
                              phi2Deg,
                              dfRoundedLatOfOrig,
                              GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
                              GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
                              FN_corrected_rounded );
                    return poLCC2SP;
                }
            }

            poLCC2SP->SetLCC( phi1Deg,
                              phi2Deg,
                              dfLatitudeOfOrigin,
                              GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
                              GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
                              FN );
        }
        return poLCC2SP;
    }

    if( EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP) &&
        EQUAL(pszTargetProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP) )
    {
        // Notations m0, t0, m1, t1, m2, t2 n, F are those of the EPSG guidance
        // "1.3.1.1 Lambert Conic Conformal (2SP)" and
        // "1.3.1.2 Lambert Conic Conformal (1SP)" and
        // or Snyder pages 106-109
        const double phiF =
            DegToRad(GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0));
        const double phi1 =
            DegToRad(GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0));
        const double phi2 =
            DegToRad(GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0));
        if( !(fabs(phiF) < M_PI / 2) )
            return nullptr;
        if( !(fabs(phi1) < M_PI / 2) )
            return nullptr;
        if( !(fabs(phi2) < M_PI / 2) )
            return nullptr;
        const double ec = GetEccentricity();
        if( ec < 0 )
            return nullptr;
        const double m1 = msfn(phi1, ec);
        const double m2 = msfn(phi2, ec);
        const double t1 = tsfn(phi1, ec);
        const double t2 = tsfn(phi2, ec);
        const double n_denom = log(t1) - log(t2);
        const double n = (fabs(n_denom) < 1e-10) ? sin(phi1) :
                                (log(m1) - log(m2)) / n_denom;
        if( fabs(n) < 1e-10 )
            return nullptr;
        const double F = m1 / (n * pow(t1, n));
        const double phi0 = asin(n);
        const double m0 = msfn(phi0, ec);
        const double t0 = tsfn(phi0, ec);
        const double F0 = m0 / (n * pow(t0, n));
        const double k0 = F / F0;
        const double a = GetSemiMajor();
        const double tF = tsfn(phiF, ec);
        const double FN_correction = a * F * (pow(tF, n) - pow(t0, n));

        OGRSpatialReference* poLCC1SP = new OGRSpatialReference();
        poLCC1SP->CopyGeogCSFrom(this);
        double phi0Deg = RadToDeg(phi0);
        // Try to round to thousandth of degree if very close to it
        if( fabs(phi0Deg * 1000 - floor(phi0Deg * 1000 + 0.5)) < 1e-8 )
            phi0Deg = floor(phi0Deg * 1000 + 0.5) / 1000;
        poLCC1SP->SetLCC1SP(
                phi0Deg,
                GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
                k0,
                GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0),
                GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0) +
                    (fabs(FN_correction) > 1e-8 ? FN_correction : 0) );
        return poLCC1SP;
    }

    return nullptr;
}


/************************************************************************/
/*                    OSRConvertToOtherProjection()                     */
/************************************************************************/

/**
 * \brief Convert to another equivalent projection
 * 
 * Currently implemented:
 * <ul>
 * <li>SRS_PT_MERCATOR_1SP to SRS_PT_MERCATOR_2SP</li>
 * <li>SRS_PT_MERCATOR_2SP to SRS_PT_MERCATOR_1SP</li>
 * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP to SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP</li>
 * <li>SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP to SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP</li>
 * </ul>
 *
 * @param hSRS source SRS 
 * @param pszTargetProjection target projection.
 * @param papszOptions lists of options. None supported currently.
 * @return a new SRS, or NULL in case of error.
 *
 * @since GDAL 2.3
 */
OGRSpatialReferenceH OSRConvertToOtherProjection(
                                    OGRSpatialReferenceH hSRS,
                                    const char* pszTargetProjection,
                                    const char* const* papszOptions )
{
    VALIDATE_POINTER1( hSRS, "OSRConvertToOtherProjection", nullptr );
    return ToHandle(
        ToPointer(hSRS)->
            convertToOtherProjection(pszTargetProjection, papszOptions));
}

/************************************************************************/
/*                           OSRFindMatches()                           */
/************************************************************************/

/**
 * \brief Try to identify a match between the passed SRS and a related SRS
 * in a catalog (currently EPSG only)
 *
 * Matching may be partial, or may fail.
 * Returned entries will be sorted by decreasing match confidence (first
 * entry has the highest match confidence).
 *
 * The exact way matching is done may change in future versions.
 *
 * The current algorithm is:
 * - try first AutoIdentifyEPSG(). If it succeeds, return the corresponding SRS
 * - otherwise iterate over all SRS from the EPSG catalog (as found in GDAL
 *   pcs.csv and gcs.csv files+esri_extra.wkt), and find those that match the
 *   input SRS using the IsSame() function (ignoring TOWGS84 clauses)
 * - if there is a single match using IsSame() or one of the matches has the
 *   same SRS name, return it with 100% confidence
 * - if a SRS has the same SRS name, but does not pass the IsSame() criteria,
 *   return it with 50% confidence.
 * - otherwise return all candidate SRS that pass the IsSame() criteria with a
 *   90% confidence.
 *
 * A pre-built SRS cache in ~/.gdal/X.Y/srs_cache will be used if existing,
 * otherwise it will be built at the first run of this function.
 *
 * This function is the same as OGRSpatialReference::FindMatches().
 *
 * @param hSRS SRS to match
 * @param papszOptions NULL terminated list of options or NULL
 * @param pnEntries Output parameter. Number of values in the returned array.
 * @param ppanMatchConfidence Output parameter (or NULL). *ppanMatchConfidence
 * will be allocated to an array of *pnEntries whose values between 0 and 100
 * indicate the confidence in the match. 100 is the highest confidence level.
 * The array must be freed with CPLFree().
 * 
 * @return an array of SRS that match the passed SRS, or NULL. Must be freed with
 * OSRFreeSRSArray()
 *
 * @since GDAL 2.3
 */
OGRSpatialReferenceH* OSRFindMatches( OGRSpatialReferenceH hSRS,
                                      char** papszOptions,
                                      int* pnEntries,
                                      int** ppanMatchConfidence )
{
    if( pnEntries )
        *pnEntries = 0;
    if( ppanMatchConfidence )
        *ppanMatchConfidence = nullptr;
    VALIDATE_POINTER1( hSRS, "OSRFindMatches", nullptr );

    OGRSpatialReference* poSRS = ToPointer(hSRS);
    return poSRS->FindMatches(papszOptions, pnEntries,
                              ppanMatchConfidence);
}

/************************************************************************/
/*                           OSRFreeSRSArray()                          */
/************************************************************************/

/**
 * \brief Free return of OSRIdentifyMatches()
 *
 * @param pahSRS array of SRS (must be NULL terminated)
 * @since GDAL 2.3
 */
void OSRFreeSRSArray(OGRSpatialReferenceH* pahSRS)
{
    if( pahSRS != nullptr )
    {
        for( int i = 0; pahSRS[i] != nullptr; ++i )
        {
            OSRRelease(pahSRS[i]);
        }
        CPLFree(pahSRS);
    }
}

/************************************************************************/
/*                             SetTOWGS84()                             */
/************************************************************************/

/**
 * \brief Set the Bursa-Wolf conversion to WGS84.
 *
 * This will create the TOWGS84 node as a child of the DATUM.  It will fail
 * if there is no existing DATUM node.  Unlike most OGRSpatialReference
 * methods it will insert itself in the appropriate order, and will replace
 * an existing TOWGS84 node if there is one.
 *
 * The parameters have the same meaning as EPSG transformation 9606
 * (Position Vector 7-param. transformation).
 *
 * This method is the same as the C function OSRSetTOWGS84().
 *
 * @param dfDX X child in meters.
 * @param dfDY Y child in meters.
 * @param dfDZ Z child in meters.
 * @param dfEX X rotation in arc seconds (optional, defaults to zero).
 * @param dfEY Y rotation in arc seconds (optional, defaults to zero).
 * @param dfEZ Z rotation in arc seconds (optional, defaults to zero).
 * @param dfPPM scaling factor (parts per million).
 *
 * @return OGRERR_NONE on success.
 */

OGRErr OGRSpatialReference::SetTOWGS84( double dfDX, double dfDY, double dfDZ,
                                        double dfEX, double dfEY, double dfEZ,
                                        double dfPPM )

{
    OGR_SRSNode *poDatum = GetAttrNode( "DATUM" );
    if( poDatum == nullptr )
        return OGRERR_FAILURE;

    if( poDatum->FindChild( "TOWGS84" ) != -1 )
        poDatum->DestroyChild( poDatum->FindChild( "TOWGS84" ) );

    int iPosition = poDatum->GetChildCount();
    if( poDatum->FindChild("AUTHORITY") != -1 )
    {
        iPosition = poDatum->FindChild("AUTHORITY");
    }

    OGR_SRSNode *poTOWGS84 = new OGR_SRSNode("TOWGS84");
    char szValue[64] = { '\0' };

    OGRsnPrintDouble( szValue, sizeof(szValue), dfDX );
    poTOWGS84->AddChild( new OGR_SRSNode( szValue ) );

    OGRsnPrintDouble( szValue, sizeof(szValue), dfDY );
    poTOWGS84->AddChild( new OGR_SRSNode( szValue ) );

    OGRsnPrintDouble( szValue, sizeof(szValue), dfDZ );
    poTOWGS84->AddChild( new OGR_SRSNode( szValue ) );

    OGRsnPrintDouble( szValue, sizeof(szValue), dfEX );
    poTOWGS84->AddChild( new OGR_SRSNode( szValue ) );

    OGRsnPrintDouble( szValue, sizeof(szValue), dfEY );
    poTOWGS84->AddChild( new OGR_SRSNode( szValue ) );

    OGRsnPrintDouble( szValue, sizeof(szValue), dfEZ );
    poTOWGS84->AddChild( new OGR_SRSNode( szValue ) );

    OGRsnPrintDouble( szValue, sizeof(szValue), dfPPM );
    poTOWGS84->AddChild( new OGR_SRSNode( szValue ) );

    poDatum->InsertChild( poTOWGS84, iPosition );

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRSetTOWGS84()                            */
/************************************************************************/

/**
 * \brief Set the Bursa-Wolf conversion to WGS84.
 *
 * This function is the same as OGRSpatialReference::SetTOWGS84().
 */
OGRErr OSRSetTOWGS84( OGRSpatialReferenceH hSRS,
                      double dfDX, double dfDY, double dfDZ,
                      double dfEX, double dfEY, double dfEZ,
                      double dfPPM )

{
    VALIDATE_POINTER1( hSRS, "OSRSetTOWGS84", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        SetTOWGS84( dfDX, dfDY, dfDZ, dfEX, dfEY, dfEZ, dfPPM );
}

/************************************************************************/
/*                             GetTOWGS84()                             */
/************************************************************************/

/**
 * \brief Fetch TOWGS84 parameters, if available.
 *
 * @param padfCoeff array into which up to 7 coefficients are placed.
 * @param nCoeffCount size of padfCoeff - defaults to 7.
 *
 * @return OGRERR_NONE on success, or OGRERR_FAILURE if there is no
 * TOWGS84 node available.
 */

OGRErr OGRSpatialReference::GetTOWGS84( double * padfCoeff,
                                        int nCoeffCount ) const

{
    const OGR_SRSNode *poNode = GetAttrNode( "TOWGS84" );

    memset( padfCoeff, 0, sizeof(double) * nCoeffCount );

    if( poNode == nullptr )
        return OGRERR_FAILURE;

    for( int i = 0; i < nCoeffCount && i < poNode->GetChildCount(); i++ )
    {
        padfCoeff[i] = CPLAtof(poNode->GetChild(i)->GetValue());
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                           OSRGetTOWGS84()                            */
/************************************************************************/

/**
 * \brief Fetch TOWGS84 parameters, if available.
 *
 * This function is the same as OGRSpatialReference::GetTOWGS84().
 */
OGRErr OSRGetTOWGS84( OGRSpatialReferenceH hSRS,
                      double * padfCoeff, int nCoeffCount )

{
    VALIDATE_POINTER1( hSRS, "OSRGetTOWGS84", OGRERR_FAILURE );

    return ToPointer(hSRS)->
        GetTOWGS84( padfCoeff, nCoeffCount);
}

/************************************************************************/
/*                         IsAngularParameter()                         */
/************************************************************************/

/** Is the passed projection parameter an angular one?
 *
 * @return TRUE or FALSE
 */

int OGRSpatialReference::IsAngularParameter( const char *pszParameterName )

{
    if( STARTS_WITH_CI(pszParameterName, "long")
        || STARTS_WITH_CI(pszParameterName, "lati")
        || EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN)
        || STARTS_WITH_CI(pszParameterName, "standard_parallel")
        || EQUAL(pszParameterName, SRS_PP_AZIMUTH)
        || EQUAL(pszParameterName, SRS_PP_RECTIFIED_GRID_ANGLE) )
        return TRUE;

    return FALSE;
}

/************************************************************************/
/*                        IsLongitudeParameter()                        */
/************************************************************************/

/** Is the passed projection parameter an angular longitude
 * (relative to a prime meridian)?
 *
 * @return TRUE or FALSE
 */

int OGRSpatialReference::IsLongitudeParameter( const char *pszParameterName )

{
    if( STARTS_WITH_CI(pszParameterName, "long")
        || EQUAL(pszParameterName, SRS_PP_CENTRAL_MERIDIAN) )
        return TRUE;

    return FALSE;
}

/************************************************************************/
/*                         IsLinearParameter()                          */
/************************************************************************/

/** Is the passed projection parameter an linear one measured in meters or
 * some similar linear measure.
 *
 * @return TRUE or FALSE
 */
int OGRSpatialReference::IsLinearParameter( const char *pszParameterName )

{
    if( STARTS_WITH_CI(pszParameterName, "false_")
        || EQUAL(pszParameterName, SRS_PP_SATELLITE_HEIGHT) )
        return TRUE;

    return FALSE;
}

/************************************************************************/
/*                            GetNormInfo()                             */
/************************************************************************/

/**
 * \brief Set the internal information for normalizing linear, and angular values.
 */
void OGRSpatialReference::GetNormInfo(void) const

{
    if( bNormInfoSet )
        return;

/* -------------------------------------------------------------------- */
/*      Initialize values.                                              */
/* -------------------------------------------------------------------- */
    bNormInfoSet = TRUE;

    dfFromGreenwich = GetPrimeMeridian(nullptr);
    dfToMeter = GetLinearUnits(nullptr);
    dfToDegrees = GetAngularUnits(nullptr) / CPLAtof(SRS_UA_DEGREE_CONV);
    if( fabs(dfToDegrees-1.0) < 0.000000001 )
        dfToDegrees = 1.0;
}

/************************************************************************/
/*                           FixupOrdering()                            */
/************************************************************************/

/**
 * \brief Correct parameter ordering to match CT Specification.
 *
 * Some mechanisms to create WKT using OGRSpatialReference, and some
 * imported WKT fail to maintain the order of parameters required according
 * to the BNF definitions in the OpenGIS SF-SQL and CT Specifications.  This
 * method attempts to massage things back into the required order.
 *
 * This method is the same as the C function OSRFixupOrdering().
 *
 * @return OGRERR_NONE on success or an error code if something goes
 * wrong.
 */

OGRErr OGRSpatialReference::FixupOrdering()

{
    if( GetRoot() != nullptr )
        return GetRoot()->FixupOrdering();

    return OGRERR_NONE;
}

/************************************************************************/
/*                          OSRFixupOrdering()                          */
/************************************************************************/

/**
 * \brief Correct parameter ordering to match CT Specification.
 *
 * This function is the same as OGRSpatialReference::FixupOrdering().
 */
OGRErr OSRFixupOrdering( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRFixupOrdering", OGRERR_FAILURE );

    return ToPointer(hSRS)->FixupOrdering();
}

/************************************************************************/
/*                               Fixup()                                */
/************************************************************************/

/**
 * \brief Fixup as needed.
 *
 * Some mechanisms to create WKT using OGRSpatialReference, and some
 * imported WKT, are not valid according to the OGC CT specification.  This
 * method attempts to fill in any missing defaults that are required, and
 * fixup ordering problems (using OSRFixupOrdering()) so that the resulting
 * WKT is valid.
 *
 * This method should be expected to evolve over time to as problems are
 * discovered.  The following are among the fixup actions this method will
 * take:
 *
 * - Fixup the ordering of nodes to match the BNF WKT ordering, using
 * the FixupOrdering() method.
 *
 * - Add missing linear or angular units nodes.
 *
 * This method is the same as the C function OSRFixup().
 *
 * @return OGRERR_NONE on success or an error code if something goes
 * wrong.
 */

OGRErr OGRSpatialReference::Fixup()

{
/* -------------------------------------------------------------------- */
/*      Ensure linear units defaulted to METER if missing for PROJCS,   */
/*      GEOCCS or LOCAL_CS.                                             */
/* -------------------------------------------------------------------- */
    const OGR_SRSNode *poCS = GetAttrNode( "PROJCS" );

    if( poCS == nullptr )
        poCS = GetAttrNode( "LOCAL_CS" );

    if( poCS == nullptr )
        poCS = GetAttrNode( "GEOCCS" );

    if( poCS != nullptr && poCS->FindChild( "UNIT" ) == -1 )
        SetLinearUnits( SRS_UL_METER, 1.0 );

/* -------------------------------------------------------------------- */
/*      Ensure angular units defaulted to degrees on the GEOGCS.        */
/* -------------------------------------------------------------------- */
    poCS = GetAttrNode( "GEOGCS" );
    if( poCS != nullptr && poCS->FindChild( "UNIT" ) == -1 )
        SetAngularUnits( SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV) );

    return FixupOrdering();
}

/************************************************************************/
/*                              OSRFixup()                              */
/************************************************************************/

/**
 * \brief Fixup as needed.
 *
 * This function is the same as OGRSpatialReference::Fixup().
 */
OGRErr OSRFixup( OGRSpatialReferenceH hSRS )

{
    VALIDATE_POINTER1( hSRS, "OSRFixup", OGRERR_FAILURE );

    return ToPointer(hSRS)->Fixup();
}

/************************************************************************/
/*                            GetExtension()                            */
/************************************************************************/

/**
 * \brief Fetch extension value.
 *
 * Fetch the value of the named EXTENSION item for the identified
 * target node.
 *
 * @param pszTargetKey the name or path to the parent node of the EXTENSION.
 * @param pszName the name of the extension being fetched.
 * @param pszDefault the value to return if the extension is not found.
 *
 * @return node value if successful or pszDefault on failure.
 */

const char *OGRSpatialReference::GetExtension( const char *pszTargetKey,
                                               const char *pszName,
                                               const char *pszDefault ) const

{
/* -------------------------------------------------------------------- */
/*      Find the target node.                                           */
/* -------------------------------------------------------------------- */
    const OGR_SRSNode *poNode = pszTargetKey == nullptr
        ? poRoot
        : GetAttrNode( pszTargetKey );

    if( poNode == nullptr )
        return nullptr;

/* -------------------------------------------------------------------- */
/*      Fetch matching EXTENSION if there is one.                       */
/* -------------------------------------------------------------------- */
    for( int i = poNode->GetChildCount()-1; i >= 0; i-- )
    {
        const OGR_SRSNode *poChild = poNode->GetChild(i);

        if( EQUAL(poChild->GetValue(), "EXTENSION")
            && poChild->GetChildCount() >= 2 )
        {
            if( EQUAL(poChild->GetChild(0)->GetValue(), pszName) )
                return poChild->GetChild(1)->GetValue();
        }
    }

    return pszDefault;
}

/************************************************************************/
/*                            SetExtension()                            */
/************************************************************************/
/**
 * \brief Set extension value.
 *
 * Set the value of the named EXTENSION item for the identified
 * target node.
 *
 * @param pszTargetKey the name or path to the parent node of the EXTENSION.
 * @param pszName the name of the extension being fetched.
 * @param pszValue the value to set
 *
 * @return OGRERR_NONE on success
 */

OGRErr OGRSpatialReference::SetExtension( const char *pszTargetKey,
                                          const char *pszName,
                                          const char *pszValue )

{
/* -------------------------------------------------------------------- */
/*      Find the target node.                                           */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poNode = nullptr;

    if( pszTargetKey == nullptr )
        poNode = poRoot;
    else
        poNode = GetAttrNode(pszTargetKey);

    if( poNode == nullptr )
        return OGRERR_FAILURE;

/* -------------------------------------------------------------------- */
/*      Fetch matching EXTENSION if there is one.                       */
/* -------------------------------------------------------------------- */
    for( int i = poNode->GetChildCount()-1; i >= 0; i-- )
    {
        OGR_SRSNode *poChild = poNode->GetChild(i);

        if( EQUAL(poChild->GetValue(), "EXTENSION")
            && poChild->GetChildCount() >= 2 )
        {
            if( EQUAL(poChild->GetChild(0)->GetValue(), pszName) )
            {
                poChild->GetChild(1)->SetValue( pszValue );
                return OGRERR_NONE;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Create a new EXTENSION node.                                    */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poAuthNode = new OGR_SRSNode( "EXTENSION" );
    poAuthNode->AddChild( new OGR_SRSNode( pszName ) );
    poAuthNode->AddChild( new OGR_SRSNode( pszValue ) );

    poNode->AddChild( poAuthNode );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRCleanup()                             */
/************************************************************************/

CPL_C_START
void CleanupESRIDatumMappingTable();
CPL_C_END
static void CleanupSRSWGS84Mutex();
void CleanupFindMatchesCacheAndMutex();

/**
 * \brief Cleanup cached SRS related memory.
 *
 * This function will attempt to cleanup any cache spatial reference
 * related information, such as cached tables of coordinate systems.
 */
void OSRCleanup( void )

{
    CleanupESRIDatumMappingTable();
    CSVDeaccess( nullptr );
    OCTCleanupProjMutex();
    CleanupSRSWGS84Mutex();
    CleanupFindMatchesCacheAndMutex();
}

/************************************************************************/
/*                              GetAxis()                               */
/************************************************************************/

/**
 * \brief Fetch the orientation of one axis.
 *
 * Fetches the request axis (iAxis - zero based) from the
 * indicated portion of the coordinate system (pszTargetKey) which
 * should be either "GEOGCS" or "PROJCS".
 *
 * No CPLError is issued on routine failures (such as not finding the AXIS).
 *
 * This method is equivalent to the C function OSRGetAxis().
 *
 * @param pszTargetKey the coordinate system part to query ("PROJCS" or "GEOGCS").
 * @param iAxis the axis to query (0 for first, 1 for second).
 * @param peOrientation location into which to place the fetch orientation, may be NULL.
 *
 * @return the name of the axis or NULL on failure.
 */

const char *
OGRSpatialReference::GetAxis( const char *pszTargetKey, int iAxis,
                              OGRAxisOrientation *peOrientation ) const

{
    if( peOrientation != nullptr )
        *peOrientation = OAO_Other;

/* -------------------------------------------------------------------- */
/*      Find the target node.                                           */
/* -------------------------------------------------------------------- */
    const OGR_SRSNode *poNode = nullptr;

    if( pszTargetKey == nullptr )
        poNode = poRoot;
    else
        poNode = GetAttrNode(pszTargetKey);

    if( poNode == nullptr )
        return nullptr;

/* -------------------------------------------------------------------- */
/*      Find desired child AXIS.                                        */
/* -------------------------------------------------------------------- */
    const OGR_SRSNode *poAxis = nullptr;
    const int nChildCount = poNode->GetChildCount();

    for( int iChild = 0; iChild < nChildCount; iChild++ )
    {
        const OGR_SRSNode *poChild = poNode->GetChild( iChild );

        if( !EQUAL(poChild->GetValue(), "AXIS") )
            continue;

        if( iAxis == 0 )
        {
            poAxis = poChild;
            break;
        }
        iAxis--;
    }

    if( poAxis == nullptr )
        return nullptr;

    if( poAxis->GetChildCount() < 2 )
        return nullptr;

/* -------------------------------------------------------------------- */
/*      Extract name and orientation if possible.                       */
/* -------------------------------------------------------------------- */
    if( peOrientation != nullptr )
    {
        const char *pszOrientation = poAxis->GetChild(1)->GetValue();

        if( EQUAL(pszOrientation, "NORTH") )
            *peOrientation = OAO_North;
        else if( EQUAL(pszOrientation, "EAST") )
            *peOrientation = OAO_East;
        else if( EQUAL(pszOrientation, "SOUTH") )
            *peOrientation = OAO_South;
        else if( EQUAL(pszOrientation, "WEST") )
            *peOrientation = OAO_West;
        else if( EQUAL(pszOrientation, "UP") )
            *peOrientation = OAO_Up;
        else if( EQUAL(pszOrientation, "DOWN") )
            *peOrientation = OAO_Down;
        else if( EQUAL(pszOrientation, "OTHER") )
            *peOrientation = OAO_Other;
        else
        {
            CPLDebug( "OSR", "Unrecognized orientation value '%s'.",
                      pszOrientation );
        }
    }

    return poAxis->GetChild(0)->GetValue();
}

/************************************************************************/
/*                             OSRGetAxis()                             */
/************************************************************************/

/**
 * \brief Fetch the orientation of one axis.
 *
 * This method is the equivalent of the C++ method OGRSpatialReference::GetAxis
 */
const char *OSRGetAxis( OGRSpatialReferenceH hSRS,
                        const char *pszTargetKey, int iAxis,
                        OGRAxisOrientation *peOrientation )

{
    VALIDATE_POINTER1( hSRS, "OSRGetAxis", nullptr );

    return ToPointer(hSRS)->GetAxis( pszTargetKey, iAxis,
                                                              peOrientation );
}

/************************************************************************/
/*                         OSRAxisEnumToName()                          */
/************************************************************************/

/**
 * \brief Return the string representation for the OGRAxisOrientation enumeration.
 *
 * For example "NORTH" for OAO_North.
 *
 * @return an internal string
 */
const char *OSRAxisEnumToName( OGRAxisOrientation eOrientation )

{
    if( eOrientation == OAO_North )
        return "NORTH";
    if( eOrientation == OAO_East )
        return "EAST";
    if( eOrientation == OAO_South )
        return "SOUTH";
    if( eOrientation == OAO_West )
        return "WEST";
    if( eOrientation == OAO_Up )
        return "UP";
    if( eOrientation == OAO_Down )
        return "DOWN";
    if( eOrientation == OAO_Other )
        return "OTHER";

    return "UNKNOWN";
}

/************************************************************************/
/*                              SetAxes()                               */
/************************************************************************/

/**
 * \brief Set the axes for a coordinate system.
 *
 * Set the names, and orientations of the axes for either a projected
 * (PROJCS) or geographic (GEOGCS) coordinate system.
 *
 * This method is equivalent to the C function OSRSetAxes().
 *
 * @param pszTargetKey either "PROJCS" or "GEOGCS", must already exist in SRS.
 * @param pszXAxisName name of first axis, normally "Long" or "Easting".
 * @param eXAxisOrientation normally OAO_East.
 * @param pszYAxisName name of second axis, normally "Lat" or "Northing".
 * @param eYAxisOrientation normally OAO_North.
 *
 * @return OGRERR_NONE on success or an error code.
 */

OGRErr
OGRSpatialReference::SetAxes( const char *pszTargetKey,
                              const char *pszXAxisName,
                              OGRAxisOrientation eXAxisOrientation,
                              const char *pszYAxisName,
                              OGRAxisOrientation eYAxisOrientation )

{
/* -------------------------------------------------------------------- */
/*      Find the target node.                                           */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poNode = nullptr;

    if( pszTargetKey == nullptr )
        poNode = poRoot;
    else
        poNode = GetAttrNode( pszTargetKey );

    if( poNode == nullptr )
        return OGRERR_FAILURE;

/* -------------------------------------------------------------------- */
/*      Strip any existing AXIS children.                               */
/* -------------------------------------------------------------------- */
    while( poNode->FindChild( "AXIS" ) >= 0 )
        poNode->DestroyChild( poNode->FindChild( "AXIS" ) );

/* -------------------------------------------------------------------- */
/*      Insert desired axes                                             */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poAxis = new OGR_SRSNode( "AXIS" );

    poAxis->AddChild( new OGR_SRSNode( pszXAxisName ) );
    poAxis->AddChild( new OGR_SRSNode( OSRAxisEnumToName(eXAxisOrientation) ));

    poNode->AddChild( poAxis );

    poAxis = new OGR_SRSNode( "AXIS" );

    poAxis->AddChild( new OGR_SRSNode( pszYAxisName ) );
    poAxis->AddChild( new OGR_SRSNode( OSRAxisEnumToName(eYAxisOrientation) ));

    poNode->AddChild( poAxis );

    return OGRERR_NONE;
}

/************************************************************************/
/*                             OSRSetAxes()                             */
/************************************************************************/
/**
 * \brief Set the axes for a coordinate system.
 *
 * This method is the equivalent of the C++ method OGRSpatialReference::SetAxes
 */
OGRErr OSRSetAxes( OGRSpatialReferenceH hSRS,
                   const char *pszTargetKey,
                   const char *pszXAxisName,
                   OGRAxisOrientation eXAxisOrientation,
                   const char *pszYAxisName,
                   OGRAxisOrientation eYAxisOrientation )
{
    VALIDATE_POINTER1( hSRS, "OSRSetAxes", OGRERR_FAILURE );

    return ToPointer(hSRS)->SetAxes( pszTargetKey,
                                                              pszXAxisName,
                                                              eXAxisOrientation,
                                                              pszYAxisName,
                                                    eYAxisOrientation );
}

#ifdef HAVE_MITAB
char CPL_DLL *MITABSpatialRef2CoordSys( const OGRSpatialReference * );
OGRSpatialReference CPL_DLL * MITABCoordSys2SpatialRef( const char * );
#endif

/************************************************************************/
/*                       OSRExportToMICoordSys()                        */
/************************************************************************/
/**
 * \brief Export coordinate system in Mapinfo style CoordSys format.
 *
 * This method is the equivalent of the C++ method OGRSpatialReference::exportToMICoordSys
 */
OGRErr OSRExportToMICoordSys( OGRSpatialReferenceH hSRS, char ** ppszReturn )

{
    VALIDATE_POINTER1( hSRS, "OSRExportToMICoordSys", OGRERR_FAILURE );

    *ppszReturn = nullptr;

    return ToPointer(hSRS)->exportToMICoordSys( ppszReturn );
}

/************************************************************************/
/*                         exportToMICoordSys()                         */
/************************************************************************/

/**
 * \brief Export coordinate system in Mapinfo style CoordSys format.
 *
 * Note that the returned WKT string should be freed with
 * CPLFree() when no longer needed.  It is the responsibility of the caller.
 *
 * This method is the same as the C function OSRExportToMICoordSys().
 *
 * @param ppszResult pointer to which dynamically allocated Mapinfo CoordSys
 * definition will be assigned.
 *
 * @return OGRERR_NONE on success, OGRERR_FAILURE on failure,
 * OGRERR_UNSUPPORTED_OPERATION if MITAB library was not linked in.
 */

OGRErr OGRSpatialReference::exportToMICoordSys( char **ppszResult ) const

{
#ifdef HAVE_MITAB
    *ppszResult = MITABSpatialRef2CoordSys( this );
    if( *ppszResult != nullptr && strlen(*ppszResult) > 0 )
        return OGRERR_NONE;

    return OGRERR_FAILURE;
#else
    CPLError( CE_Failure, CPLE_NotSupported,
              "MITAB not available, CoordSys support disabled." );

    return OGRERR_UNSUPPORTED_OPERATION;
#endif
}

/************************************************************************/
/*                       OSRImportFromMICoordSys()                      */
/************************************************************************/
/**
 * \brief Import Mapinfo style CoordSys definition.
 *
 * This method is the equivalent of the C++ method OGRSpatialReference::importFromMICoordSys
 */

OGRErr OSRImportFromMICoordSys( OGRSpatialReferenceH hSRS,
                                const char *pszCoordSys )

{
    VALIDATE_POINTER1( hSRS, "OSRImportFromMICoordSys", OGRERR_FAILURE );

    return ToPointer(hSRS)->importFromMICoordSys( pszCoordSys );
}

/************************************************************************/
/*                        importFromMICoordSys()                        */
/************************************************************************/

/**
 * \brief Import Mapinfo style CoordSys definition.
 *
 * The OGRSpatialReference is initialized from the passed Mapinfo style CoordSys definition string.
 *
 * This method is the equivalent of the C function OSRImportFromMICoordSys().
 *
 * @param pszCoordSys Mapinfo style CoordSys definition string.
 *
 * @return OGRERR_NONE on success, OGRERR_FAILURE on failure,
 * OGRERR_UNSUPPORTED_OPERATION if MITAB library was not linked in.
 */

OGRErr OGRSpatialReference::importFromMICoordSys( const char *pszCoordSys )

{
#ifdef HAVE_MITAB
    OGRSpatialReference *poResult = MITABCoordSys2SpatialRef( pszCoordSys );

    if( poResult == nullptr )
        return OGRERR_FAILURE;

    *this = *poResult;
    delete poResult;

    return OGRERR_NONE;
#else
    CPLError( CE_Failure, CPLE_NotSupported,
              "MITAB not available, CoordSys support disabled." );

    return OGRERR_UNSUPPORTED_OPERATION;
#endif
}

/************************************************************************/
/*                        OSRCalcInvFlattening()                        */
/************************************************************************/

/**
 * \brief Compute inverse flattening from semi-major and semi-minor axis
 *
 * @param dfSemiMajor Semi-major axis length.
 * @param dfSemiMinor Semi-minor axis length.
 *
 * @return inverse flattening, or 0 if both axis are equal.
 * @since GDAL 2.0
 */

double OSRCalcInvFlattening( double dfSemiMajor, double dfSemiMinor )
{
    if( fabs(dfSemiMajor-dfSemiMinor) < 1e-1 )
        return 0;
    if( dfSemiMajor <= 0 || dfSemiMinor <= 0 || dfSemiMinor > dfSemiMajor )
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
                 "OSRCalcInvFlattening(): Wrong input values");
        return 0;
    }

    return dfSemiMajor / (dfSemiMajor - dfSemiMinor);
}

/************************************************************************/
/*                        OSRCalcInvFlattening()                        */
/************************************************************************/

/**
 * \brief Compute semi-minor axis from semi-major axis and inverse flattening.
 *
 * @param dfSemiMajor Semi-major axis length.
 * @param dfInvFlattening Inverse flattening or 0 for sphere.
 *
 * @return semi-minor axis
 * @since GDAL 2.0
 */

double OSRCalcSemiMinorFromInvFlattening( double dfSemiMajor, double dfInvFlattening )
{
    if( fabs(dfInvFlattening) < 0.000000000001 )
        return dfSemiMajor;
    if( dfSemiMajor <= 0.0 || dfInvFlattening <= 1.0 )
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
                 "OSRCalcSemiMinorFromInvFlattening(): Wrong input values");
        return dfSemiMajor;
    }

    return dfSemiMajor * (1.0 - 1.0/dfInvFlattening);
}

/************************************************************************/
/*                        GetWGS84SRS()                                 */
/************************************************************************/

static OGRSpatialReference* poSRSWGS84 = nullptr;
static CPLMutex* hMutex = nullptr;

/**
 * \brief Returns an instance of a SRS object with WGS84 WKT.
 *
 * The reference counter of the returned object is not increased by this operation.
 *
 * @return instance.
 * @since GDAL 2.0
 */

OGRSpatialReference* OGRSpatialReference::GetWGS84SRS()
{
    CPLMutexHolderD(&hMutex);
    if( poSRSWGS84 == nullptr )
        poSRSWGS84 = new OGRSpatialReference(SRS_WKT_WGS84);
    return poSRSWGS84;
}

/************************************************************************/
/*                        CleanupSRSWGS84Mutex()                       */
/************************************************************************/

static void CleanupSRSWGS84Mutex()
{
    if( hMutex != nullptr )
    {
        poSRSWGS84->Release();
        poSRSWGS84 = nullptr;
        CPLDestroyMutex(hMutex);
        hMutex = nullptr;
    }
}
