/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implementation of the OGRSpatialReference::Validate() method and
 *           related infrastructure.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2001, Frank Warmerdam <warmerdam@pobox.com>
 * 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_srs_api.h"

#include <cstdlib>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "ogr_core.h"
#include "ogr_p.h"
#include "ogr_spatialref.h"
#include "osr_cs_wkt.h"

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

// Why would fipszone and zone be parameters when they relate to a composite
// projection which renders down into a non-zoned projection?

static const char * const papszParameters[] =
{
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_STANDARD_PARALLEL_1,
    SRS_PP_STANDARD_PARALLEL_2,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_LONGITUDE_OF_ORIGIN,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    SRS_PP_AZIMUTH,
    SRS_PP_LONGITUDE_OF_POINT_1,
    SRS_PP_LATITUDE_OF_POINT_1,
    SRS_PP_LONGITUDE_OF_POINT_2,
    SRS_PP_LATITUDE_OF_POINT_2,
    SRS_PP_LONGITUDE_OF_POINT_3,
    SRS_PP_LATITUDE_OF_POINT_3,
    SRS_PP_LANDSAT_NUMBER,
    SRS_PP_PATH_NUMBER,
    SRS_PP_PERSPECTIVE_POINT_HEIGHT,
    SRS_PP_FIPSZONE,
    SRS_PP_ZONE,
    SRS_PP_RECTIFIED_GRID_ANGLE,
    SRS_PP_SATELLITE_HEIGHT,
    SRS_PP_PSEUDO_STD_PARALLEL_1,
    SRS_PP_LATITUDE_OF_1ST_POINT,
    SRS_PP_LONGITUDE_OF_1ST_POINT,
    SRS_PP_LATITUDE_OF_2ND_POINT,
    SRS_PP_LONGITUDE_OF_2ND_POINT,
    SRS_PP_PEG_POINT_LATITUDE,   // For SCH.
    SRS_PP_PEG_POINT_LONGITUDE,  // For SCH.
    SRS_PP_PEG_POINT_HEADING,    // For SCH.
    SRS_PP_PEG_POINT_HEIGHT,     // For SCH.
    nullptr
};

// The following projection lists are incomplete.  They will likely
// change after the CT RPF response.  Examples show alternate forms with
// underscores instead of spaces.  Should we use the EPSG names were available?
// Plate-Caree has an accent in the spec!

static const char * const papszProjectionSupported[] =
{
    SRS_PT_CASSINI_SOLDNER,
    SRS_PT_BONNE,
    SRS_PT_EQUIDISTANT_CONIC,
    SRS_PT_EQUIRECTANGULAR,
    SRS_PT_ECKERT_I,
    SRS_PT_ECKERT_II,
    SRS_PT_ECKERT_III,
    SRS_PT_ECKERT_IV,
    SRS_PT_ECKERT_V,
    SRS_PT_ECKERT_VI,
    SRS_PT_MERCATOR_1SP,
    SRS_PT_MERCATOR_2SP,
    SRS_PT_MOLLWEIDE,
    SRS_PT_ROBINSON,
    SRS_PT_ALBERS_CONIC_EQUAL_AREA,
    SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP,
    SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP,
    SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP_BELGIUM,
    SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA,
    SRS_PT_TRANSVERSE_MERCATOR,
    SRS_PT_TRANSVERSE_MERCATOR_SOUTH_ORIENTED,
    SRS_PT_OBLIQUE_STEREOGRAPHIC,
    SRS_PT_POLAR_STEREOGRAPHIC,
    SRS_PT_HOTINE_OBLIQUE_MERCATOR,
    SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN,
    SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER,
    SRS_PT_LABORDE_OBLIQUE_MERCATOR,
    SRS_PT_SWISS_OBLIQUE_CYLINDRICAL,
    SRS_PT_AZIMUTHAL_EQUIDISTANT,
    SRS_PT_MILLER_CYLINDRICAL,
    SRS_PT_NEW_ZEALAND_MAP_GRID,
    SRS_PT_SINUSOIDAL,
    SRS_PT_STEREOGRAPHIC,
    SRS_PT_GNOMONIC,
    SRS_PT_GALL_STEREOGRAPHIC,
    SRS_PT_ORTHOGRAPHIC,
    SRS_PT_POLYCONIC,
    SRS_PT_VANDERGRINTEN,
    SRS_PT_GEOSTATIONARY_SATELLITE,
    SRS_PT_TWO_POINT_EQUIDISTANT,
    SRS_PT_IMW_POLYCONIC,
    SRS_PT_WAGNER_I,
    SRS_PT_WAGNER_II,
    SRS_PT_WAGNER_III,
    SRS_PT_WAGNER_IV,
    SRS_PT_WAGNER_V,
    SRS_PT_WAGNER_VI,
    SRS_PT_WAGNER_VII,
    SRS_PT_QSC,
    SRS_PT_SCH,
    SRS_PT_GAUSSSCHREIBERTMERCATOR,
    SRS_PT_KROVAK,
    SRS_PT_CYLINDRICAL_EQUAL_AREA,
    SRS_PT_GOODE_HOMOLOSINE,
    SRS_PT_IGH,
    nullptr
};

static const char * const papszProjectionUnsupported[] =
{
    SRS_PT_NEW_ZEALAND_MAP_GRID,
    SRS_PT_TUNISIA_MINING_GRID,
    nullptr
};

// List of supported projections with the PARAMETERS[] acceptable for each.
static const char * const papszProjWithParms[] = {

    SRS_PT_TRANSVERSE_MERCATOR,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_TRANSVERSE_MERCATOR_SOUTH_ORIENTED,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_TUNISIA_MINING_GRID,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_ALBERS_CONIC_EQUAL_AREA,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_STANDARD_PARALLEL_1,
    SRS_PP_STANDARD_PARALLEL_2,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_AZIMUTHAL_EQUIDISTANT,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_BONNE,
    SRS_PP_STANDARD_PARALLEL_1,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_CYLINDRICAL_EQUAL_AREA,
    SRS_PP_STANDARD_PARALLEL_1,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_CASSINI_SOLDNER,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_EQUIDISTANT_CONIC,
    SRS_PP_STANDARD_PARALLEL_1,
    SRS_PP_STANDARD_PARALLEL_2,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_ECKERT_I,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_ECKERT_II,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_ECKERT_III,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_ECKERT_IV,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_ECKERT_V,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_ECKERT_VI,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_EQUIRECTANGULAR,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_STANDARD_PARALLEL_1,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_GALL_STEREOGRAPHIC,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_GNOMONIC,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_HOTINE_OBLIQUE_MERCATOR,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_AZIMUTH,
    SRS_PP_RECTIFIED_GRID_ANGLE,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_AZIMUTH,
    SRS_PP_RECTIFIED_GRID_ANGLE,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_LATITUDE_OF_POINT_1,
    SRS_PP_LONGITUDE_OF_POINT_1,
    SRS_PP_LATITUDE_OF_POINT_2,
    SRS_PP_LONGITUDE_OF_POINT_2,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP,
    SRS_PP_STANDARD_PARALLEL_1,
    SRS_PP_STANDARD_PARALLEL_2,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP_BELGIUM,
    SRS_PP_STANDARD_PARALLEL_1,
    SRS_PP_STANDARD_PARALLEL_2,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_MILLER_CYLINDRICAL,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_MERCATOR_1SP,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_MERCATOR_2SP,
    SRS_PP_STANDARD_PARALLEL_1,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_MOLLWEIDE,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_NEW_ZEALAND_MAP_GRID,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_ORTHOGRAPHIC,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_POLYCONIC,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_POLAR_STEREOGRAPHIC,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_ROBINSON,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_SINUSOIDAL,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_STEREOGRAPHIC,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_SWISS_OBLIQUE_CYLINDRICAL,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_OBLIQUE_STEREOGRAPHIC,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_VANDERGRINTEN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_GEOSTATIONARY_SATELLITE,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_SATELLITE_HEIGHT,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_KROVAK,
    SRS_PP_LATITUDE_OF_CENTER,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_AZIMUTH,
    SRS_PP_PSEUDO_STD_PARALLEL_1,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_TWO_POINT_EQUIDISTANT,
    SRS_PP_LATITUDE_OF_1ST_POINT,
    SRS_PP_LONGITUDE_OF_1ST_POINT,
    SRS_PP_LATITUDE_OF_2ND_POINT,
    SRS_PP_LONGITUDE_OF_2ND_POINT,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_IMW_POLYCONIC,
    SRS_PP_LATITUDE_OF_1ST_POINT,
    SRS_PP_LATITUDE_OF_2ND_POINT,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_WAGNER_I,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_WAGNER_II,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_WAGNER_III,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_WAGNER_IV,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_WAGNER_V,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_WAGNER_VI,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_WAGNER_VII,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_QSC,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    nullptr,

    SRS_PT_SCH,
    SRS_PP_PEG_POINT_LATITUDE,
    SRS_PP_PEG_POINT_LONGITUDE,
    SRS_PP_PEG_POINT_HEADING,
    SRS_PP_PEG_POINT_HEIGHT,
    nullptr,

    SRS_PT_GAUSSSCHREIBERTMERCATOR,
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_SCALE_FACTOR,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_GOODE_HOMOLOSINE,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_FALSE_EASTING,
    SRS_PP_FALSE_NORTHING,
    nullptr,

    SRS_PT_IGH,
    nullptr,

    nullptr
};

static const char * const papszAliasGroupList[] = {
    SRS_PP_LATITUDE_OF_ORIGIN,
    SRS_PP_LATITUDE_OF_CENTER,
    nullptr,
    SRS_PP_CENTRAL_MERIDIAN,
    SRS_PP_LONGITUDE_OF_CENTER,
    SRS_PP_LONGITUDE_OF_ORIGIN,
    nullptr,
    nullptr
};

/************************************************************************/
/*                              Validate()                              */
/************************************************************************/

/**
 * \brief Validate SRS tokens.
 *
 * This method attempts to verify that the spatial reference system is
 * well formed, and consists of known tokens.  The validation is not
 * comprehensive.
 *
 * This method is the same as the C function OSRValidate().
 *
 * @return OGRERR_NONE if all is fine, OGRERR_CORRUPT_DATA if the SRS is
 * not well formed, and OGRERR_UNSUPPORTED_SRS if the SRS is well formed,
 * but contains non-standard PROJECTION[] values.
 */

OGRErr OGRSpatialReference::Validate() const

{
/* -------------------------------------------------------------------- */
/*      Validate root node.                                             */
/* -------------------------------------------------------------------- */
    if( poRoot == nullptr )
    {
        CPLDebug( "OGRSpatialReference::Validate", "No root pointer." );
        return OGRERR_CORRUPT_DATA;
    }

    OGRErr eErr = Validate(poRoot);

    // Even if hand-validation has succeeded, try a more formal validation
    // using the CT spec grammar.
    static int bUseCTGrammar = -1;
    if( bUseCTGrammar < 0 )
        bUseCTGrammar =
            CPLTestBool(CPLGetConfigOption("OSR_USE_CT_GRAMMAR", "TRUE"));

    if( eErr == OGRERR_NONE && bUseCTGrammar )
    {
        char* pszWKT = nullptr;
        exportToWkt(&pszWKT);

        osr_cs_wkt_parse_context sContext;
        sContext.pszInput = pszWKT;
        sContext.pszLastSuccess = pszWKT;
        sContext.pszNext = pszWKT;
        sContext.szErrorMsg[0] = '\0';

        if( osr_cs_wkt_parse(&sContext) != 0 )
        {
            CPLDebug( "OGRSpatialReference::Validate", "%s",
                      sContext.szErrorMsg );
            eErr = OGRERR_CORRUPT_DATA;
        }

        CPLFree(pszWKT);
    }
    return eErr;
}

OGRErr OGRSpatialReference::Validate(OGR_SRSNode *poRoot)
{
    if( !EQUAL(poRoot->GetValue(), "GEOGCS")
        && !EQUAL(poRoot->GetValue(), "PROJCS")
        && !EQUAL(poRoot->GetValue(), "LOCAL_CS")
        && !EQUAL(poRoot->GetValue(), "GEOCCS")
        && !EQUAL(poRoot->GetValue(), "VERT_CS")
        && !EQUAL(poRoot->GetValue(), "COMPD_CS"))
    {
        CPLDebug( "OGRSpatialReference::Validate",
                  "Unrecognized root node `%s'",
                  poRoot->GetValue() );
        return OGRERR_CORRUPT_DATA;
    }

/* -------------------------------------------------------------------- */
/*      For a COMPD_CS, validate subparameters and head & tail cs       */
/* -------------------------------------------------------------------- */
    if( EQUAL(poRoot->GetValue(), "COMPD_CS") )
    {
        for( int i = 1; i < poRoot->GetChildCount(); i++ )
        {
            OGR_SRSNode *poNode = poRoot->GetChild(i);

            if( EQUAL(poNode->GetValue(), "GEOGCS") ||
                EQUAL(poNode->GetValue(), "PROJCS") ||
                EQUAL(poNode->GetValue(), "LOCAL_CS") ||
                EQUAL(poNode->GetValue(), "GEOCCS") ||
                EQUAL(poNode->GetValue(), "VERT_CS") ||
                EQUAL(poNode->GetValue(), "COMPD_CS") )
            {
                OGRErr eErr = Validate(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else if( EQUAL(poNode->GetValue(), "AUTHORITY") )
            {
                OGRErr eErr = ValidateAuthority(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else if( EQUAL(poNode->GetValue(), "EXTENSION") )
            {
                // We do not try to control the sub-organization of
                // EXTENSION nodes.
            }
            else
            {
                CPLDebug( "OGRSpatialReference::Validate",
                          "Unexpected child for COMPD_CS `%s'.",
                          poNode->GetValue() );

                return OGRERR_CORRUPT_DATA;
            }
        }

        return OGRERR_NONE;
    }

/* -------------------------------------------------------------------- */
/*      Validate VERT_CS                                                */
/* -------------------------------------------------------------------- */
    if( EQUAL(poRoot->GetValue(), "VERT_CS") )
    {
        bool bGotVertDatum = false;
        bool bGotUnit = false;
        int nCountAxis = 0;

        for( int i = 1; i < poRoot->GetChildCount(); i++ )
        {
            OGR_SRSNode *poNode = poRoot->GetChild(i);

            if( EQUAL(poNode->GetValue(), "VERT_DATUM") )
            {
                OGRErr eErr = ValidateVertDatum(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
                bGotVertDatum = true;
            }
            else if( EQUAL(poNode->GetValue(), "UNIT") )
            {
                OGRErr eErr = ValidateUnit(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
                bGotUnit = true;
            }
            else if( EQUAL(poNode->GetValue(), "AXIS") )
            {
                OGRErr eErr = ValidateAxis(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
                nCountAxis++;
            }
            else if( EQUAL(poNode->GetValue(), "AUTHORITY") )
            {
                OGRErr eErr = ValidateAuthority(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else
            {
                CPLDebug( "OGRSpatialReference::Validate",
                          "Unexpected child for VERT_CS `%s'.",
                          poNode->GetValue() );

                return OGRERR_CORRUPT_DATA;
            }
        }

        if( !bGotVertDatum )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "No VERT_DATUM child in VERT_CS." );

            return OGRERR_CORRUPT_DATA;
        }

        if( !bGotUnit )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "No UNIT child in VERT_CS." );

            return OGRERR_CORRUPT_DATA;
        }

        if( nCountAxis > 1 )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "Too many AXIS children in VERT_CS." );

            return OGRERR_CORRUPT_DATA;
        }
        return OGRERR_NONE;
    }

/* -------------------------------------------------------------------- */
/*      Validate GEOCCS                                                 */
/* -------------------------------------------------------------------- */
    if( EQUAL(poRoot->GetValue(), "GEOCCS") )
    {
        bool bGotDatum = false;
        bool bGotPrimeM = false;
        bool bGotUnit = false;
        int nCountAxis = 0;

        for( int i = 1; i < poRoot->GetChildCount(); i++ )
        {
            OGR_SRSNode *poNode = poRoot->GetChild(i);

            if( EQUAL(poNode->GetValue(), "DATUM") )
            {
                bGotDatum = true;
            }
            else if( EQUAL(poNode->GetValue(), "PRIMEM") )
            {
                bGotPrimeM = true;

                if( poNode->GetChildCount() < 2
                    || poNode->GetChildCount() > 3 )
                {
                    CPLDebug( "OGRSpatialReference::Validate",
                              "PRIMEM has wrong number of children (%d), "
                              "not 2 or 3 as expected.",
                              poNode->GetChildCount() );

                    return OGRERR_CORRUPT_DATA;
                }
            }
            else if( EQUAL(poNode->GetValue(), "UNIT") )
            {
                OGRErr eErr = ValidateUnit(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
                bGotUnit = true;
            }
            else if( EQUAL(poNode->GetValue(), "AXIS") )
            {
                OGRErr eErr = ValidateAxis(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
                nCountAxis++;
            }
            else if( EQUAL(poNode->GetValue(), "AUTHORITY") )
            {
                OGRErr eErr = ValidateAuthority(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else
            {
                CPLDebug( "OGRSpatialReference::Validate",
                          "Unexpected child for GEOCCS `%s'.",
                          poNode->GetValue() );

                return OGRERR_CORRUPT_DATA;
            }
        }

        if( !bGotDatum )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "No DATUM child in GEOCCS." );

            return OGRERR_CORRUPT_DATA;
        }

        if( !bGotPrimeM )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "No PRIMEM child in GEOCCS." );

            return OGRERR_CORRUPT_DATA;
        }

        if( !bGotUnit )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "No UNIT child in GEOCCS." );

            return OGRERR_CORRUPT_DATA;
        }

        if( nCountAxis != 0 && nCountAxis != 3 )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "Wrong number of AXIS children in GEOCCS." );

            return OGRERR_CORRUPT_DATA;
        }
    }

/* -------------------------------------------------------------------- */
/*      For a PROJCS, validate subparameters (other than GEOGCS).       */
/* -------------------------------------------------------------------- */
    if( EQUAL(poRoot->GetValue(), "PROJCS") )
    {
        for( int i = 1; i < poRoot->GetChildCount(); i++ )
        {
            OGR_SRSNode *poNode = poRoot->GetChild(i);

            if( EQUAL(poNode->GetValue(), "GEOGCS") )
            {
                // Validated elsewhere.
            }
            else if( EQUAL(poNode->GetValue(), "UNIT") )
            {
                OGRErr eErr = ValidateUnit(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else if( EQUAL(poNode->GetValue(), "PARAMETER") )
            {
                if( poNode->GetChildCount() != 2 )
                {
                    CPLDebug( "OGRSpatialReference::Validate",
                              "PARAMETER has wrong number of children (%d), "
                              "not 2 as expected.",
                              poNode->GetChildCount() );

                    return OGRERR_CORRUPT_DATA;
                }
                else if( CSLFindString( papszParameters,
                                        poNode->GetChild(0)->GetValue()) == -1)
                {
                    CPLDebug( "OGRSpatialReference::Validate",
                              "Unrecognized PARAMETER `%s'.",
                              poNode->GetChild(0)->GetValue() );

                    return OGRERR_UNSUPPORTED_SRS;
                }
            }
            else if( EQUAL(poNode->GetValue(), "PROJECTION") )
            {
                if( poNode->GetChildCount() != 1 &&
                    poNode->GetChildCount() != 2 )
                {
                    CPLDebug( "OGRSpatialReference::Validate",
                              "PROJECTION has wrong number of children (%d), "
                              "not 1 or 2 as expected.",
                              poNode->GetChildCount() );

                    return OGRERR_CORRUPT_DATA;
                }
                else if( CSLFindString( papszProjectionSupported,
                                        poNode->GetChild(0)->GetValue()) == -1
                      && CSLFindString( papszProjectionUnsupported,
                                        poNode->GetChild(0)->GetValue()) == -1)
                {
                    CPLDebug( "OGRSpatialReference::Validate",
                              "Unrecognized PROJECTION `%s'.",
                              poNode->GetChild(0)->GetValue() );

                    return OGRERR_UNSUPPORTED_SRS;
                }
                else if( CSLFindString( papszProjectionSupported,
                                        poNode->GetChild(0)->GetValue()) == -1)
                {
                    CPLDebug( "OGRSpatialReference::Validate",
                              "Unsupported, but recognized PROJECTION `%s'.",
                              poNode->GetChild(0)->GetValue() );

                    return OGRERR_UNSUPPORTED_SRS;
                }

                if( poNode->GetChildCount() == 2 )
                {
                    poNode = poNode->GetChild(1);
                    if( EQUAL(poNode->GetValue(), "AUTHORITY") )
                    {
                        const OGRErr eErr = ValidateAuthority(poNode);
                        if( eErr != OGRERR_NONE )
                            return eErr;
                    }
                    else
                    {
                        CPLDebug( "OGRSpatialReference::Validate",
                                  "Unexpected child for PROJECTION `%s'.",
                                  poNode->GetValue() );

                        return OGRERR_CORRUPT_DATA;
                    }
                }
            }
            else if( EQUAL(poNode->GetValue(), "AUTHORITY") )
            {
                OGRErr eErr = ValidateAuthority(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else if( EQUAL(poNode->GetValue(), "AXIS") )
            {
                OGRErr eErr = ValidateAxis(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else if( EQUAL(poNode->GetValue(), "EXTENSION") )
            {
                // We do not try to control the sub-organization of
                // EXTENSION nodes.
            }
            else
            {
                CPLDebug( "OGRSpatialReference::Validate",
                          "Unexpected child for PROJCS `%s'.",
                          poNode->GetValue() );

                return OGRERR_CORRUPT_DATA;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Validate GEOGCS if found.                                       */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poGEOGCS = poRoot->GetNode( "GEOGCS" );

    if( poGEOGCS != nullptr )
    {
        for( int i = 1; i < poGEOGCS->GetChildCount(); i++ )
        {
            OGR_SRSNode *poNode = poGEOGCS->GetChild(i);

            if( EQUAL(poNode->GetValue(), "DATUM") )
            {
                // Validated elsewhere.
            }
            else if( EQUAL(poNode->GetValue(), "PRIMEM") )
            {
                if( poNode->GetChildCount() < 2
                    || poNode->GetChildCount() > 3 )
                {
                    CPLDebug( "OGRSpatialReference::Validate",
                              "PRIMEM has wrong number of children (%d), "
                              "not 2 or 3 as expected.",
                              poNode->GetChildCount() );

                    return OGRERR_CORRUPT_DATA;
                }
            }
            else if( EQUAL(poNode->GetValue(), "UNIT") )
            {
                const OGRErr eErr = ValidateUnit(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else if( EQUAL(poNode->GetValue(), "AXIS") )
            {
                const OGRErr eErr = ValidateAxis(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else if( EQUAL(poNode->GetValue(), "EXTENSION") )
            {
                // We do not try to control the sub-organization of
                // EXTENSION nodes.
            }
            else if( EQUAL(poNode->GetValue(), "AUTHORITY") )
            {
                OGRErr eErr = ValidateAuthority(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else
            {
                CPLDebug( "OGRSpatialReference::Validate",
                          "Unexpected child for GEOGCS `%s'.",
                          poNode->GetValue() );

                return OGRERR_CORRUPT_DATA;
            }
        }

        if( poGEOGCS->GetNode("DATUM") == nullptr )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "No DATUM child in GEOGCS." );

            return OGRERR_CORRUPT_DATA;
        }
    }

/* -------------------------------------------------------------------- */
/*      Validate DATUM/SPHEROID.                                        */
/* -------------------------------------------------------------------- */
    OGR_SRSNode *poDATUM = poRoot->GetNode( "DATUM" );

    if( poDATUM != nullptr )
    {
        if( poDATUM->GetChildCount() == 0 )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "DATUM has no children." );

            return OGRERR_CORRUPT_DATA;
        }

        bool bGotSpheroid = false;

        for( int i = 1; i < poDATUM->GetChildCount(); i++ )
        {
            OGR_SRSNode *poNode = poDATUM->GetChild(i);

            if( EQUAL(poNode->GetValue(), "SPHEROID") )
            {
                OGR_SRSNode *poSPHEROID = poDATUM->GetChild(1);
                bGotSpheroid = true;

                if( poSPHEROID->GetChildCount() != 3
                    && poSPHEROID->GetChildCount() != 4 )
                {
                    CPLDebug( "OGRSpatialReference::Validate",
                              "SPHEROID has wrong number of children (%d), "
                              "not 3 or 4 as expected.",
                              poSPHEROID->GetChildCount() );

                    return OGRERR_CORRUPT_DATA;
                }
                else if( CPLAtof(poSPHEROID->GetChild(1)->GetValue()) == 0.0 )
                {
                    CPLDebug( "OGRSpatialReference::Validate",
                              "SPHEROID semi-major axis is zero (%s)!",
                              poSPHEROID->GetChild(1)->GetValue() );
                    return OGRERR_CORRUPT_DATA;
                }
            }
            else if( EQUAL(poNode->GetValue(), "AUTHORITY") )
            {
                OGRErr eErr = ValidateAuthority(poNode);
                if( eErr != OGRERR_NONE )
                    return eErr;
            }
            else if( EQUAL(poNode->GetValue(), "TOWGS84") )
            {
                if( poNode->GetChildCount() != 3
                    && poNode->GetChildCount() != 7)
                {
                    CPLDebug("OGRSpatialReference::Validate",
                             "TOWGS84 has wrong number of children (%d), "
                             "not 3 or 7.",
                             poNode->GetChildCount() );
                    return OGRERR_CORRUPT_DATA;
                }
            }
            else if( EQUAL(poNode->GetValue(), "EXTENSION") )
            {
                // We do not try to control the sub-organization of
                // EXTENSION nodes.
            }
            else
            {
                CPLDebug( "OGRSpatialReference::Validate",
                          "Unexpected child for DATUM `%s'.",
                          poNode->GetValue() );

                return OGRERR_CORRUPT_DATA;
            }
        }

        if( !bGotSpheroid )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "No SPHEROID child in DATUM." );

            return OGRERR_CORRUPT_DATA;
        }
    }

/* -------------------------------------------------------------------- */
/*      If this is projected, try to validate the detailed set of       */
/*      parameters used for the projection.                             */
/* -------------------------------------------------------------------- */
    const OGRErr eErr = ValidateProjection(poRoot);
    if( eErr != OGRERR_NONE )
        return eErr;

    return OGRERR_NONE;
}

/************************************************************************/
/*                            OSRValidate()                             */
/************************************************************************/
/**
 * \brief Validate SRS tokens.
 *
 * This function is the same as the C++ method OGRSpatialReference::Validate().
 */
OGRErr OSRValidate( OGRSpatialReferenceH hSRS )

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

    return OGRSpatialReference::FromHandle(hSRS)->Validate();
}

/************************************************************************/
/*                             IsAliasFor()                             */
/************************************************************************/

/**
 * \brief Return whether the first string passed in an acceptable alias for the
 * second string according to the AliasGroupList
 *
 * @param pszParm1 first string
 * @param pszParm2 second string
 *
 * @return TRUE if both strings are aliases according to the
 * AliasGroupList, FALSE otherwise
 */
int OGRSpatialReference::IsAliasFor( const char *pszParm1,
                                     const char *pszParm2 )

{
/* -------------------------------------------------------------------- */
/*      Look for a group containing pszParm1.                           */
/* -------------------------------------------------------------------- */
    int iGroup = 0;  // Used after for.
    for( ; papszAliasGroupList[iGroup] != nullptr; iGroup++ )
    {
        int i = iGroup;  // Used after for.

        for( ; papszAliasGroupList[i] != nullptr; i++ )
        {
            if( EQUAL(pszParm1, papszAliasGroupList[i]) )
                break;
        }

        if( papszAliasGroupList[i] == nullptr )
            iGroup = i;
        else
            break;
    }

/* -------------------------------------------------------------------- */
/*      Does this group also contain pszParm2?                          */
/* -------------------------------------------------------------------- */
    for( ; papszAliasGroupList[iGroup] != nullptr; iGroup++ )
    {
        if( EQUAL(papszAliasGroupList[iGroup], pszParm2) )
            return TRUE;
    }

    return FALSE;
}

/************************************************************************/
/*                         ValidateProjection()                         */
/************************************************************************/

/**
 * \brief Validate the current PROJECTION's arguments.
 *
 * @return OGRERR_NONE if the PROJECTION's arguments validate, an error code
 *         otherwise
 */
OGRErr OGRSpatialReference::ValidateProjection(OGR_SRSNode *poRoot)
{
    OGR_SRSNode *poPROJCS = poRoot->GetNode( "PROJCS" );

    if( poPROJCS == nullptr )
        return OGRERR_NONE;

    if( poPROJCS->GetNode( "PROJECTION" ) == nullptr )
    {
        CPLDebug( "OGRSpatialReference::Validate",
                  "PROJCS does not have PROJECTION subnode." );
        return OGRERR_CORRUPT_DATA;
    }

/* -------------------------------------------------------------------- */
/*      Find the matching group in the proj and parms table.            */
/* -------------------------------------------------------------------- */
    const char *pszProjection =
        poPROJCS->GetNode("PROJECTION")->GetChild(0)->GetValue();

    int iOffset = 0;  // Used after for.
    for( ;
         papszProjWithParms[iOffset] != nullptr
             && !EQUAL(papszProjWithParms[iOffset], pszProjection); )
    {
        while( papszProjWithParms[iOffset] != nullptr )
            iOffset++;
        iOffset++;
    }

    if( papszProjWithParms[iOffset] == nullptr )
        return OGRERR_UNSUPPORTED_SRS;

    iOffset++;

/* -------------------------------------------------------------------- */
/*      Check all parameters, and verify they are in the permitted      */
/*      list.                                                           */
/* -------------------------------------------------------------------- */
    for( int iNode = 0; iNode < poPROJCS->GetChildCount(); iNode++ )
    {
        OGR_SRSNode *poParm = poPROJCS->GetChild(iNode);

        if( !EQUAL(poParm->GetValue(), "PARAMETER") )
            continue;

        OGR_SRSNode *poParmNameNode = poParm->GetChild(0);
        if( poParmNameNode == nullptr )
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "Parameter name for PROJECTION %s is corrupt.",
                       pszProjection );
            return OGRERR_CORRUPT_DATA;
        }
        const char *pszParmName = poParmNameNode->GetValue();

        int i = iOffset;  // Used after for.
        for( ; papszProjWithParms[i] != nullptr; i++ )
        {
            if( EQUAL(papszProjWithParms[i], pszParmName) )
                break;
        }

        // This parameter is not an exact match, is it an alias?
        if( papszProjWithParms[i] == nullptr )
        {
            for( i = iOffset; papszProjWithParms[i] != nullptr; i++ )
            {
                if( IsAliasFor(papszProjWithParms[i], pszParmName) )
                    break;
            }

            if( papszProjWithParms[i] == nullptr )
            {
                CPLDebug( "OGRSpatialReference::Validate",
                          "PARAMETER %s for PROJECTION %s is not permitted.",
                          pszParmName, pszProjection );
                return OGRERR_CORRUPT_DATA;
            }
            else
            {
                CPLDebug( "OGRSpatialReference::Validate",
                          "PARAMETER %s for PROJECTION %s is an alias for %s.",
                          pszParmName, pszProjection,
                          papszProjWithParms[i] );
                return OGRERR_CORRUPT_DATA;
            }
        }
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                         ValidateVertDatum()                          */
/************************************************************************/

/**
 * \brief Validate the current VERT_DATUM's arguments.
 *
 * @return OGRERR_NONE if the VERT_DATUM's arguments validate, an error code
 *         otherwise
 */
OGRErr OGRSpatialReference::ValidateVertDatum( OGR_SRSNode *poRoot )
{
    if( !EQUAL(poRoot->GetValue(), "VERT_DATUM") )
        return OGRERR_NONE;

    if( poRoot->GetChildCount() < 2 )
    {
        CPLDebug( "OGRSpatialReference::Validate",
                  "Invalid number of children : %d", poRoot->GetChildCount() );
        return OGRERR_CORRUPT_DATA;
    }

    if( atoi(poRoot->GetChild(1)->GetValue()) == 0 )
    {
        CPLDebug( "OGRSpatialReference::Validate",
                  "Invalid value for datum type (%s) : must be a number",
                  poRoot->GetChild(1)->GetValue());
        return OGRERR_CORRUPT_DATA;
    }

    for( int i = 2; i < poRoot->GetChildCount(); i++ )
    {
        OGR_SRSNode *poNode = poRoot->GetChild(i);

        if( EQUAL(poNode->GetValue(), "AUTHORITY") )
        {
            OGRErr eErr = ValidateAuthority(poNode);
            if( eErr != OGRERR_NONE )
                return eErr;
        }
        else if( EQUAL(poNode->GetValue(), "EXTENSION") )
        {
            // We do not try to control the sub-organization of
            // EXTENSION nodes.
        }
        else
        {
            CPLDebug( "OGRSpatialReference::Validate",
                      "Unexpected child for VERT_DATUM `%s'.",
                      poNode->GetValue() );

            return OGRERR_CORRUPT_DATA;
        }
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                         ValidateAuthority()                          */
/************************************************************************/

/**
 * \brief Validate the current AUTHORITY's arguments.
 *
 * @return OGRERR_NONE if the AUTHORITY's arguments validate, an error code
 *         otherwise
 */
OGRErr OGRSpatialReference::ValidateAuthority( OGR_SRSNode *poRoot )
{
    if( !EQUAL(poRoot->GetValue(), "AUTHORITY") )
        return OGRERR_NONE;

    if( poRoot->GetChildCount() != 2 )
    {
        CPLDebug( "OGRSpatialReference::Validate",
                  "AUTHORITY has wrong number of children (%d), not 2.",
                  poRoot->GetChildCount() );
        return OGRERR_CORRUPT_DATA;
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                           ValidateAxis()                             */
/************************************************************************/

/**
 * \brief Validate the current AXIS's arguments.
 *
 * @return OGRERR_NONE if the AXIS's arguments validate, an error code
 *         otherwise
 */
OGRErr OGRSpatialReference::ValidateAxis( OGR_SRSNode *poRoot )
{
    if( !EQUAL(poRoot->GetValue(), "AXIS") )
        return OGRERR_NONE;

    if( poRoot->GetChildCount() != 2 )
    {
        CPLDebug( "OGRSpatialReference::Validate",
                    "AXIS has wrong number of children (%d), not 2.",
                    poRoot->GetChildCount() );
        return OGRERR_CORRUPT_DATA;
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                           ValidateUnit()                             */
/************************************************************************/

/**
 * \brief Validate the current UNIT's arguments.
 *
 * @return OGRERR_NONE if the UNIT's arguments validate, an error code
 *         otherwise
 */
OGRErr OGRSpatialReference::ValidateUnit( OGR_SRSNode *poRoot )
{
    if( !EQUAL(poRoot->GetValue(), "UNIT") )
        return OGRERR_NONE;

    if( poRoot->GetChildCount() != 2
        && poRoot->GetChildCount() != 3 )
    {
        CPLDebug( "OGRSpatialReference::Validate",
                  "UNIT has wrong number of children (%d), not 2.",
                  poRoot->GetChildCount() );
        return OGRERR_CORRUPT_DATA;
    }
    else if( CPLAtof(poRoot->GetChild(1)->GetValue()) == 0.0 )
    {
        CPLDebug( "OGRSpatialReference::Validate",
                  "UNIT does not appear to have meaningful"
                  "coefficient (%s).",
                  poRoot->GetChild(1)->GetValue() );
        return OGRERR_CORRUPT_DATA;
    }

    return OGRERR_NONE;
}
