/******************************************************************************
 *
 * 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 spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "cpl_port.h"
#include "ogr_geometry.h"

#include <cstring>
#include <cstddef>
#include <new>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_vsi.h"
#include "ogr_api.h"
#include "ogr_core.h"
#include "ogr_geos.h"
#include "ogr_sfcgal.h"
#include "ogr_p.h"

/************************************************************************/
/*        OGRPolygon(double x1, double y1, double x2, double y2)        */
/************************************************************************/

/**
 * \brief Construct a rectangular polygon from opposite corner coordinates.
 *
 * @since GDAL 3.11
 */

OGRPolygon::OGRPolygon(double x1, double y1, double x2, double y2)
{
    auto poLR = std::make_unique<OGRLinearRing>();
    poLR->addPoint(x1, y1);
    poLR->addPoint(x1, y2);
    poLR->addPoint(x2, y2);
    poLR->addPoint(x2, y1);
    poLR->addPoint(x1, y1);
    addRingDirectly(poLR.release());
}

/************************************************************************/
/*               OGRPolygon(const OGREnvelope &envelope)                */
/************************************************************************/

/**
 * \brief Construct a rectangular polygon from opposite corner coordinates
 * of a OGREnvelope.
 *
 * @since GDAL 3.11
 */

OGRPolygon::OGRPolygon(const OGREnvelope &envelope)
    : OGRPolygon(envelope.MinX, envelope.MinY, envelope.MaxX, envelope.MaxY)
{
}

/************************************************************************/
/*                   OGRPolygon( const OGRPolygon& )                    */
/************************************************************************/

/**
 * \brief Copy constructor.
 */

OGRPolygon::OGRPolygon(const OGRPolygon &) = default;

/************************************************************************/
/*                    operator=( const OGRPolygon&)                     */
/************************************************************************/

/**
 * \brief Assignment operator.
 */

OGRPolygon &OGRPolygon::operator=(const OGRPolygon &other)
{
    if (this != &other)
    {
        OGRCurvePolygon::operator=(other);
    }
    return *this;
}

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

OGRPolygon *OGRPolygon::clone() const

{
    auto ret = new (std::nothrow) OGRPolygon(*this);
    if (ret)
    {
        if (ret->WkbSize() != WkbSize())
        {
            delete ret;
            ret = nullptr;
        }
    }
    return ret;
}

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

OGRwkbGeometryType OGRPolygon::getGeometryType() const

{
    if ((flags & OGR_G_3D) && (flags & OGR_G_MEASURED))
        return wkbPolygonZM;
    else if (flags & OGR_G_MEASURED)
        return wkbPolygonM;
    else if (flags & OGR_G_3D)
        return wkbPolygon25D;
    else
        return wkbPolygon;
}

/************************************************************************/
/*                          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 (oCC.nCurveCount > 0)
        return oCC.papoCurves[0]->toLinearRing();

    return nullptr;
}

/**
 * \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.
 */

const OGRLinearRing *OGRPolygon::getExteriorRing() const

{
    if (oCC.nCurveCount > 0)
        return oCC.papoCurves[0]->toLinearRing();

    return nullptr;
}

/************************************************************************/
/*                         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()
{
    return stealExteriorRingCurve()->toLinearRing();
}

/************************************************************************/
/*                          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 getNumInteriorRings() - 1.
 *
 * @return pointer to interior ring.  May be NULL.
 */

OGRLinearRing *OGRPolygon::getInteriorRing(int iRing)

{
    if (iRing < 0 || iRing >= oCC.nCurveCount - 1)
        return nullptr;

    return oCC.papoCurves[iRing + 1]->toLinearRing();
}

/**
 * \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 getNumInteriorRings() - 1.
 *
 * @return pointer to interior ring.  May be NULL.
 */

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

{
    if (iRing < 0 || iRing >= oCC.nCurveCount - 1)
        return nullptr;

    return oCC.papoCurves[iRing + 1]->toLinearRing();
}

/************************************************************************/
/*                         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 getNumInteriorRings() - 1.
 * @return pointer to interior ring.  May be NULL.
 */

OGRLinearRing *OGRPolygon::stealInteriorRing(int iRing)
{
    if (iRing < 0 || iRing >= oCC.nCurveCount - 1)
        return nullptr;
    OGRLinearRing *poRet = oCC.papoCurves[iRing + 1]->toLinearRing();
    oCC.papoCurves[iRing + 1] = nullptr;
    return poRet;
}

/*! @cond Doxygen_Suppress */

/************************************************************************/
/*                         isRingCorrectType()                          */
/************************************************************************/
bool OGRPolygon::isRingCorrectType(const OGRCurve *poRing) const
{
    return poRing != nullptr && EQUAL(poRing->getGeometryName(), "LINEARRING");
}

/************************************************************************/
/*                             checkRing()                              */
/************************************************************************/

bool OGRPolygon::checkRing(const OGRCurve *poNewRing) const
{
    if (!isRingCorrectType(poNewRing))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Wrong curve type. Expected LINEARRING.");
        return false;
    }

    if (!poNewRing->IsEmpty() && !poNewRing->get_IsClosed())
    {
        // This configuration option name must be the same as in
        // OGRCurvePolygon::checkRing()
        const char *pszEnvVar =
            CPLGetConfigOption("OGR_GEOMETRY_ACCEPT_UNCLOSED_RING", nullptr);
        if (pszEnvVar != nullptr && !CPLTestBool(pszEnvVar))
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Non closed ring detected.");
            return false;
        }
        else
        {
            CPLError(CE_Warning, CPLE_AppDefined, "Non closed ring detected.%s",
                     pszEnvVar == nullptr
                         ? " To avoid accepting it, set the "
                           "OGR_GEOMETRY_ACCEPT_UNCLOSED_RING configuration "
                           "option to NO"
                         : "");
        }
    }

    return true;
}

/*! @endcond */

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

size_t OGRPolygon::WkbSize() const

{
    size_t nSize = 9;

    for (auto &&poRing : *this)
    {
        nSize += poRing->_WkbSize(flags);
    }

    return nSize;
}

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

OGRErr OGRPolygon::importFromWkb(const unsigned char *pabyData, size_t nSize,
                                 OGRwkbVariant eWkbVariant,
                                 size_t &nBytesConsumedOut)

{
    OGRwkbByteOrder eByteOrder = wkbNDR;
    size_t nDataOffset = 0;

    if (oCC.nCurveCount == 1 && flags == 0 && nSize >= 9 &&
        pabyData[0] == wkbNDR &&
        memcmp(pabyData + 1, "\x03\x00\x00\x00\x01\x00\x00\x00", 8) == 0)
    {
        // Optimization to import a Intel-ordered 1-ring polygon on
        // top of an existing 1-ring polygon, to save dynamic memory
        // allocations.
        size_t nBytesConsumedRing = 0;
        nDataOffset = 9;
        // cppcheck-suppress knownConditionTrueFalse
        if (nSize != static_cast<size_t>(-1))
            nSize -= nDataOffset;
        OGRErr eErr =
            cpl::down_cast<OGRLinearRing *>(oCC.papoCurves[0])
                ->_importFromWkb(eByteOrder, flags, pabyData + nDataOffset,
                                 nSize, nBytesConsumedRing);
        if (eErr == OGRERR_NONE)
        {
            nBytesConsumedOut = nDataOffset + nBytesConsumedRing;
        }
        else
        {
            empty();
        }

        return eErr;
    }

    nBytesConsumedOut = 0;

    // coverity[tainted_data]
    OGRErr eErr = oCC.importPreambleFromWkb(this, pabyData, nSize, nDataOffset,
                                            eByteOrder, 4, eWkbVariant);
    if (eErr != OGRERR_NONE)
        return eErr;

    /* -------------------------------------------------------------------- */
    /*      Get the rings.                                                  */
    /* -------------------------------------------------------------------- */
    for (int iRing = 0; iRing < oCC.nCurveCount; iRing++)
    {
        OGRLinearRing *poLR = new OGRLinearRing();
        oCC.papoCurves[iRing] = poLR;
        size_t nBytesConsumedRing = 0;
        eErr = poLR->_importFromWkb(eByteOrder, flags, pabyData + nDataOffset,
                                    nSize, nBytesConsumedRing);
        if (eErr != OGRERR_NONE)
        {
            delete oCC.papoCurves[iRing];
            oCC.nCurveCount = iRing;
            return eErr;
        }

        CPLAssert(nBytesConsumedRing > 0);
        if (nSize != static_cast<size_t>(-1))
        {
            CPLAssert(nSize >= nBytesConsumedRing);
            nSize -= nBytesConsumedRing;
        }

        nDataOffset += nBytesConsumedRing;
    }
    nBytesConsumedOut = nDataOffset;

    return OGRERR_NONE;
}

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

OGRErr OGRPolygon::exportToWkb(unsigned char *pabyData,
                               const OGRwkbExportOptions *psOptions) const

{
    if (psOptions == nullptr)
    {
        static const OGRwkbExportOptions defaultOptions;
        psOptions = &defaultOptions;
    }

    /* -------------------------------------------------------------------- */
    /*      Set the byte order.                                             */
    /* -------------------------------------------------------------------- */
    pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER(
        static_cast<unsigned char>(psOptions->eByteOrder));

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

    if (psOptions->eWkbVariant == wkbVariantPostGIS1)
    {
        nGType = wkbFlatten(nGType);
        if (Is3D())
            // Explicitly set wkb25DBit.
            nGType =
                static_cast<OGRwkbGeometryType>(nGType | wkb25DBitInternalUse);
        if (IsMeasured())
            nGType = static_cast<OGRwkbGeometryType>(nGType | 0x40000000);
    }
    else if (psOptions->eWkbVariant == wkbVariantIso)
        nGType = getIsoGeometryType();

    if (OGR_SWAP(psOptions->eByteOrder))
    {
        nGType = CPL_SWAP32(nGType);
    }

    memcpy(pabyData + 1, &nGType, 4);

    /* -------------------------------------------------------------------- */
    /*      Copy in the raw data.                                           */
    /* -------------------------------------------------------------------- */
    if (OGR_SWAP(psOptions->eByteOrder))
    {
        const int nCount = CPL_SWAP32(oCC.nCurveCount);
        memcpy(pabyData + 5, &nCount, 4);
    }
    else
    {
        memcpy(pabyData + 5, &oCC.nCurveCount, 4);
    }

    /* ==================================================================== */
    /*      Serialize each of the rings.                                    */
    /* ==================================================================== */
    size_t nOffset = 9;

    for (auto &&poRing : *this)
    {
        poRing->_exportToWkb(flags, pabyData + nOffset, psOptions);

        nOffset += poRing->_WkbSize(flags);
    }

    return OGRERR_NONE;
}

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

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

{
    int bHasZ = FALSE;
    int bHasM = FALSE;
    bool bIsEmpty = false;
    OGRErr eErr = importPreambleFromWkt(ppszInput, &bHasZ, &bHasM, &bIsEmpty);
    flags = 0;
    if (eErr != OGRERR_NONE)
        return eErr;
    if (bHasZ)
        flags |= OGR_G_3D;
    if (bHasM)
        flags |= OGR_G_MEASURED;
    if (bIsEmpty)
        return OGRERR_NONE;

    OGRRawPoint *paoPoints = nullptr;
    int nMaxPoints = 0;
    double *padfZ = nullptr;

    eErr = importFromWKTListOnly(ppszInput, bHasZ, bHasM, paoPoints, nMaxPoints,
                                 padfZ);

    CPLFree(paoPoints);
    CPLFree(padfZ);

    return eErr;
}

/*! @cond Doxygen_Suppress */
/************************************************************************/
/*                        importFromWKTListOnly()                       */
/*                                                                      */
/*      Instantiate from "((x y, x y, ...),(x y, ...),...)"             */
/************************************************************************/

OGRErr OGRPolygon::importFromWKTListOnly(const char **ppszInput, int bHasZ,
                                         int bHasM, OGRRawPoint *&paoPoints,
                                         int &nMaxPoints, double *&padfZ)

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

    // Skip first '('.
    pszInput = OGRWktReadToken(pszInput, szToken);
    if (EQUAL(szToken, "EMPTY"))
    {
        *ppszInput = pszInput;
        return OGRERR_NONE;
    }
    if (!EQUAL(szToken, "("))
        return OGRERR_CORRUPT_DATA;

    /* ==================================================================== */
    /*      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.                                   */
    /* ==================================================================== */
    int nMaxRings = 0;
    double *padfM = nullptr;

    do
    {
        const char *pszNext = OGRWktReadToken(pszInput, szToken);
        if (EQUAL(szToken, "EMPTY"))
        {
            /* --------------------------------------------------------------------
             */
            /*      Do we need to grow the ring array? */
            /* --------------------------------------------------------------------
             */
            if (oCC.nCurveCount == nMaxRings)
            {
                nMaxRings = nMaxRings * 2 + 1;
                oCC.papoCurves = static_cast<OGRCurve **>(CPLRealloc(
                    oCC.papoCurves, nMaxRings * sizeof(OGRLinearRing *)));
            }
            oCC.papoCurves[oCC.nCurveCount] = new OGRLinearRing();
            oCC.nCurveCount++;

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

            continue;
        }

        /* --------------------------------------------------------------------
         */
        /*      Read points for one ring from input. */
        /* --------------------------------------------------------------------
         */
        int nPoints = 0;
        int flagsFromInput = flags;
        if (flagsFromInput == 0)
        {
            // Flags was not set, this is not called by us.
            if (bHasM)
                flagsFromInput |= OGR_G_MEASURED;
            if (bHasZ)
                flagsFromInput |= OGR_G_3D;
        }

        pszInput = OGRWktReadPointsM(pszInput, &paoPoints, &padfZ, &padfM,
                                     &flagsFromInput, &nMaxPoints, &nPoints);
        if (pszInput == nullptr || nPoints == 0)
        {
            CPLFree(padfM);
            return OGRERR_CORRUPT_DATA;
        }
        if ((flagsFromInput & OGR_G_3D) && !(flags & OGR_G_3D))
        {
            flags |= OGR_G_3D;
            bHasZ = TRUE;
        }
        if ((flagsFromInput & OGR_G_MEASURED) && !(flags & OGR_G_MEASURED))
        {
            flags |= OGR_G_MEASURED;
            bHasM = TRUE;
        }

        /* --------------------------------------------------------------------
         */
        /*      Do we need to grow the ring array? */
        /* --------------------------------------------------------------------
         */
        if (oCC.nCurveCount == nMaxRings)
        {
            nMaxRings = nMaxRings * 2 + 1;
            oCC.papoCurves = static_cast<OGRCurve **>(CPLRealloc(
                oCC.papoCurves, nMaxRings * sizeof(OGRLinearRing *)));
        }

        /* --------------------------------------------------------------------
         */
        /*      Create the new ring, and assign to ring list. */
        /* --------------------------------------------------------------------
         */
        OGRLinearRing *poLR = new OGRLinearRing();
        oCC.papoCurves[oCC.nCurveCount] = poLR;

        if (bHasM && bHasZ)
            poLR->setPoints(nPoints, paoPoints, padfZ, padfM);
        else if (bHasM)
            poLR->setPointsM(nPoints, paoPoints, padfM);
        else if (bHasZ)
            poLR->setPoints(nPoints, paoPoints, padfZ);
        else
            poLR->setPoints(nPoints, paoPoints);

        oCC.nCurveCount++;

        /* --------------------------------------------------------------------
         */
        /*      Read the delimiter following the ring. */
        /* --------------------------------------------------------------------
         */

        pszInput = OGRWktReadToken(pszInput, szToken);
    } while (szToken[0] == ',');

    CPLFree(padfM);

    /* -------------------------------------------------------------------- */
    /*      freak if we don't get a closing bracket.                        */
    /* -------------------------------------------------------------------- */

    if (szToken[0] != ')')
        return OGRERR_CORRUPT_DATA;

    *ppszInput = pszInput;
    return OGRERR_NONE;
}

/*! @endcond */

/************************************************************************/
/*                            exportToWkt()                             */
/*                                                                      */
/*      Translate this structure into its well known text format        */
/*      equivalent.  This could be made a lot more CPU efficient.       */
/************************************************************************/

std::string OGRPolygon::exportToWkt(const OGRWktOptions &opts,
                                    OGRErr *err) const
{
    std::string wkt;
    /* -------------------------------------------------------------------- */
    /*      If we have no valid exterior ring, return POLYGON EMPTY.        */
    /* -------------------------------------------------------------------- */
    wkt = getGeometryName();
    wkt += wktTypeString(opts.variant);
    if (getExteriorRing() == nullptr || getExteriorRing()->IsEmpty())
        wkt += "EMPTY";

    /* -------------------------------------------------------------------- */
    /*      Build a list of strings containing the stuff for each ring.     */
    /* -------------------------------------------------------------------- */

    else
    {
        try
        {
            bool first(true);
            wkt += '(';
            for (int iRing = 0; iRing < oCC.nCurveCount; iRing++)
            {
                OGRLinearRing *poLR = oCC.papoCurves[iRing]->toLinearRing();
                if (poLR->getNumPoints())
                {
                    if (!first)
                        wkt += ',';
                    first = false;
                    OGRErr subgeomErr = OGRERR_NONE;
                    std::string tempWkt = poLR->exportToWkt(opts, &subgeomErr);
                    if (subgeomErr != OGRERR_NONE)
                    {
                        if (err)
                            *err = subgeomErr;
                        return std::string();
                    }

                    // Remove leading "LINEARRING..."
                    wkt += tempWkt.substr(tempWkt.find_first_of('('));
                }
            }
            wkt += ')';
        }
        catch (const std::bad_alloc &e)
        {
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
            if (err)
                *err = OGRERR_FAILURE;
            return std::string();
        }
    }

    if (err)
        *err = OGRERR_NONE;
    return wkt;
}

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

/** Return whether the point is on the surface.
 * @return TRUE or FALSE
 */
OGRBoolean OGRPolygon::IsPointOnSurface(const OGRPoint *pt) const
{
    if (nullptr == pt)
        return FALSE;

    bool bOnSurface = false;
    // The point must be in the outer ring, but not in the inner rings
    int iRing = 0;
    for (auto &&poRing : *this)
    {
        if (poRing->isPointInRing(pt))
        {
            if (iRing == 0)
            {
                bOnSurface = true;
            }
            else
            {
                return false;
            }
        }
        else
        {
            if (iRing == 0)
            {
                return false;
            }
        }
        iRing++;
    }

    return bOnSurface;
}

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

void OGRPolygon::closeRings()

{
    for (auto &&poRing : *this)
        poRing->closeRings();
}

/************************************************************************/
/*                          CurvePolyToPoly()                           */
/************************************************************************/

OGRPolygon *
OGRPolygon::CurvePolyToPoly(CPL_UNUSED double dfMaxAngleStepSizeDegrees,
                            CPL_UNUSED const char *const *papszOptions) const
{
    return clone();
}

/************************************************************************/
/*                          hasCurveGeometry()                          */
/************************************************************************/

OGRBoolean OGRPolygon::hasCurveGeometry(CPL_UNUSED int bLookForNonLinear) const
{
    return FALSE;
}

/************************************************************************/
/*                         getLinearGeometry()                          */
/************************************************************************/

OGRGeometry *
OGRPolygon::getLinearGeometry(double dfMaxAngleStepSizeDegrees,
                              const char *const *papszOptions) const
{
    return OGRGeometry::getLinearGeometry(dfMaxAngleStepSizeDegrees,
                                          papszOptions);
}

/************************************************************************/
/*                          getCurveGeometry()                          */
/************************************************************************/

OGRGeometry *OGRPolygon::getCurveGeometry(const char *const *papszOptions) const
{
    OGRCurvePolygon *poCC = new OGRCurvePolygon();
    poCC->assignSpatialReference(getSpatialReference());
    bool bHasCurveGeometry = false;
    for (auto &&poRing : *this)
    {
        auto poSubGeom = poRing->getCurveGeometry(papszOptions);
        if (wkbFlatten(poSubGeom->getGeometryType()) != wkbLineString)
            bHasCurveGeometry = true;
        poCC->addRingDirectly(poSubGeom->toCurve());
    }
    if (!bHasCurveGeometry)
    {
        delete poCC;
        return clone();
    }
    return poCC;
}

/*! @cond Doxygen_Suppress */
/************************************************************************/
/*                         CastToCurvePolygon()                         */
/************************************************************************/

/**
 * \brief Cast to curve polygon.
 *
 * The passed in geometry is consumed and a new one returned .
 *
 * @param poPoly the input geometry - ownership is passed to the method.
 * @return new geometry.
 */

OGRCurvePolygon *OGRPolygon::CastToCurvePolygon(OGRPolygon *poPoly)
{
    OGRCurvePolygon *poCP = new OGRCurvePolygon();
    poCP->set3D(poPoly->Is3D());
    poCP->setMeasured(poPoly->IsMeasured());
    poCP->assignSpatialReference(poPoly->getSpatialReference());
    poCP->oCC.nCurveCount = poPoly->oCC.nCurveCount;
    poCP->oCC.papoCurves = poPoly->oCC.papoCurves;
    poPoly->oCC.nCurveCount = 0;
    poPoly->oCC.papoCurves = nullptr;

    for (auto &&poRing : *poCP)
    {
        poRing = OGRLinearRing::CastToLineString(poRing->toLinearRing());
    }

    delete poPoly;
    return poCP;
}

/************************************************************************/
/*                         GetCasterToPolygon()                         */
/************************************************************************/

static OGRPolygon *CasterToPolygon(OGRSurface *poSurface)
{
    return poSurface->toPolygon();
}

OGRSurfaceCasterToPolygon OGRPolygon::GetCasterToPolygon() const
{
    return ::CasterToPolygon;
}

/************************************************************************/
/*                   OGRSurfaceCasterToCurvePolygon()                   */
/************************************************************************/

OGRCurvePolygon *OGRPolygon::CasterToCurvePolygon(OGRSurface *poSurface)
{
    return OGRPolygon::CastToCurvePolygon(poSurface->toPolygon());
}

OGRSurfaceCasterToCurvePolygon OGRPolygon::GetCasterToCurvePolygon() const
{
    return OGRPolygon::CasterToCurvePolygon;
}

/*! @endcond */
