/******************************************************************************
 * $Id: ogrpolygon.cpp 27044 2014-03-16 23:41:27Z rouault $
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  The OGRPolygon geometry class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam
 * Copyright (c) 2008-2014, 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 "ogr_geometry.h"
#include "ogr_p.h"
#include "ogr_geos.h"
#include "ogr_api.h"

CPL_CVSID("$Id: ogrpolygon.cpp 27044 2014-03-16 23:41:27Z rouault $");

/************************************************************************/
/*                             OGRPolygon()                             */
/************************************************************************/

/**
 * \brief Create an empty polygon.
 */

OGRPolygon::OGRPolygon()

{
    nRingCount = 0;
    papoRings = NULL;
}

/************************************************************************/
/*                            ~OGRPolygon()                             */
/************************************************************************/

OGRPolygon::~OGRPolygon()

{
    empty();
}

/************************************************************************/
/*                               clone()                                */
/************************************************************************/

OGRGeometry *OGRPolygon::clone() const

{
    OGRPolygon  *poNewPolygon;

    poNewPolygon = new OGRPolygon;
    poNewPolygon->assignSpatialReference( getSpatialReference() );

    for( int i = 0; i < nRingCount; i++ )
    {
        poNewPolygon->addRing( papoRings[i] );
    }

    return poNewPolygon;
}

/************************************************************************/
/*                               empty()                                */
/************************************************************************/

void OGRPolygon::empty()

{
    if( papoRings != NULL )
    {
        for( int i = 0; i < nRingCount; i++ )
        {
            delete papoRings[i];
        }
        OGRFree( papoRings );
    }

    papoRings = NULL;
    nRingCount = 0;
}

/************************************************************************/
/*                          getGeometryType()                           */
/************************************************************************/

OGRwkbGeometryType OGRPolygon::getGeometryType() const

{
    if( nCoordDimension == 3 )
        return wkbPolygon25D;
    else
        return wkbPolygon;
}


/************************************************************************/
/*                            getDimension()                            */
/************************************************************************/

int OGRPolygon::getDimension() const

{
    return 2;
}

/************************************************************************/
/*                            flattenTo2D()                             */
/************************************************************************/

void OGRPolygon::flattenTo2D()

{
    for( int iRing = 0; iRing < nRingCount; iRing++ )
        papoRings[iRing]->flattenTo2D();

    nCoordDimension = 2;
}

/************************************************************************/
/*                          getGeometryName()                           */
/************************************************************************/

const char * OGRPolygon::getGeometryName() const

{
    return "POLYGON";
}

/************************************************************************/
/*                          getExteriorRing()                           */
/************************************************************************/

/**
 * \brief Fetch reference to external polygon ring.
 *
 * Note that the returned ring pointer is to an internal data object of
 * the OGRPolygon.  It should not be modified or deleted by the application,
 * and the pointer is only valid till the polygon is next modified.  Use
 * the OGRGeometry::clone() method to make a separate copy within the
 * application.
 *
 * Relates to the SFCOM IPolygon::get_ExteriorRing() method.
 *
 * @return pointer to external ring.  May be NULL if the OGRPolygon is empty.
 */

OGRLinearRing *OGRPolygon::getExteriorRing()

{
    if( nRingCount > 0 )
        return papoRings[0];
    else
        return NULL;
}

const OGRLinearRing *OGRPolygon::getExteriorRing() const

{
    if( nRingCount > 0 )
        return papoRings[0];
    else
        return NULL;
}

/************************************************************************/
/*                          stealExteriorRing()                         */
/************************************************************************/

/**
 * \brief "Steal" reference to external polygon ring.
 *
 * After the call to that function, only call to stealInteriorRing() or
 * destruction of the OGRPolygon is valid. Other operations may crash.
 *
 * @return pointer to external ring.  May be NULL if the OGRPolygon is empty.
 */

OGRLinearRing *OGRPolygon::stealExteriorRing()
{
    if( nRingCount == 0 )
        return NULL;
    OGRLinearRing *poRet = papoRings[0];
    papoRings[0] = NULL;
    return poRet;
}

/************************************************************************/
/*                        getNumInteriorRings()                         */
/************************************************************************/

/**
 * \brief Fetch the number of internal rings.
 *
 * Relates to the SFCOM IPolygon::get_NumInteriorRings() method.
 *
 * @return count of internal rings, zero or more.
 */


int OGRPolygon::getNumInteriorRings() const

{
    if( nRingCount > 0 )
        return nRingCount-1;
    else
        return 0;
}

/************************************************************************/
/*                          getInteriorRing()                           */
/************************************************************************/

/**
 * \brief Fetch reference to indicated internal ring.
 *
 * Note that the returned ring pointer is to an internal data object of
 * the OGRPolygon.  It should not be modified or deleted by the application,
 * and the pointer is only valid till the polygon is next modified.  Use
 * the OGRGeometry::clone() method to make a separate copy within the
 * application.
 *
 * Relates to the SFCOM IPolygon::get_InternalRing() method.
 *
 * @param iRing internal ring index from 0 to getNumInternalRings() - 1.
 *
 * @return pointer to interior ring.  May be NULL.
 */

OGRLinearRing *OGRPolygon::getInteriorRing( int iRing )

{
    if( iRing < 0 || iRing >= nRingCount-1 )
        return NULL;
    else
        return papoRings[iRing+1];
}

const OGRLinearRing *OGRPolygon::getInteriorRing( int iRing ) const

{
    if( iRing < 0 || iRing >= nRingCount-1 )
        return NULL;
    else
        return papoRings[iRing+1];
}

/************************************************************************/
/*                          stealInteriorRing()                         */
/************************************************************************/

/**
 * \brief "Steal" reference to indicated interior ring.
 *
 * After the call to that function, only call to stealInteriorRing() or
 * destruction of the OGRPolygon is valid. Other operations may crash.
 *
 * @param iRing internal ring index from 0 to getNumInternalRings() - 1.
 * @return pointer to interior ring.  May be NULL.
 */

OGRLinearRing *OGRPolygon::stealInteriorRing(int iRing)
{
    if( iRing < 0 || iRing >= nRingCount-1 )
        return NULL;
    OGRLinearRing *poRet = papoRings[iRing+1];
    papoRings[iRing+1] = NULL;
    return poRet;
}

/************************************************************************/
/*                              addRing()                               */
/************************************************************************/

/**
 * \brief Add a ring to a polygon.
 *
 * If the polygon has no external ring (it is empty) this will be used as
 * the external ring, otherwise it is used as an internal ring.  The passed
 * OGRLinearRing remains the responsibility of the caller (an internal copy
 * is made).
 *
 * This method has no SFCOM analog.
 *
 * @param poNewRing ring to be added to the polygon.
 */

void OGRPolygon::addRing( OGRLinearRing * poNewRing )

{
    papoRings = (OGRLinearRing **) OGRRealloc( papoRings,
                                               sizeof(void*) * (nRingCount+1));

    papoRings[nRingCount] = new OGRLinearRing( poNewRing );

    nRingCount++;

    if( poNewRing->getCoordinateDimension() == 3 )
        nCoordDimension = 3;
}

/************************************************************************/
/*                          addRingDirectly()                           */
/************************************************************************/

/**
 * \brief Add a ring to a polygon.
 *
 * If the polygon has no external ring (it is empty) this will be used as
 * the external ring, otherwise it is used as an internal ring.  Ownership
 * of the passed ring is assumed by the OGRPolygon, but otherwise this
 * method operates the same as OGRPolygon::AddRing().
 *
 * This method has no SFCOM analog.
 *
 * @param poNewRing ring to be added to the polygon.
 */

void OGRPolygon::addRingDirectly( OGRLinearRing * poNewRing )

{
    papoRings = (OGRLinearRing **) OGRRealloc( papoRings,
                                               sizeof(void*) * (nRingCount+1));

    papoRings[nRingCount] = poNewRing;

    nRingCount++;


    if( poNewRing->getCoordinateDimension() == 3 )
        nCoordDimension = 3;
}

/************************************************************************/
/*                              WkbSize()                               */
/*                                                                      */
/*      Return the size of this object in well known binary             */
/*      representation including the byte order, and type information.  */
/************************************************************************/

int OGRPolygon::WkbSize() const

{
    int         nSize = 9;
    int         b3D = getCoordinateDimension() == 3;

    for( int i = 0; i < nRingCount; i++ )
    {
        nSize += papoRings[i]->_WkbSize( b3D );
    }

    return nSize;
}

/************************************************************************/
/*                           importFromWkb()                            */
/*                                                                      */
/*      Initialize from serialized stream in well known binary          */
/*      format.                                                         */
/************************************************************************/

OGRErr OGRPolygon::importFromWkb( unsigned char * pabyData,
                                  int nSize )

{
    OGRwkbByteOrder     eByteOrder;
    int                 nDataOffset;
    
    if( nSize < 9 && nSize != -1 )
        return OGRERR_NOT_ENOUGH_DATA;

/* -------------------------------------------------------------------- */
/*      Get the byte order byte.                                        */
/* -------------------------------------------------------------------- */
    eByteOrder = DB2_V72_FIX_BYTE_ORDER((OGRwkbByteOrder) *pabyData);
    if (!( eByteOrder == wkbXDR || eByteOrder == wkbNDR ))
        return OGRERR_CORRUPT_DATA;

/* -------------------------------------------------------------------- */
/*      Get the geometry feature type.  For now we assume that          */
/*      geometry type is between 0 and 255 so we only have to fetch     */
/*      one byte.                                                       */
/* -------------------------------------------------------------------- */

    OGRBoolean b3D;
    OGRwkbGeometryType eGeometryType;
    OGRErr err = OGRReadWKBGeometryType( pabyData, &eGeometryType, &b3D );

    if( err != OGRERR_NONE || eGeometryType != wkbPolygon )
        return OGRERR_CORRUPT_DATA;

    if( b3D )
        nCoordDimension = 3;
    else
        nCoordDimension = 2;


/* -------------------------------------------------------------------- */
/*      Do we already have some rings?                                  */
/* -------------------------------------------------------------------- */
    if( nRingCount != 0 )
    {
        for( int iRing = 0; iRing < nRingCount; iRing++ )
            delete papoRings[iRing];

        OGRFree( papoRings );
        papoRings = NULL;
    }
    
/* -------------------------------------------------------------------- */
/*      Get the ring count.                                             */
/* -------------------------------------------------------------------- */
    memcpy( &nRingCount, pabyData + 5, 4 );
    
    if( OGR_SWAP( eByteOrder ) )
        nRingCount = CPL_SWAP32(nRingCount);

    if (nRingCount < 0 || nRingCount > INT_MAX / 4)
    {
        nRingCount = 0;
        return OGRERR_CORRUPT_DATA;
    }

    /* Each ring has a minimum of 4 bytes (point count) */
    if (nSize != -1 && nSize - 9 < nRingCount * 4)
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Length of input WKB is too small" );
        nRingCount = 0;
        return OGRERR_NOT_ENOUGH_DATA;
    }

    papoRings = (OGRLinearRing **) VSIMalloc2(sizeof(void*), nRingCount);
    if (nRingCount != 0 && papoRings == NULL)
    {
        nRingCount = 0;
        return OGRERR_NOT_ENOUGH_MEMORY;
    }

    nDataOffset = 9;
    if( nSize != -1 )
        nSize -= nDataOffset;

/* -------------------------------------------------------------------- */
/*      Get the rings.                                                  */
/* -------------------------------------------------------------------- */
    for( int iRing = 0; iRing < nRingCount; iRing++ )
    {
        OGRErr  eErr;
        
        papoRings[iRing] = new OGRLinearRing();
        eErr = papoRings[iRing]->_importFromWkb( eByteOrder, b3D,
                                                 pabyData + nDataOffset,
                                                 nSize );
        if( eErr != OGRERR_NONE )
        {
            delete papoRings[iRing];
            nRingCount = iRing;
            return eErr;
        }

        if( nSize != -1 )
            nSize -= papoRings[iRing]->_WkbSize( b3D );

        nDataOffset += papoRings[iRing]->_WkbSize( b3D );
    }
    
    return OGRERR_NONE;
}

/************************************************************************/
/*                            exportToWkb()                             */
/*                                                                      */
/*      Build a well known binary representation of this object.        */
/************************************************************************/

OGRErr  OGRPolygon::exportToWkb( OGRwkbByteOrder eByteOrder,
                                 unsigned char * pabyData,
                                 OGRwkbVariant eWkbVariant ) const

{
    int         nOffset;
    int         b3D = getCoordinateDimension() == 3;
    
/* -------------------------------------------------------------------- */
/*      Set the byte order.                                             */
/* -------------------------------------------------------------------- */
    pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER((unsigned char) eByteOrder);

/* -------------------------------------------------------------------- */
/*      Set the geometry feature type.                                  */
/* -------------------------------------------------------------------- */
    GUInt32 nGType = getGeometryType();

    if ( eWkbVariant == wkbVariantIso )
        nGType = getIsoGeometryType();
    
    if( eByteOrder == wkbNDR )
        nGType = CPL_LSBWORD32( nGType );
    else
        nGType = CPL_MSBWORD32( nGType );

    memcpy( pabyData + 1, &nGType, 4 );
    
/* -------------------------------------------------------------------- */
/*      Copy in the raw data.                                           */
/* -------------------------------------------------------------------- */
    if( OGR_SWAP( eByteOrder ) )
    {
        int     nCount;

        nCount = CPL_SWAP32( nRingCount );
        memcpy( pabyData+5, &nCount, 4 );
    }
    else
    {
        memcpy( pabyData+5, &nRingCount, 4 );
    }
    
    nOffset = 9;
    
/* ==================================================================== */
/*      Serialize each of the rings.                                    */
/* ==================================================================== */
    for( int iRing = 0; iRing < nRingCount; iRing++ )
    {
        papoRings[iRing]->_exportToWkb( eByteOrder, b3D,
                                        pabyData + nOffset );

        nOffset += papoRings[iRing]->_WkbSize(b3D);
    }
    
    return OGRERR_NONE;
}

/************************************************************************/
/*                           importFromWkt()                            */
/*                                                                      */
/*      Instantiate from well known text format.  Currently this is     */
/*      `POLYGON ((x y, x y, ...),(x y, ...),...)'.                     */
/************************************************************************/

OGRErr OGRPolygon::importFromWkt( char ** ppszInput )

{
    char        szToken[OGR_WKT_TOKEN_MAX];
    const char  *pszInput = *ppszInput;

/* -------------------------------------------------------------------- */
/*      Clear existing rings.                                           */
/* -------------------------------------------------------------------- */
    empty();

/* -------------------------------------------------------------------- */
/*      Read and verify the ``POLYGON'' keyword token.                  */
/* -------------------------------------------------------------------- */
    pszInput = OGRWktReadToken( pszInput, szToken );

    if( !EQUAL(szToken,"POLYGON") )
        return OGRERR_CORRUPT_DATA;

/* -------------------------------------------------------------------- */
/*      Check for EMPTY ...                                             */
/* -------------------------------------------------------------------- */
    const char *pszPreScan;
    int bHasZ = FALSE, bHasM = FALSE;

    pszPreScan = OGRWktReadToken( pszInput, szToken );
    if( EQUAL(szToken,"EMPTY") )
    {
        *ppszInput = (char *) pszPreScan;
        empty();
        return OGRERR_NONE;
    }

/* -------------------------------------------------------------------- */
/*      Check for Z, M or ZM. Will ignore the Measure                   */
/* -------------------------------------------------------------------- */
    else if( EQUAL(szToken,"Z") )
    {
        bHasZ = TRUE;
    }
    else if( EQUAL(szToken,"M") )
    {
        bHasM = TRUE;
    }
    else if( EQUAL(szToken,"ZM") )
    {
        bHasZ = TRUE;
        bHasM = TRUE;
    }

    if (bHasZ || bHasM)
    {
        pszInput = pszPreScan;
        pszPreScan = OGRWktReadToken( pszInput, szToken );
        if( EQUAL(szToken,"EMPTY") )
        {
            *ppszInput = (char *) pszPreScan;
            empty();
            /* FIXME?: In theory we should store the dimension and M presence */
            /* if we want to allow round-trip with ExportToWKT v1.2 */
            return OGRERR_NONE;
        }
    }

    if( !EQUAL(szToken,"(") )
        return OGRERR_CORRUPT_DATA;

    if ( !bHasZ && !bHasM )
    {
        /* Test for old-style POLYGON(EMPTY) */
        pszPreScan = OGRWktReadToken( pszPreScan, szToken );
        if( EQUAL(szToken,"EMPTY") )
        {
            pszPreScan = OGRWktReadToken( pszPreScan, szToken );

            if( EQUAL(szToken,",") )
            {
                /* This is OK according to SFSQL SPEC. */
            }
            else if( !EQUAL(szToken,")") )
                return OGRERR_CORRUPT_DATA;
            else
            {
                *ppszInput = (char *) pszPreScan;
                empty();
                return OGRERR_NONE;
            }
        }
    }

    /* Skip first '(' */
    pszInput = OGRWktReadToken( pszInput, szToken );

/* ==================================================================== */
/*      Read each ring in turn.  Note that we try to reuse the same     */
/*      point list buffer from ring to ring to cut down on              */
/*      allocate/deallocate overhead.                                   */
/* ==================================================================== */
    OGRRawPoint *paoPoints = NULL;
    int         nMaxPoints = 0, nMaxRings = 0;
    double      *padfZ = NULL;

    nCoordDimension = 2;
    
    do
    {
        int     nPoints = 0;

        const char* pszNext = OGRWktReadToken( pszInput, szToken );
        if (EQUAL(szToken,"EMPTY"))
        {
/* -------------------------------------------------------------------- */
/*      Do we need to grow the ring array?                              */
/* -------------------------------------------------------------------- */
            if( nRingCount == nMaxRings )
            {
                nMaxRings = nMaxRings * 2 + 1;
                papoRings = (OGRLinearRing **)
                    CPLRealloc(papoRings, nMaxRings * sizeof(OGRLinearRing*));
            }
            papoRings[nRingCount] = new OGRLinearRing();
            nRingCount++;

            pszInput = OGRWktReadToken( pszNext, szToken );
            if ( !EQUAL(szToken, ",") )
                break;

            continue;
        }

/* -------------------------------------------------------------------- */
/*      Read points for one ring from input.                            */
/* -------------------------------------------------------------------- */
        pszInput = OGRWktReadPoints( pszInput, &paoPoints, &padfZ, &nMaxPoints,
                                     &nPoints );

        if( pszInput == NULL || nPoints == 0 )
        {
            CPLFree( paoPoints );
            return OGRERR_CORRUPT_DATA;
        }
        
/* -------------------------------------------------------------------- */
/*      Do we need to grow the ring array?                              */
/* -------------------------------------------------------------------- */
        if( nRingCount == nMaxRings )
        {
            nMaxRings = nMaxRings * 2 + 1;
            papoRings = (OGRLinearRing **)
                CPLRealloc(papoRings, nMaxRings * sizeof(OGRLinearRing*));
        }

/* -------------------------------------------------------------------- */
/*      Create the new ring, and assign to ring list.                   */
/* -------------------------------------------------------------------- */
        papoRings[nRingCount] = new OGRLinearRing();
        /* Ignore Z array when we have a POLYGON M */
        if (bHasM && !bHasZ)
            papoRings[nRingCount]->setPoints( nPoints, paoPoints, NULL );
        else
            papoRings[nRingCount]->setPoints( nPoints, paoPoints, padfZ );

        nRingCount++;

        if( padfZ && !(bHasM && !bHasZ) )
            nCoordDimension = 3;

/* -------------------------------------------------------------------- */
/*      Read the delimeter following the ring.                          */
/* -------------------------------------------------------------------- */
        
        pszInput = OGRWktReadToken( pszInput, szToken );
    } while( szToken[0] == ',' );

/* -------------------------------------------------------------------- */
/*      freak if we don't get a closing bracket.                        */
/* -------------------------------------------------------------------- */
    CPLFree( paoPoints );
    CPLFree( padfZ );

    if( szToken[0] != ')' )
        return OGRERR_CORRUPT_DATA;
    
    *ppszInput = (char *) pszInput;
    return OGRERR_NONE;
}

/************************************************************************/
/*                            exportToWkt()                             */
/*                                                                      */
/*      Translate this structure into it's well known text format       */
/*      equivelent.  This could be made alot more CPU efficient!        */
/************************************************************************/

OGRErr OGRPolygon::exportToWkt( char ** ppszDstText ) const

{
    char        **papszRings;
    int         iRing, nCumulativeLength = 0, nNonEmptyRings = 0;
    OGRErr      eErr;
    int         bMustWriteComma = FALSE;

/* -------------------------------------------------------------------- */
/*      If we have no valid exterior ring, return POLYGON EMPTY.        */
/* -------------------------------------------------------------------- */
    if (getExteriorRing() == NULL || 
        getExteriorRing()->IsEmpty() )
    {
        *ppszDstText = CPLStrdup("POLYGON EMPTY");
        return OGRERR_NONE;
    }

/* -------------------------------------------------------------------- */
/*      Build a list of strings containing the stuff for each ring.     */
/* -------------------------------------------------------------------- */
    papszRings = (char **) CPLCalloc(sizeof(char *),nRingCount);

    for( iRing = 0; iRing < nRingCount; iRing++ )
    {
        papoRings[iRing]->setCoordinateDimension( getCoordinateDimension() );
        if( papoRings[iRing]->getNumPoints() == 0 )
        {
            papszRings[iRing] = NULL;
            continue;
        }

        eErr = papoRings[iRing]->exportToWkt( &(papszRings[iRing]) );
        if( eErr != OGRERR_NONE )
            goto error;

        CPLAssert( EQUALN(papszRings[iRing],"LINEARRING (", 12) );
        nCumulativeLength += strlen(papszRings[iRing] + 11);

        nNonEmptyRings++;
    }

/* -------------------------------------------------------------------- */
/*      Allocate exactly the right amount of space for the              */
/*      aggregated string.                                              */
/* -------------------------------------------------------------------- */
    *ppszDstText = (char *) VSIMalloc(nCumulativeLength + nNonEmptyRings + 11);

    if( *ppszDstText == NULL )
    {
        eErr = OGRERR_NOT_ENOUGH_MEMORY;
        goto error;
    }

/* -------------------------------------------------------------------- */
/*      Build up the string, freeing temporary strings as we go.        */
/* -------------------------------------------------------------------- */
    strcpy( *ppszDstText, "POLYGON (" );
    nCumulativeLength = strlen(*ppszDstText);

    for( iRing = 0; iRing < nRingCount; iRing++ )
    {                                                           
        if( papszRings[iRing] == NULL )
        {
            CPLDebug( "OGR", "OGRPolygon::exportToWkt() - skipping empty ring.");
            continue;
        }

        if( bMustWriteComma )
            (*ppszDstText)[nCumulativeLength++] = ',';
        bMustWriteComma = TRUE;
        
        int nRingLen = strlen(papszRings[iRing] + 11);
        memcpy( *ppszDstText + nCumulativeLength, papszRings[iRing] + 11, nRingLen );
        nCumulativeLength += nRingLen;
        VSIFree( papszRings[iRing] );
    }

    (*ppszDstText)[nCumulativeLength++] = ')';
    (*ppszDstText)[nCumulativeLength] = '\0';

    CPLFree( papszRings );

    return OGRERR_NONE;

error:
    for( iRing = 0; iRing < nRingCount; iRing++ )
        CPLFree(papszRings[iRing]);
    CPLFree(papszRings);
    return eErr;
}

/************************************************************************/
/*                           PointOnSurface()                           */
/************************************************************************/

int OGRPolygon::PointOnSurface( OGRPoint *poPoint ) const

{
    if( poPoint == NULL || poPoint->IsEmpty() )
        return OGRERR_FAILURE;

    OGRGeometryH hInsidePoint = OGR_G_PointOnSurface( (OGRGeometryH) this );
    if( hInsidePoint == NULL )
        return OGRERR_FAILURE;

    OGRPoint *poInsidePoint = (OGRPoint *) hInsidePoint;
    if( poInsidePoint->IsEmpty() )
        poPoint->empty();
    else
    {
        poPoint->setX( poInsidePoint->getX() );
        poPoint->setY( poInsidePoint->getY() );
    }

    return OGRERR_NONE;
}


/************************************************************************/
/*                            getEnvelope()                             */
/************************************************************************/

void OGRPolygon::getEnvelope( OGREnvelope * psEnvelope ) const

{
    OGREnvelope         oRingEnv;
    int                 bExtentSet = FALSE;

    for( int iRing = 0; iRing < nRingCount; iRing++ )
    {
        if (!papoRings[iRing]->IsEmpty())
        {
            if (!bExtentSet)
            {
                papoRings[iRing]->getEnvelope( psEnvelope );
                bExtentSet = TRUE;
            }
            else
            {
                papoRings[iRing]->getEnvelope( &oRingEnv );

                if( psEnvelope->MinX > oRingEnv.MinX )
                    psEnvelope->MinX = oRingEnv.MinX;
                if( psEnvelope->MinY > oRingEnv.MinY )
                    psEnvelope->MinY = oRingEnv.MinY;
                if( psEnvelope->MaxX < oRingEnv.MaxX )
                    psEnvelope->MaxX = oRingEnv.MaxX;
                if( psEnvelope->MaxY < oRingEnv.MaxY )
                    psEnvelope->MaxY = oRingEnv.MaxY;
            }
        }
    }

    if (!bExtentSet)
    {
        psEnvelope->MinX = psEnvelope->MinY = 0;
        psEnvelope->MaxX = psEnvelope->MaxY = 0;
    }
}

/************************************************************************/
/*                            getEnvelope()                             */
/************************************************************************/
 
void OGRPolygon::getEnvelope( OGREnvelope3D * psEnvelope ) const

{
    OGREnvelope3D       oRingEnv;
    int                 bExtentSet = FALSE;

    for( int iRing = 0; iRing < nRingCount; iRing++ )
    {
        if (!papoRings[iRing]->IsEmpty())
        {
            if (!bExtentSet)
            {
                papoRings[iRing]->getEnvelope( psEnvelope );
                bExtentSet = TRUE;
            }
            else
            {
                papoRings[iRing]->getEnvelope( &oRingEnv );

                if( psEnvelope->MinX > oRingEnv.MinX )
                    psEnvelope->MinX = oRingEnv.MinX;
                if( psEnvelope->MinY > oRingEnv.MinY )
                    psEnvelope->MinY = oRingEnv.MinY;
                if( psEnvelope->MaxX < oRingEnv.MaxX )
                    psEnvelope->MaxX = oRingEnv.MaxX;
                if( psEnvelope->MaxY < oRingEnv.MaxY )
                    psEnvelope->MaxY = oRingEnv.MaxY;

                if( psEnvelope->MinZ > oRingEnv.MinZ )
                    psEnvelope->MinZ = oRingEnv.MinZ;
                if( psEnvelope->MaxZ < oRingEnv.MaxZ )
                    psEnvelope->MaxZ = oRingEnv.MaxZ;
            }
        }
    }

    if (!bExtentSet)
    {
        psEnvelope->MinX = psEnvelope->MinY = psEnvelope->MinZ = 0;
        psEnvelope->MaxX = psEnvelope->MaxY = psEnvelope->MaxZ = 0;
    }
}

/************************************************************************/
/*                               Equal()                                */
/************************************************************************/

OGRBoolean OGRPolygon::Equals( OGRGeometry * poOther ) const

{
    OGRPolygon *poOPoly = (OGRPolygon *) poOther;

    if( poOPoly == this )
        return TRUE;
    
    if( poOther->getGeometryType() != getGeometryType() )
        return FALSE;

    if ( IsEmpty() && poOther->IsEmpty() )
        return TRUE;

    if( getNumInteriorRings() != poOPoly->getNumInteriorRings() )
        return FALSE;

    if( getExteriorRing() == NULL && poOPoly->getExteriorRing() == NULL )
        /* ok */;
    else if( getExteriorRing() == NULL || poOPoly->getExteriorRing() == NULL )
        return FALSE;
    else if( !getExteriorRing()->Equals( poOPoly->getExteriorRing() ) )
        return FALSE;
    
    // we should eventually test the SRS.

    for( int iRing = 0; iRing < getNumInteriorRings(); iRing++ )
    {
        if( !getInteriorRing(iRing)->Equals(poOPoly->getInteriorRing(iRing)) )
            return FALSE;
    }

    return TRUE;
}

/************************************************************************/
/*                             transform()                              */
/************************************************************************/

OGRErr OGRPolygon::transform( OGRCoordinateTransformation *poCT )

{
#ifdef DISABLE_OGRGEOM_TRANSFORM
    return OGRERR_FAILURE;
#else
    for( int iRing = 0; iRing < nRingCount; iRing++ )
    {
        OGRErr  eErr;

        eErr = papoRings[iRing]->transform( poCT );
        if( eErr != OGRERR_NONE )
        {
            if( iRing != 0 )
            {
                CPLDebug("OGR", 
                         "OGRPolygon::transform() failed for a ring other\n"
                         "than the first, meaning some rings are transformed\n"
                         "and some are not!\n" );

                return OGRERR_FAILURE;
            }

            return eErr;
        }
    }

    assignSpatialReference( poCT->GetTargetCS() );

    return OGRERR_NONE;
#endif
}

/************************************************************************/
/*                           IsPointOnSurface()                           */
/************************************************************************/

OGRBoolean OGRPolygon::IsPointOnSurface( const OGRPoint * pt) const
{
    if ( NULL == pt)
        return 0;

    for( int iRing = 0; iRing < nRingCount; iRing++ )
    {
        if ( papoRings[iRing]->isPointInRing(pt) )
        {
            return 1;
        }
    }

    return 0;
}

/************************************************************************/
/*                             closeRings()                             */
/************************************************************************/

void OGRPolygon::closeRings()

{
    for( int iRing = 0; iRing < nRingCount; iRing++ )
        papoRings[iRing]->closeRings();
}

/************************************************************************/
/*                              get_Area()                              */
/************************************************************************/

/**
 * \brief Compute area of polygon.
 *
 * The area is computed as the area of the outer ring less the area of all
 * internal rings. 
 *
 * @return computed area.
 */

double OGRPolygon::get_Area() const

{
    double dfArea = 0.0;

    if( getExteriorRing() != NULL )
    {
        int iRing;

        dfArea = getExteriorRing()->get_Area();

        for( iRing = 0; iRing < getNumInteriorRings(); iRing++ )
            dfArea -= getInteriorRing( iRing )->get_Area();
    }

    return dfArea;
}

/************************************************************************/
/*                       setCoordinateDimension()                       */
/************************************************************************/

void OGRPolygon::setCoordinateDimension( int nNewDimension )

{
    for( int iRing = 0; iRing < nRingCount; iRing++ )
        papoRings[iRing]->setCoordinateDimension( nNewDimension );

    OGRGeometry::setCoordinateDimension( nNewDimension );
}


/************************************************************************/
/*                               IsEmpty()                              */
/************************************************************************/

OGRBoolean OGRPolygon::IsEmpty(  ) const
{
    for( int iRing = 0; iRing < nRingCount; iRing++ )
        if ( papoRings[iRing]->IsEmpty() == FALSE )
            return FALSE;
    return TRUE;
}

/************************************************************************/
/*                       OGRPolygon::segmentize()                       */
/************************************************************************/

void OGRPolygon::segmentize( double dfMaxLength )
{
    for( int iRing = 0; iRing < nRingCount; iRing++ )
        papoRings[iRing]->segmentize(dfMaxLength);
}

/************************************************************************/
/*                               swapXY()                               */
/************************************************************************/

void OGRPolygon::swapXY()
{
    for( int iRing = 0; iRing < nRingCount; iRing++ )
        papoRings[iRing]->swapXY();
}
