/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  The OGRCompoundCurve geometry class.
 * Author:   Even Rouault, even dot rouault at spatialys dot com
 *
 ******************************************************************************
 * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys dot com>
 *
 * 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_geometry.h"

#include <cmath>
#include <cstddef>

#include "cpl_error.h"
#include "ogr_core.h"
#include "ogr_geometry.h"
#include "ogr_p.h"
#include "ogr_spatialref.h"

CPL_CVSID("$Id: ogrcompoundcurve.cpp c7d51c5ead794772b42f3c58c394bfff6045f8d6 2019-08-22 09:59:35 +0200 Even Rouault $")

/************************************************************************/
/*                         OGRCompoundCurve()                           */
/************************************************************************/

/**
 * \brief Create an empty compound curve.
 */

OGRCompoundCurve::OGRCompoundCurve() = default;

/************************************************************************/
/*             OGRCompoundCurve( const OGRCompoundCurve& )              */
/************************************************************************/

/**
 * \brief Copy constructor.
 *
 * Note: before GDAL 2.1, only the default implementation of the constructor
 * existed, which could be unsafe to use.
 *
 * @since GDAL 2.1
 */

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

/************************************************************************/
/*                         ~OGRCompoundCurve()                          */
/************************************************************************/

OGRCompoundCurve::~OGRCompoundCurve() = default;

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

/**
 * \brief Assignment operator.
 *
 * Note: before GDAL 2.1, only the default implementation of the operator
 * existed, which could be unsafe to use.
 *
 * @since GDAL 2.1
 */

OGRCompoundCurve& OGRCompoundCurve::operator=( const OGRCompoundCurve& other )
{
    if( this != &other)
    {
        OGRCurve::operator=( other );

        oCC = other.oCC;
    }
    return *this;
}

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

OGRwkbGeometryType OGRCompoundCurve::getGeometryType() const

{
    if( (flags & OGR_G_3D) && (flags & OGR_G_MEASURED) )
        return wkbCompoundCurveZM;
    else if( flags & OGR_G_MEASURED )
        return wkbCompoundCurveM;
    else if( flags & OGR_G_3D )
        return wkbCompoundCurveZ;
    else
        return wkbCompoundCurve;
}

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

const char * OGRCompoundCurve::getGeometryName() const

{
    return "COMPOUNDCURVE";
}

/************************************************************************/
/*                              WkbSize()                               */
/************************************************************************/
int OGRCompoundCurve::WkbSize() const
{
    return oCC.WkbSize();
}

/************************************************************************/
/*                       addCurveDirectlyFromWkt()                      */
/************************************************************************/

OGRErr OGRCompoundCurve::addCurveDirectlyFromWkb( OGRGeometry* poSelf,
                                                  OGRCurve* poCurve )
{
    OGRCompoundCurve* poCC = poSelf->toCompoundCurve();
    return poCC->addCurveDirectlyInternal( poCurve, 1e-14, FALSE );
}

/************************************************************************/
/*                           importFromWkb()                            */
/************************************************************************/

OGRErr OGRCompoundCurve::importFromWkb( const unsigned char * pabyData,
                                        int nSize,
                                        OGRwkbVariant eWkbVariant,
                                       int& nBytesConsumedOut )
{
    OGRwkbByteOrder eByteOrder = wkbNDR;
    int nDataOffset = 0;
    // coverity[tainted_data]
    OGRErr eErr = oCC.importPreambleFromWkb(this, pabyData, nSize, nDataOffset,
                                             eByteOrder, 9, eWkbVariant);
    if( eErr != OGRERR_NONE )
        return eErr;

    eErr =  oCC.importBodyFromWkb(this, pabyData + nDataOffset, nSize,
                                 FALSE,  // bAcceptCompoundCurve
                                 addCurveDirectlyFromWkb,
                                 eWkbVariant,
                                 nBytesConsumedOut);
    if( eErr == OGRERR_NONE )
        nBytesConsumedOut += nDataOffset;
    return eErr;
}

/************************************************************************/
/*                            exportToWkb()                             */
/************************************************************************/
OGRErr OGRCompoundCurve::exportToWkb( OGRwkbByteOrder eByteOrder,
                                      unsigned char * pabyData,
                                      OGRwkbVariant eWkbVariant ) const
{
    // Does not make sense for new geometries, so patch it.
    if( eWkbVariant == wkbVariantOldOgc )
        eWkbVariant = wkbVariantIso;
    return oCC.exportToWkb(this, eByteOrder, pabyData, eWkbVariant);
}

/************************************************************************/
/*                       addCurveDirectlyFromWkt()                      */
/************************************************************************/

OGRErr OGRCompoundCurve::addCurveDirectlyFromWkt( OGRGeometry* poSelf,
                                                  OGRCurve* poCurve )
{
    return poSelf->toCompoundCurve()->addCurveDirectly(poCurve);
}

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

OGRErr OGRCompoundCurve::importFromWkt( const char ** ppszInput )
{
    return importCurveCollectionFromWkt( ppszInput,
                                         FALSE, // bAllowEmptyComponent
                                         TRUE, // bAllowLineString
                                         TRUE, // bAllowCurve
                                         FALSE, // bAllowCompoundCurve
                                         addCurveDirectlyFromWkt );
}

/************************************************************************/
/*                            exportToWkt()                             */
/************************************************************************/
std::string OGRCompoundCurve::exportToWkt(const OGRWktOptions& opts,
                                          OGRErr *err) const
{
    return oCC.exportToWkt(this, opts, err);
}

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

OGRGeometry *OGRCompoundCurve::clone() const
{
    OGRCompoundCurve *poNewCC = new OGRCompoundCurve;
    poNewCC->assignSpatialReference( getSpatialReference() );
    poNewCC->flags = flags;

    for( int i = 0; i < oCC.nCurveCount; i++ )
    {
        poNewCC->addCurve( oCC.papoCurves[i] );
    }

    return poNewCC;
}

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

void OGRCompoundCurve::empty()
{
    oCC.empty(this);
}

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

void OGRCompoundCurve::getEnvelope( OGREnvelope * psEnvelope ) const
{
    oCC.getEnvelope(psEnvelope);
}

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

void OGRCompoundCurve::getEnvelope( OGREnvelope3D * psEnvelope ) const
{
    oCC.getEnvelope(psEnvelope);
}

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

OGRBoolean OGRCompoundCurve::IsEmpty() const
{
    return oCC.IsEmpty();
}

/************************************************************************/
/*                             get_Length()                             */
/*                                                                      */
/*      For now we return a simple euclidean 2D distance.               */
/************************************************************************/

double OGRCompoundCurve::get_Length() const
{
    double dfLength = 0.0;
    for( int iGeom = 0; iGeom < oCC.nCurveCount; iGeom++ )
        dfLength += oCC.papoCurves[iGeom]->get_Length();
    return dfLength;
}

/************************************************************************/
/*                             StartPoint()                             */
/************************************************************************/

void OGRCompoundCurve::StartPoint(OGRPoint *p) const
{
    CPLAssert(oCC.nCurveCount > 0);
    oCC.papoCurves[0]->StartPoint(p);
}

/************************************************************************/
/*                              EndPoint()                              */
/************************************************************************/

void OGRCompoundCurve::EndPoint(OGRPoint *p) const
{
    CPLAssert(oCC.nCurveCount > 0);
    oCC.papoCurves[oCC.nCurveCount-1]->EndPoint(p);
}

/************************************************************************/
/*                               Value()                                */
/************************************************************************/

void OGRCompoundCurve::Value( double dfDistance, OGRPoint *poPoint ) const
{

    if( dfDistance < 0 )
    {
        StartPoint( poPoint );
        return;
    }

    double dfLength = 0.0;
    for( int iGeom = 0; iGeom < oCC.nCurveCount; iGeom++ )
    {
        const double dfSegLength = oCC.papoCurves[iGeom]->get_Length();
        if( dfSegLength > 0 )
        {
            if( (dfLength <= dfDistance) && ((dfLength + dfSegLength) >=
                                             dfDistance) )
            {
                oCC.papoCurves[iGeom]->Value(dfDistance - dfLength, poPoint);

                return;
            }

            dfLength += dfSegLength;
        }
    }

    EndPoint( poPoint );
}

/************************************************************************/
/*                         CurveToLineInternal()                        */
/************************************************************************/

OGRLineString *
OGRCompoundCurve::CurveToLineInternal( double dfMaxAngleStepSizeDegrees,
                                       const char* const* papszOptions,
                                       int bIsLinearRing ) const
{
    OGRLineString* const poLine = bIsLinearRing
        ? new OGRLinearRing()
        : new OGRLineString();
    poLine->assignSpatialReference(getSpatialReference());
    for( int iGeom = 0; iGeom < oCC.nCurveCount; iGeom++ )
    {
        OGRLineString* poSubLS =
            oCC.papoCurves[iGeom]->CurveToLine(dfMaxAngleStepSizeDegrees,
                                               papszOptions);
        poLine->addSubLineString(poSubLS, (iGeom == 0) ? 0 : 1);
        delete poSubLS;
    }
    return poLine;
}

/************************************************************************/
/*                          CurveToLine()                               */
/************************************************************************/

OGRLineString *
OGRCompoundCurve::CurveToLine( double dfMaxAngleStepSizeDegrees,
                               const char* const* papszOptions ) const
{
    return CurveToLineInternal(dfMaxAngleStepSizeDegrees, papszOptions, FALSE);
}

/************************************************************************/
/*                               Equals()                                */
/************************************************************************/

OGRBoolean OGRCompoundCurve::Equals( const OGRGeometry *poOther ) const
{
    if( poOther == this )
        return TRUE;

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

    return oCC.Equals(&(poOther->toCompoundCurve()->oCC));
}

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

void OGRCompoundCurve::setCoordinateDimension( int nNewDimension )
{
    oCC.setCoordinateDimension( this, nNewDimension );
}

void OGRCompoundCurve::set3D( OGRBoolean bIs3D )
{
    oCC.set3D(this, bIs3D);
}

void OGRCompoundCurve::setMeasured( OGRBoolean bIsMeasured )
{
    oCC.setMeasured(this, bIsMeasured);
}

/************************************************************************/
/*                       assignSpatialReference()                       */
/************************************************************************/

void OGRCompoundCurve::assignSpatialReference( OGRSpatialReference * poSR )
{
    oCC.assignSpatialReference(this, poSR);
}

/************************************************************************/
/*                          getNumCurves()                              */
/************************************************************************/

/**
 * \brief Return the number of curves.
 *
 * Note that the number of curves making this compound curve.
 *
 * Relates to the ISO SQL/MM ST_NumCurves() function.
 *
 * @return number of curves.
 */

int OGRCompoundCurve::getNumCurves() const
{
    return oCC.nCurveCount;
}

/************************************************************************/
/*                           getCurve()                                 */
/************************************************************************/

/**
 * \brief Fetch reference to indicated internal ring.
 *
 * Note that the returned curve pointer is to an internal data object of the
 * OGRCompoundCurve.  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 ISO SQL/MM ST_CurveN() function.
 *
 * @param iRing curve index from 0 to getNumCurves() - 1.
 *
 * @return pointer to curve.  May be NULL.
 */

OGRCurve *OGRCompoundCurve::getCurve( int iRing )
{
    return oCC.getCurve(iRing);
}

/************************************************************************/
/*                           getCurve()                                 */
/************************************************************************/

/**
 * \brief Fetch reference to indicated internal ring.
 *
 * Note that the returned curve pointer is to an internal data object of the
 * OGRCompoundCurve.  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 ISO SQL/MM ST_CurveN() function.
 *
 * @param iCurve curve index from 0 to getNumCurves() - 1.
 *
 * @return pointer to curve.  May be NULL.
 */

const OGRCurve *OGRCompoundCurve::getCurve( int iCurve ) const
{
    return oCC.getCurve(iCurve);
}

/************************************************************************/
/*                           stealCurve()                               */
/************************************************************************/

/**
 * \brief "Steal" reference to curve.
 *
 * @param iCurve curve index from 0 to getNumCurves() - 1.
 *
 * @return pointer to curve.  May be NULL.
 */

OGRCurve* OGRCompoundCurve::stealCurve( int iCurve )
{
    return oCC.stealCurve(iCurve);
}

/************************************************************************/
/*                            addCurve()                                */
/************************************************************************/

/**
 * \brief Add a curve to the container.
 *
 * The passed geometry is cloned to make an internal copy.
 *
 * There is no ISO SQL/MM analog to this method.
 *
 * This method is the same as the C function OGR_G_AddGeometry().
 *
 * @param poCurve geometry to add to the container.
 * @param dfToleranceEps tolerance when checking that the first point of a
 *                       segment matches then end point of the previous one.
 *                       Default value: 1e-14.
 *
 * @return OGRERR_NONE if successful, or OGRERR_FAILURE in case of error
 * (for example if curves are not contiguous)
 */

OGRErr OGRCompoundCurve::addCurve( OGRCurve* poCurve, double dfToleranceEps )
{
    OGRCurve* poClonedCurve = poCurve->clone()->toCurve();
    const OGRErr eErr = addCurveDirectly( poClonedCurve, dfToleranceEps );
    if( eErr != OGRERR_NONE )
        delete poClonedCurve;
    return eErr;
}

/************************************************************************/
/*                          addCurveDirectly()                          */
/************************************************************************/

/**
 * \brief Add a curve directly to the container.
 *
 * Ownership of the passed geometry is taken by the container rather than
 * cloning as addCurve() does.
 *
 * There is no ISO SQL/MM analog to this method.
 *
 * This method is the same as the C function OGR_G_AddGeometryDirectly().
 *
 * @param poCurve geometry to add to the container.
 * @param dfToleranceEps tolerance when checking that the first point of a
 *                       segment matches then end point of the previous one.
 *                       Default value: 1e-14.
 *
 * @return OGRERR_NONE if successful, or OGRERR_FAILURE in case of error
 * (for example if curves are not contiguous)
 */
OGRErr OGRCompoundCurve::addCurveDirectly( OGRCurve* poCurve,
                                           double dfToleranceEps )
{
    return addCurveDirectlyInternal(poCurve, dfToleranceEps, TRUE );
}

OGRErr OGRCompoundCurve::addCurveDirectlyInternal( OGRCurve* poCurve,
                                                   double dfToleranceEps,
                                                   int bNeedRealloc )
{
    if( poCurve->getNumPoints() == 1 )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Invalid curve: not enough points");
        return OGRERR_FAILURE;
    }

    const OGRwkbGeometryType eCurveType =
        wkbFlatten(poCurve->getGeometryType());
    if( EQUAL(poCurve->getGeometryName(), "LINEARRING") )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Linearring not allowed.");
        return OGRERR_FAILURE;
    }
    else if( eCurveType == wkbCompoundCurve )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot add a compound curve inside a compound curve");
        return OGRERR_FAILURE;
    }

    if( oCC.nCurveCount > 0 )
    {
        if( oCC.papoCurves[oCC.nCurveCount-1]->IsEmpty() ||
            poCurve->IsEmpty() )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Non contiguous curves");
            return OGRERR_FAILURE;
        }

        OGRPoint oEnd;
        OGRPoint start;
        oCC.papoCurves[oCC.nCurveCount-1]->EndPoint(&oEnd);
        poCurve->StartPoint(&start);
        if( fabs(oEnd.getX() - start.getX()) > dfToleranceEps ||
            fabs(oEnd.getY() - start.getY()) > dfToleranceEps ||
            fabs(oEnd.getZ() - start.getZ()) > dfToleranceEps )
        {
            poCurve->EndPoint(&start);
            if( fabs(oEnd.getX() - start.getX()) > dfToleranceEps ||
                fabs(oEnd.getY() - start.getY()) > dfToleranceEps ||
                fabs(oEnd.getZ() - start.getZ()) > dfToleranceEps )
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Non contiguous curves");
                return OGRERR_FAILURE;
            }

            CPLDebug("GML", "reversing curve");
            poCurve->toSimpleCurve()->reversePoints();
        }
        // Patch so that it matches exactly.
        poCurve->toSimpleCurve()->setPoint(0, &oEnd);
    }

    return oCC.addCurveDirectly(this, poCurve, bNeedRealloc);
}

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

OGRErr OGRCompoundCurve::transform( OGRCoordinateTransformation *poCT )
{
    return oCC.transform(this, poCT);
}

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

void OGRCompoundCurve::flattenTo2D()
{
    oCC.flattenTo2D(this);
}

/************************************************************************/
/*                              segmentize()                            */
/************************************************************************/

void OGRCompoundCurve::segmentize( double dfMaxLength )
{
    oCC.segmentize(dfMaxLength);
}

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

void OGRCompoundCurve::swapXY()
{
    oCC.swapXY();
}

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

OGRBoolean OGRCompoundCurve::hasCurveGeometry( int bLookForNonLinear ) const
{
    if( bLookForNonLinear )
    {
        return oCC.hasCurveGeometry(bLookForNonLinear);
    }

    return TRUE;
}

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

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

/************************************************************************/
/*                           getNumPoints()                             */
/************************************************************************/

int OGRCompoundCurve::getNumPoints() const
{
    int nPoints = 0;
    for( int i = 0; i < oCC.nCurveCount; i++ )
    {
        nPoints += oCC.papoCurves[i]->getNumPoints();
        if( i != 0 )
            nPoints--;
    }
    return nPoints;
}

/************************************************************************/
/*                      OGRCompoundCurvePointIterator                   */
/************************************************************************/

class OGRCompoundCurvePointIterator final: public OGRPointIterator
{
        CPL_DISALLOW_COPY_ASSIGN(OGRCompoundCurvePointIterator)

        const OGRCompoundCurve *poCC = nullptr;
        int                     iCurCurve = 0;
        OGRPointIterator       *poCurveIter = nullptr;

    public:
        explicit OGRCompoundCurvePointIterator( const OGRCompoundCurve* poCCIn ) :
            poCC(poCCIn) {}
        ~OGRCompoundCurvePointIterator() override { delete poCurveIter; }

        OGRBoolean getNextPoint( OGRPoint* p ) override;
};

/************************************************************************/
/*                            getNextPoint()                            */
/************************************************************************/

OGRBoolean OGRCompoundCurvePointIterator::getNextPoint( OGRPoint* p )
{
    if( iCurCurve == poCC->getNumCurves() )
        return FALSE;
    if( poCurveIter == nullptr )
        poCurveIter = poCC->getCurve(0)->getPointIterator();
    if( !poCurveIter->getNextPoint(p) )
    {
        iCurCurve++;
        if( iCurCurve == poCC->getNumCurves() )
            return FALSE;
        delete poCurveIter;
        poCurveIter = poCC->getCurve(iCurCurve)->getPointIterator();
        // Skip first point.
        return poCurveIter->getNextPoint(p) &&
               poCurveIter->getNextPoint(p);
    }
    return TRUE;
}

/************************************************************************/
/*                         getPointIterator()                           */
/************************************************************************/

OGRPointIterator* OGRCompoundCurve::getPointIterator() const
{
    return new OGRCompoundCurvePointIterator(this);
}

/************************************************************************/
/*                         CastToLineString()                        */
/************************************************************************/

//! @cond Doxygen_Suppress
OGRLineString* OGRCompoundCurve::CastToLineString( OGRCompoundCurve* poCC )
{
    for( int i = 0; i < poCC->oCC.nCurveCount; i++ )
    {
        poCC->oCC.papoCurves[i] =
            OGRCurve::CastToLineString(poCC->oCC.papoCurves[i]);
        if( poCC->oCC.papoCurves[i] == nullptr )
        {
            delete poCC;
            return nullptr;
        }
    }

    if( poCC->oCC.nCurveCount == 1 )
    {
        OGRLineString* poLS = poCC->oCC.papoCurves[0]->toLineString();
        poLS->assignSpatialReference(poCC->getSpatialReference());
        poCC->oCC.papoCurves[0] = nullptr;
        delete poCC;
        return poLS;
    }

    OGRLineString* poLS = poCC->CurveToLineInternal(0, nullptr, FALSE);
    delete poCC;
    return poLS;
}

/************************************************************************/
/*                           CastToLinearRing()                         */
/************************************************************************/

/**
 * \brief Cast to linear ring.
 *
 * The passed in geometry is consumed and a new one returned (or NULL in case
 * of failure)
 *
 * @param poCC the input geometry - ownership is passed to the method.
 * @return new geometry.
 */

OGRLinearRing* OGRCompoundCurve::CastToLinearRing( OGRCompoundCurve* poCC )
{
    for( int i = 0; i < poCC->oCC.nCurveCount; i++ )
    {
        poCC->oCC.papoCurves[i] =
            OGRCurve::CastToLineString(poCC->oCC.papoCurves[i]);
        if( poCC->oCC.papoCurves[i] == nullptr )
        {
            delete poCC;
            return nullptr;
        }
    }

    if( poCC->oCC.nCurveCount == 1 )
    {
        OGRLinearRing* poLR =
            OGRCurve::CastToLinearRing( poCC->oCC.papoCurves[0] );
        if( poLR != nullptr )
        {
            poLR->assignSpatialReference(poCC->getSpatialReference());
        }
        poCC->oCC.papoCurves[0] = nullptr;
        delete poCC;
        return poLR;
    }

    OGRLinearRing* poLR =
        poCC->CurveToLineInternal(0, nullptr, TRUE)->toLinearRing();
    delete poCC;
    return poLR;
}

/************************************************************************/
/*                     GetCasterToLineString()                          */
/************************************************************************/

OGRLineString* OGRCompoundCurve::CasterToLineString( OGRCurve* poCurve )
{
    OGRCompoundCurve* poCC = poCurve->toCompoundCurve();
    return OGRCompoundCurve::CastToLineString(poCC);
}

OGRCurveCasterToLineString OGRCompoundCurve::GetCasterToLineString() const {
    return OGRCompoundCurve::CasterToLineString;
}

/************************************************************************/
/*                        GetCasterToLinearRing()                       */
/************************************************************************/

OGRLinearRing* OGRCompoundCurve::CasterToLinearRing( OGRCurve* poCurve )
{
    OGRCompoundCurve* poCC = poCurve->toCompoundCurve();
    return OGRCompoundCurve::CastToLinearRing(poCC);
}

OGRCurveCasterToLinearRing OGRCompoundCurve::GetCasterToLinearRing() const {
    return OGRCompoundCurve::CasterToLinearRing;
}
//! @endcond

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

double OGRCompoundCurve::get_Area() const
{
    if( IsEmpty() || !get_IsClosed() )
        return 0;

    // Optimization for convex rings.
    if( IsConvex() )
    {
        // Compute area of shape without the circular segments.
        OGRPointIterator* poIter = getPointIterator();
        OGRLineString oLS;
        oLS.setNumPoints( getNumPoints() );
        OGRPoint p;
        for( int i = 0; poIter->getNextPoint(&p); i++ )
        {
            oLS.setPoint( i, p.getX(), p.getY() );
        }
        double dfArea = oLS.get_Area();
        delete poIter;

        // Add the area of the spherical segments.
        dfArea += get_AreaOfCurveSegments();

        return dfArea;
    }

    OGRLineString* poLS = CurveToLine();
    double dfArea = poLS->get_Area();
    delete poLS;

    return dfArea;
}

/************************************************************************/
/*                       get_AreaOfCurveSegments()                      */
/************************************************************************/

/** Return area of curve segments
 * @return area.
 */
double OGRCompoundCurve::get_AreaOfCurveSegments() const
{
    double dfArea = 0;
    for( int i = 0; i < getNumCurves(); i++ )
    {
        const OGRCurve* poPart = getCurve(i);
        dfArea += poPart->get_AreaOfCurveSegments();
    }
    return dfArea;
}
