LCOV - code coverage report
Current view: top level - ogr - ogrgeometryfactory.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2526 2777 91.0 %
Date: 2026-05-17 16:12:46 Functions: 89 92 96.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Factory for converting geometry to and from well known binary
       5             :  *           format.
       6             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 1999, Frank Warmerdam
      10             :  * Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys dot com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "cpl_port.h"
      16             : #include "cpl_quad_tree.h"
      17             : 
      18             : #include "cpl_conv.h"
      19             : #include "cpl_error.h"
      20             : #include "cpl_string.h"
      21             : #include "ogr_geometry.h"
      22             : #include "ogr_api.h"
      23             : #include "ogr_core.h"
      24             : #include "ogr_geos.h"
      25             : #include "ogr_sfcgal.h"
      26             : #include "ogr_p.h"
      27             : #include "ogr_spatialref.h"
      28             : #include "ogr_srs_api.h"
      29             : #ifdef HAVE_GEOS
      30             : #include "ogr_geos.h"
      31             : #endif
      32             : 
      33             : #include "ogrgeojsongeometry.h"
      34             : 
      35             : #include <cassert>
      36             : #include <climits>
      37             : #include <cmath>
      38             : #include <cstdlib>
      39             : #include <cstring>
      40             : #include <cstddef>
      41             : 
      42             : #include <algorithm>
      43             : #include <limits>
      44             : #include <new>
      45             : #include <set>
      46             : #include <utility>
      47             : #include <vector>
      48             : 
      49             : #ifndef HAVE_GEOS
      50             : #define UNUSED_IF_NO_GEOS CPL_UNUSED
      51             : #else
      52             : #define UNUSED_IF_NO_GEOS
      53             : #endif
      54             : 
      55             : /************************************************************************/
      56             : /*                           createFromWkb()                            */
      57             : /************************************************************************/
      58             : 
      59             : /**
      60             :  * \brief Create a geometry object of the appropriate type from its
      61             :  * well known binary representation.
      62             :  *
      63             :  * Note that if nBytes is passed as zero, no checking can be done on whether
      64             :  * the pabyData is sufficient.  This can result in a crash if the input
      65             :  * data is corrupt.  This function returns no indication of the number of
      66             :  * bytes from the data source actually used to represent the returned
      67             :  * geometry object.  Use OGRGeometry::WkbSize() on the returned geometry to
      68             :  * establish the number of bytes it required in WKB format.
      69             :  *
      70             :  * Also note that this is a static method, and that there
      71             :  * is no need to instantiate an OGRGeometryFactory object.
      72             :  *
      73             :  * The C function OGR_G_CreateFromWkb() is the same as this method.
      74             :  *
      75             :  * @param pabyData pointer to the input BLOB data.
      76             :  * @param poSR pointer to the spatial reference to be assigned to the
      77             :  *             created geometry object.  This may be NULL.
      78             :  * @param ppoReturn the newly created geometry object will be assigned to the
      79             :  *                  indicated pointer on return.  This will be NULL in case
      80             :  *                  of failure. If not NULL, *ppoReturn should be freed with
      81             :  *                  OGRGeometryFactory::destroyGeometry() after use.
      82             :  * @param nBytes the number of bytes available in pabyData, or -1 if it isn't
      83             :  *               known
      84             :  * @param eWkbVariant WKB variant.
      85             :  *
      86             :  * @return OGRERR_NONE if all goes well, otherwise any of
      87             :  * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
      88             :  * OGRERR_CORRUPT_DATA may be returned.
      89             :  */
      90             : 
      91       60854 : OGRErr OGRGeometryFactory::createFromWkb(const void *pabyData,
      92             :                                          const OGRSpatialReference *poSR,
      93             :                                          OGRGeometry **ppoReturn, size_t nBytes,
      94             :                                          OGRwkbVariant eWkbVariant)
      95             : 
      96             : {
      97       60854 :     size_t nBytesConsumedOutIgnored = 0;
      98       60854 :     return createFromWkb(pabyData, poSR, ppoReturn, nBytes, eWkbVariant,
      99      121708 :                          nBytesConsumedOutIgnored);
     100             : }
     101             : 
     102             : /**
     103             :  * \brief Create a geometry object of the appropriate type from its
     104             :  * well known binary representation.
     105             :  *
     106             :  * Note that if nBytes is passed as zero, no checking can be done on whether
     107             :  * the pabyData is sufficient.  This can result in a crash if the input
     108             :  * data is corrupt.  This function returns no indication of the number of
     109             :  * bytes from the data source actually used to represent the returned
     110             :  * geometry object.  Use OGRGeometry::WkbSize() on the returned geometry to
     111             :  * establish the number of bytes it required in WKB format.
     112             :  *
     113             :  * Also note that this is a static method, and that there
     114             :  * is no need to instantiate an OGRGeometryFactory object.
     115             :  *
     116             :  * The C function OGR_G_CreateFromWkb() is the same as this method.
     117             :  *
     118             :  * @param pabyData pointer to the input BLOB data.
     119             :  * @param poSR pointer to the spatial reference to be assigned to the
     120             :  *             created geometry object.  This may be NULL.
     121             :  * @param ppoReturn the newly created geometry object will be assigned to the
     122             :  *                  indicated pointer on return.  This will be NULL in case
     123             :  *                  of failure. If not NULL, *ppoReturn should be freed with
     124             :  *                  OGRGeometryFactory::destroyGeometry() after use.
     125             :  * @param nBytes the number of bytes available in pabyData, or -1 if it isn't
     126             :  *               known
     127             :  * @param eWkbVariant WKB variant.
     128             :  * @param nBytesConsumedOut output parameter. Number of bytes consumed.
     129             :  *
     130             :  * @return OGRERR_NONE if all goes well, otherwise any of
     131             :  * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
     132             :  * OGRERR_CORRUPT_DATA may be returned.
     133             :  */
     134             : 
     135      100107 : OGRErr OGRGeometryFactory::createFromWkb(const void *pabyData,
     136             :                                          const OGRSpatialReference *poSR,
     137             :                                          OGRGeometry **ppoReturn, size_t nBytes,
     138             :                                          OGRwkbVariant eWkbVariant,
     139             :                                          size_t &nBytesConsumedOut)
     140             : 
     141             : {
     142      100107 :     const GByte *l_pabyData = static_cast<const GByte *>(pabyData);
     143      100107 :     nBytesConsumedOut = 0;
     144      100107 :     *ppoReturn = nullptr;
     145             : 
     146      100107 :     if (nBytes < 9 && nBytes != static_cast<size_t>(-1))
     147        1242 :         return OGRERR_NOT_ENOUGH_DATA;
     148             : 
     149             :     /* -------------------------------------------------------------------- */
     150             :     /*      Get the byte order byte.  The extra tests are to work around    */
     151             :     /*      bug sin the WKB of DB2 v7.2 as identified by Safe Software.     */
     152             :     /* -------------------------------------------------------------------- */
     153       98865 :     const int nByteOrder = DB2_V72_FIX_BYTE_ORDER(*l_pabyData);
     154       98865 :     if (nByteOrder != wkbXDR && nByteOrder != wkbNDR)
     155             :     {
     156         298 :         CPLDebug("OGR",
     157             :                  "OGRGeometryFactory::createFromWkb() - got corrupt data.\n"
     158             :                  "%02X%02X%02X%02X%02X%02X%02X%02X%02X",
     159         298 :                  l_pabyData[0], l_pabyData[1], l_pabyData[2], l_pabyData[3],
     160         298 :                  l_pabyData[4], l_pabyData[5], l_pabyData[6], l_pabyData[7],
     161         298 :                  l_pabyData[8]);
     162         298 :         return OGRERR_CORRUPT_DATA;
     163             :     }
     164             : 
     165             :     /* -------------------------------------------------------------------- */
     166             :     /*      Get the geometry feature type.  For now we assume that          */
     167             :     /*      geometry type is between 0 and 255 so we only have to fetch     */
     168             :     /*      one byte.                                                       */
     169             :     /* -------------------------------------------------------------------- */
     170             : 
     171       98567 :     OGRwkbGeometryType eGeometryType = wkbUnknown;
     172             :     const OGRErr err =
     173       98567 :         OGRReadWKBGeometryType(l_pabyData, eWkbVariant, &eGeometryType);
     174             : 
     175       98567 :     if (err != OGRERR_NONE)
     176         563 :         return err;
     177             : 
     178             :     /* -------------------------------------------------------------------- */
     179             :     /*      Instantiate a geometry of the appropriate type, and             */
     180             :     /*      initialize from the input stream.                               */
     181             :     /* -------------------------------------------------------------------- */
     182       98004 :     OGRGeometry *poGeom = createGeometry(eGeometryType);
     183             : 
     184       98004 :     if (poGeom == nullptr)
     185           0 :         return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
     186             : 
     187             :     /* -------------------------------------------------------------------- */
     188             :     /*      Import from binary.                                             */
     189             :     /* -------------------------------------------------------------------- */
     190      196008 :     const OGRErr eErr = poGeom->importFromWkb(l_pabyData, nBytes, eWkbVariant,
     191       98004 :                                               nBytesConsumedOut);
     192       98004 :     if (eErr != OGRERR_NONE)
     193             :     {
     194        7315 :         delete poGeom;
     195        7315 :         return eErr;
     196             :     }
     197             : 
     198             :     /* -------------------------------------------------------------------- */
     199             :     /*      Assign spatial reference system.                                */
     200             :     /* -------------------------------------------------------------------- */
     201             : 
     202       94250 :     if (poGeom->hasCurveGeometry() &&
     203        3561 :         CPLTestBool(CPLGetConfigOption("OGR_STROKE_CURVE", "FALSE")))
     204             :     {
     205           5 :         OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
     206           5 :         delete poGeom;
     207           5 :         poGeom = poNewGeom;
     208             :     }
     209       90689 :     poGeom->assignSpatialReference(poSR);
     210       90689 :     *ppoReturn = poGeom;
     211             : 
     212       90689 :     return OGRERR_NONE;
     213             : }
     214             : 
     215             : /************************************************************************/
     216             : /*                        OGR_G_CreateFromWkb()                         */
     217             : /************************************************************************/
     218             : /**
     219             :  * \brief Create a geometry object of the appropriate type from its
     220             :  * well known binary representation.
     221             :  *
     222             :  * Note that if nBytes is passed as zero, no checking can be done on whether
     223             :  * the pabyData is sufficient.  This can result in a crash if the input
     224             :  * data is corrupt.  This function returns no indication of the number of
     225             :  * bytes from the data source actually used to represent the returned
     226             :  * geometry object.  Use OGR_G_WkbSize() on the returned geometry to
     227             :  * establish the number of bytes it required in WKB format.
     228             :  *
     229             :  * The OGRGeometryFactory::createFromWkb() CPP method is the same as this
     230             :  * function.
     231             :  *
     232             :  * @param pabyData pointer to the input BLOB data.
     233             :  * @param hSRS handle to the spatial reference to be assigned to the
     234             :  *             created geometry object.  This may be NULL.
     235             :  * @param phGeometry the newly created geometry object will
     236             :  * be assigned to the indicated handle on return.  This will be NULL in case
     237             :  * of failure. If not NULL, *phGeometry should be freed with
     238             :  * OGR_G_DestroyGeometry() after use.
     239             :  * @param nBytes the number of bytes of data available in pabyData, or -1
     240             :  * if it is not known, but assumed to be sufficient.
     241             :  *
     242             :  * @return OGRERR_NONE if all goes well, otherwise any of
     243             :  * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
     244             :  * OGRERR_CORRUPT_DATA may be returned.
     245             :  */
     246             : 
     247           2 : OGRErr CPL_DLL OGR_G_CreateFromWkb(const void *pabyData,
     248             :                                    OGRSpatialReferenceH hSRS,
     249             :                                    OGRGeometryH *phGeometry, int nBytes)
     250             : 
     251             : {
     252           2 :     return OGRGeometryFactory::createFromWkb(
     253           2 :         pabyData, OGRSpatialReference::FromHandle(hSRS),
     254           2 :         reinterpret_cast<OGRGeometry **>(phGeometry), nBytes);
     255             : }
     256             : 
     257             : /************************************************************************/
     258             : /*                       OGR_G_CreateFromWkbEx()                        */
     259             : /************************************************************************/
     260             : /**
     261             :  * \brief Create a geometry object of the appropriate type from its
     262             :  * well known binary representation.
     263             :  *
     264             :  * Note that if nBytes is passed as zero, no checking can be done on whether
     265             :  * the pabyData is sufficient.  This can result in a crash if the input
     266             :  * data is corrupt.  This function returns no indication of the number of
     267             :  * bytes from the data source actually used to represent the returned
     268             :  * geometry object.  Use OGR_G_WkbSizeEx() on the returned geometry to
     269             :  * establish the number of bytes it required in WKB format.
     270             :  *
     271             :  * The OGRGeometryFactory::createFromWkb() CPP method is the same as this
     272             :  * function.
     273             :  *
     274             :  * @param pabyData pointer to the input BLOB data.
     275             :  * @param hSRS handle to the spatial reference to be assigned to the
     276             :  *             created geometry object.  This may be NULL.
     277             :  * @param phGeometry the newly created geometry object will
     278             :  * be assigned to the indicated handle on return.  This will be NULL in case
     279             :  * of failure. If not NULL, *phGeometry should be freed with
     280             :  * OGR_G_DestroyGeometry() after use.
     281             :  * @param nBytes the number of bytes of data available in pabyData, or -1
     282             :  * if it is not known, but assumed to be sufficient.
     283             :  *
     284             :  * @return OGRERR_NONE if all goes well, otherwise any of
     285             :  * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
     286             :  * OGRERR_CORRUPT_DATA may be returned.
     287             :  * @since GDAL 3.3
     288             :  */
     289             : 
     290       31058 : OGRErr CPL_DLL OGR_G_CreateFromWkbEx(const void *pabyData,
     291             :                                      OGRSpatialReferenceH hSRS,
     292             :                                      OGRGeometryH *phGeometry, size_t nBytes)
     293             : 
     294             : {
     295       31058 :     return OGRGeometryFactory::createFromWkb(
     296       31058 :         pabyData, OGRSpatialReference::FromHandle(hSRS),
     297       31058 :         reinterpret_cast<OGRGeometry **>(phGeometry), nBytes);
     298             : }
     299             : 
     300             : /************************************************************************/
     301             : /*                           createFromWkt()                            */
     302             : /************************************************************************/
     303             : 
     304             : /**
     305             :  * \brief Create a geometry object of the appropriate type from its
     306             :  * well known text representation.
     307             :  *
     308             :  * The C function OGR_G_CreateFromWkt() is the same as this method.
     309             :  *
     310             :  * @param ppszData input zero terminated string containing well known text
     311             :  *                representation of the geometry to be created.  The pointer
     312             :  *                is updated to point just beyond that last character consumed.
     313             :  * @param poSR pointer to the spatial reference to be assigned to the
     314             :  *             created geometry object.  This may be NULL.
     315             :  * @param ppoReturn the newly created geometry object will be assigned to the
     316             :  *                  indicated pointer on return.  This will be NULL if the
     317             :  *                  method fails. If not NULL, *ppoReturn should be freed with
     318             :  *                  OGRGeometryFactory::destroyGeometry() after use.
     319             :  *
     320             :  *  <b>Example:</b>
     321             :  *
     322             :  * \code{.cpp}
     323             :  *    const char* wkt= "POINT(0 0)";
     324             :  *
     325             :  *    // cast because OGR_G_CreateFromWkt will move the pointer
     326             :  *    char* pszWkt = (char*) wkt;
     327             :  *    OGRSpatialReferenceH ref = OSRNewSpatialReference(NULL);
     328             :  *    OGRGeometryH new_geom;
     329             :  *    OSRSetAxisMappingStrategy(poSR, OAMS_TRADITIONAL_GIS_ORDER);
     330             :  *    OGRErr err = OGR_G_CreateFromWkt(&pszWkt, ref, &new_geom);
     331             :  * \endcode
     332             :  *
     333             :  * Since GDAL 3.14, PostGIS-style "extended" WKT inputs of the format
     334             :  * SRID=EPSG_CODE;WKT are supported. (The axis order of the coordinates
     335             :  * is assumed to follow the OAMS_TRADITIONAL_GIS_ORDER convention.)
     336             :  * If ppszData points to an EWKT input and poSR is also specified,
     337             :  * the value of poSR will override the SRS specified in the EWKT.
     338             :  *
     339             :  * @return OGRERR_NONE if all goes well, otherwise any of
     340             :  * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
     341             :  * OGRERR_CORRUPT_DATA may be returned.
     342             :  */
     343             : 
     344      124628 : OGRErr OGRGeometryFactory::createFromWkt(const char **ppszData,
     345             :                                          const OGRSpatialReference *poSR,
     346             :                                          OGRGeometry **ppoReturn)
     347             : 
     348             : {
     349      124628 :     const char *pszInput = *ppszData;
     350      124628 :     *ppoReturn = nullptr;
     351             : 
     352      249256 :     OGRSpatialReferenceRefCountedPtr poEwktSR;
     353             : 
     354             :     /* -------------------------------------------------------------------- */
     355             :     /*      Check for a SRID (PostGIS EWKT)                                 */
     356             :     /* -------------------------------------------------------------------- */
     357      124628 :     if (STARTS_WITH_CI(pszInput, "SRID="))
     358             :     {
     359          13 :         const char *pszSRID = pszInput + 5;
     360             :         char *pszEnd;
     361             : 
     362          13 :         auto nSRID = std::strtol(pszSRID, &pszEnd, 10);
     363             : 
     364          13 :         if (static_cast<int>(nSRID) != nSRID || !isdigit(*pszSRID))
     365             :         {
     366          10 :             return OGRERR_CORRUPT_DATA;
     367             :         }
     368             : 
     369          36 :         while (pszSRID != pszEnd)
     370             :         {
     371          28 :             if (!isdigit(*pszSRID))
     372             :             {
     373           0 :                 return OGRERR_CORRUPT_DATA;
     374             :             }
     375          28 :             pszSRID++;
     376             :         }
     377             : 
     378           8 :         if (*pszEnd != ';')
     379             :         {
     380           5 :             return OGRERR_CORRUPT_DATA;
     381             :         }
     382             : 
     383           3 :         pszInput = pszEnd + 1;
     384             : 
     385           3 :         if (poSR == nullptr)
     386             :         {
     387           2 :             poEwktSR = OGRSpatialReferenceRefCountedPtr::makeInstance();
     388           2 :             if (poEwktSR->importFromEPSG(static_cast<int>(nSRID)) !=
     389             :                 OGRERR_NONE)
     390             :             {
     391           0 :                 return OGRERR_CORRUPT_DATA;
     392             :             }
     393           2 :             poEwktSR->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     394           2 :             poSR = poEwktSR.get();
     395             :         }
     396             :     }
     397             : 
     398             :     /* -------------------------------------------------------------------- */
     399             :     /*      Get the first token, which should be the geometry type.         */
     400             :     /* -------------------------------------------------------------------- */
     401      124618 :     char szToken[OGR_WKT_TOKEN_MAX] = {};
     402      124618 :     if (OGRWktReadToken(pszInput, szToken) == nullptr)
     403           0 :         return OGRERR_CORRUPT_DATA;
     404             : 
     405             :     /* -------------------------------------------------------------------- */
     406             :     /*      Instantiate a geometry of the appropriate type.                 */
     407             :     /* -------------------------------------------------------------------- */
     408      124618 :     OGRGeometry *poGeom = nullptr;
     409      124618 :     if (STARTS_WITH_CI(szToken, "POINT"))
     410             :     {
     411       97582 :         poGeom = new OGRPoint();
     412             :     }
     413       27036 :     else if (STARTS_WITH_CI(szToken, "LINESTRING"))
     414             :     {
     415        1692 :         poGeom = new OGRLineString();
     416             :     }
     417       25344 :     else if (STARTS_WITH_CI(szToken, "POLYGON"))
     418             :     {
     419       16669 :         poGeom = new OGRPolygon();
     420             :     }
     421        8675 :     else if (STARTS_WITH_CI(szToken, "TRIANGLE"))
     422             :     {
     423          62 :         poGeom = new OGRTriangle();
     424             :     }
     425        8613 :     else if (STARTS_WITH_CI(szToken, "GEOMETRYCOLLECTION"))
     426             :     {
     427         531 :         poGeom = new OGRGeometryCollection();
     428             :     }
     429        8082 :     else if (STARTS_WITH_CI(szToken, "MULTIPOLYGON"))
     430             :     {
     431         958 :         poGeom = new OGRMultiPolygon();
     432             :     }
     433        7124 :     else if (STARTS_WITH_CI(szToken, "MULTIPOINT"))
     434             :     {
     435         653 :         poGeom = new OGRMultiPoint();
     436             :     }
     437        6471 :     else if (STARTS_WITH_CI(szToken, "MULTILINESTRING"))
     438             :     {
     439         646 :         poGeom = new OGRMultiLineString();
     440             :     }
     441        5825 :     else if (STARTS_WITH_CI(szToken, "CIRCULARSTRING"))
     442             :     {
     443        3557 :         poGeom = new OGRCircularString();
     444             :     }
     445        2268 :     else if (STARTS_WITH_CI(szToken, "COMPOUNDCURVE"))
     446             :     {
     447         316 :         poGeom = new OGRCompoundCurve();
     448             :     }
     449        1952 :     else if (STARTS_WITH_CI(szToken, "CURVEPOLYGON"))
     450             :     {
     451         336 :         poGeom = new OGRCurvePolygon();
     452             :     }
     453        1616 :     else if (STARTS_WITH_CI(szToken, "MULTICURVE"))
     454             :     {
     455         146 :         poGeom = new OGRMultiCurve();
     456             :     }
     457        1470 :     else if (STARTS_WITH_CI(szToken, "MULTISURFACE"))
     458             :     {
     459         162 :         poGeom = new OGRMultiSurface();
     460             :     }
     461             : 
     462        1308 :     else if (STARTS_WITH_CI(szToken, "POLYHEDRALSURFACE"))
     463             :     {
     464          70 :         poGeom = new OGRPolyhedralSurface();
     465             :     }
     466             : 
     467        1238 :     else if (STARTS_WITH_CI(szToken, "TIN"))
     468             :     {
     469         123 :         poGeom = new OGRTriangulatedSurface();
     470             :     }
     471             : 
     472             :     else
     473             :     {
     474        1115 :         return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
     475             :     }
     476             : 
     477             :     /* -------------------------------------------------------------------- */
     478             :     /*      Do the import.                                                  */
     479             :     /* -------------------------------------------------------------------- */
     480      123503 :     const OGRErr eErr = poGeom->importFromWkt(&pszInput);
     481             : 
     482             :     /* -------------------------------------------------------------------- */
     483             :     /*      Assign spatial reference system.                                */
     484             :     /* -------------------------------------------------------------------- */
     485      123503 :     if (eErr == OGRERR_NONE)
     486             :     {
     487      127753 :         if (poGeom->hasCurveGeometry() &&
     488        4490 :             CPLTestBool(CPLGetConfigOption("OGR_STROKE_CURVE", "FALSE")))
     489             :         {
     490           9 :             OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
     491           9 :             delete poGeom;
     492           9 :             poGeom = poNewGeom;
     493             :         }
     494      123263 :         poGeom->assignSpatialReference(poSR);
     495      123263 :         *ppoReturn = poGeom;
     496      123263 :         *ppszData = pszInput;
     497             :     }
     498             :     else
     499             :     {
     500         240 :         delete poGeom;
     501             :     }
     502             : 
     503      123503 :     return eErr;
     504             : }
     505             : 
     506             : /**
     507             :  * \brief Create a geometry object of the appropriate type from its
     508             :  * well known text representation.
     509             :  *
     510             :  * The C function OGR_G_CreateFromWkt() is the same as this method.
     511             :  *
     512             :  * @param pszData input zero terminated string containing well known text
     513             :  *                representation of the geometry to be created.
     514             :  * @param poSR pointer to the spatial reference to be assigned to the
     515             :  *             created geometry object.  This may be NULL.
     516             :  * @param ppoReturn the newly created geometry object will be assigned to the
     517             :  *                  indicated pointer on return.  This will be NULL if the
     518             :  *                  method fails. If not NULL, *ppoReturn should be freed with
     519             :  *                  OGRGeometryFactory::destroyGeometry() after use.
     520             : 
     521             :  * @return OGRERR_NONE if all goes well, otherwise any of
     522             :  * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
     523             :  * OGRERR_CORRUPT_DATA may be returned.
     524             :  */
     525             : 
     526        2089 : OGRErr OGRGeometryFactory::createFromWkt(const char *pszData,
     527             :                                          const OGRSpatialReference *poSR,
     528             :                                          OGRGeometry **ppoReturn)
     529             : 
     530             : {
     531        2089 :     return createFromWkt(&pszData, poSR, ppoReturn);
     532             : }
     533             : 
     534             : /**
     535             :  * \brief Create a geometry object of the appropriate type from its
     536             :  * well known text representation.
     537             :  *
     538             :  * The C function OGR_G_CreateFromWkt() is the same as this method.
     539             :  *
     540             :  * @param pszData input zero terminated string containing well known text
     541             :  *                representation of the geometry to be created.
     542             :  * @param poSR pointer to the spatial reference to be assigned to the
     543             :  *             created geometry object.  This may be NULL.
     544             : 
     545             :  * @return a pair of the newly created geometry an error code of OGRERR_NONE
     546             :  * if all goes well, otherwise any of OGRERR_NOT_ENOUGH_DATA,
     547             :  * OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or OGRERR_CORRUPT_DATA.
     548             :  *
     549             :  * @since GDAL 3.11
     550             :  */
     551             : 
     552             : std::pair<std::unique_ptr<OGRGeometry>, OGRErr>
     553        3886 : OGRGeometryFactory::createFromWkt(const char *pszData,
     554             :                                   const OGRSpatialReference *poSR)
     555             : 
     556             : {
     557        3886 :     std::unique_ptr<OGRGeometry> poGeom;
     558             :     OGRGeometry *poTmpGeom;
     559        3886 :     auto err = createFromWkt(&pszData, poSR, &poTmpGeom);
     560        3886 :     poGeom.reset(poTmpGeom);
     561             : 
     562        7772 :     return {std::move(poGeom), err};
     563             : }
     564             : 
     565             : /************************************************************************/
     566             : /*                        OGR_G_CreateFromWkt()                         */
     567             : /************************************************************************/
     568             : /**
     569             :  * \brief Create a geometry object of the appropriate type from its well known
     570             :  * text representation.
     571             :  *
     572             :  * The OGRGeometryFactory::createFromWkt CPP method is the same as this
     573             :  * function.
     574             :  *
     575             :  * @param ppszData input zero terminated string containing well known text
     576             :  *                representation of the geometry to be created.  The pointer
     577             :  *                is updated to point just beyond that last character consumed.
     578             :  * @param hSRS handle to the spatial reference to be assigned to the
     579             :  *             created geometry object.  This may be NULL.
     580             :  * @param phGeometry the newly created geometry object will be assigned to the
     581             :  *                  indicated handle on return.  This will be NULL if the
     582             :  *                  method fails. If not NULL, *phGeometry should be freed with
     583             :  *                  OGR_G_DestroyGeometry() after use.
     584             :  *
     585             :  * @return OGRERR_NONE if all goes well, otherwise any of
     586             :  * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
     587             :  * OGRERR_CORRUPT_DATA may be returned.
     588             :  */
     589             : 
     590      116925 : OGRErr CPL_DLL OGR_G_CreateFromWkt(char **ppszData, OGRSpatialReferenceH hSRS,
     591             :                                    OGRGeometryH *phGeometry)
     592             : 
     593             : {
     594      116925 :     return OGRGeometryFactory::createFromWkt(
     595             :         const_cast<const char **>(ppszData),
     596      116925 :         OGRSpatialReference::FromHandle(hSRS),
     597      116925 :         reinterpret_cast<OGRGeometry **>(phGeometry));
     598             : }
     599             : 
     600             : /************************************************************************/
     601             : /*                      OGR_G_CreateFromEnvelope()                      */
     602             : /************************************************************************/
     603             : /**
     604             :  * \brief Create a Polygon geometry from an envelope
     605             :  *
     606             :  *
     607             :  * @param dfMinX minimum X coordinate
     608             :  * @param dfMinY minimum Y coordinate
     609             :  * @param dfMaxX maximum X coordinate
     610             :  * @param dfMaxY maximum Y coordinate
     611             :  * @param hSRS handle to the spatial reference to be assigned to the
     612             :  *             created geometry object. This may be NULL.
     613             :  *
     614             :  * @return the newly created geometry. Should be freed with
     615             :  *          OGR_G_DestroyGeometry() after use.
     616             :  * @since 3.12
     617             :  */
     618             : 
     619           1 : OGRGeometryH CPL_DLL OGR_G_CreateFromEnvelope(double dfMinX, double dfMinY,
     620             :                                               double dfMaxX, double dfMaxY,
     621             :                                               OGRSpatialReferenceH hSRS)
     622             : 
     623             : {
     624             :     auto poPolygon =
     625           2 :         std::make_unique<OGRPolygon>(dfMinX, dfMinY, dfMaxX, dfMaxY);
     626             : 
     627           1 :     if (hSRS)
     628             :     {
     629           2 :         poPolygon->assignSpatialReference(
     630           1 :             OGRSpatialReference::FromHandle(hSRS));
     631             :     }
     632             : 
     633           2 :     return OGRGeometry::ToHandle(poPolygon.release());
     634             : }
     635             : 
     636             : /************************************************************************/
     637             : /*                           createGeometry()                           */
     638             : /************************************************************************/
     639             : 
     640             : /**
     641             :  * \brief Create an empty geometry of desired type.
     642             :  *
     643             :  * This is equivalent to allocating the desired geometry with new, but
     644             :  * the allocation is guaranteed to take place in the context of the
     645             :  * GDAL/OGR heap.
     646             :  *
     647             :  * This method is the same as the C function OGR_G_CreateGeometry().
     648             :  *
     649             :  * @param eGeometryType the type code of the geometry class to be instantiated.
     650             :  *
     651             :  * @return the newly create geometry or NULL on failure. Should be freed with
     652             :  *          OGRGeometryFactory::destroyGeometry() after use.
     653             :  */
     654             : 
     655             : OGRGeometry *
     656      268534 : OGRGeometryFactory::createGeometry(OGRwkbGeometryType eGeometryType)
     657             : 
     658             : {
     659      268534 :     OGRGeometry *poGeom = nullptr;
     660      268534 :     switch (wkbFlatten(eGeometryType))
     661             :     {
     662      184698 :         case wkbPoint:
     663      369396 :             poGeom = new (std::nothrow) OGRPoint();
     664      184698 :             break;
     665             : 
     666       12168 :         case wkbLineString:
     667       24336 :             poGeom = new (std::nothrow) OGRLineString();
     668       12168 :             break;
     669             : 
     670       30223 :         case wkbPolygon:
     671       60446 :             poGeom = new (std::nothrow) OGRPolygon();
     672       30223 :             break;
     673             : 
     674        2067 :         case wkbGeometryCollection:
     675        4134 :             poGeom = new (std::nothrow) OGRGeometryCollection();
     676        2067 :             break;
     677             : 
     678        3299 :         case wkbMultiPolygon:
     679        6598 :             poGeom = new (std::nothrow) OGRMultiPolygon();
     680        3299 :             break;
     681             : 
     682        1499 :         case wkbMultiPoint:
     683        2998 :             poGeom = new (std::nothrow) OGRMultiPoint();
     684        1499 :             break;
     685             : 
     686        1993 :         case wkbMultiLineString:
     687        3986 :             poGeom = new (std::nothrow) OGRMultiLineString();
     688        1993 :             break;
     689             : 
     690          61 :         case wkbLinearRing:
     691         122 :             poGeom = new (std::nothrow) OGRLinearRing();
     692          61 :             break;
     693             : 
     694          69 :         case wkbCircularString:
     695         138 :             poGeom = new (std::nothrow) OGRCircularString();
     696          69 :             break;
     697             : 
     698        1982 :         case wkbCompoundCurve:
     699        3964 :             poGeom = new (std::nothrow) OGRCompoundCurve();
     700        1982 :             break;
     701             : 
     702          46 :         case wkbCurvePolygon:
     703          92 :             poGeom = new (std::nothrow) OGRCurvePolygon();
     704          46 :             break;
     705             : 
     706        1121 :         case wkbMultiCurve:
     707        2242 :             poGeom = new (std::nothrow) OGRMultiCurve();
     708        1121 :             break;
     709             : 
     710        1183 :         case wkbMultiSurface:
     711        2366 :             poGeom = new (std::nothrow) OGRMultiSurface();
     712        1183 :             break;
     713             : 
     714       14503 :         case wkbTriangle:
     715       29006 :             poGeom = new (std::nothrow) OGRTriangle();
     716       14503 :             break;
     717             : 
     718        7378 :         case wkbPolyhedralSurface:
     719       14756 :             poGeom = new (std::nothrow) OGRPolyhedralSurface();
     720        7378 :             break;
     721             : 
     722        6243 :         case wkbTIN:
     723       12486 :             poGeom = new (std::nothrow) OGRTriangulatedSurface();
     724        6243 :             break;
     725             : 
     726           1 :         case wkbUnknown:
     727           1 :             break;
     728             : 
     729           0 :         default:
     730           0 :             CPLAssert(false);
     731             :             break;
     732             :     }
     733      268534 :     if (poGeom)
     734             :     {
     735      268533 :         if (OGR_GT_HasZ(eGeometryType))
     736       64854 :             poGeom->set3D(true);
     737      268533 :         if (OGR_GT_HasM(eGeometryType))
     738       59829 :             poGeom->setMeasured(true);
     739             :     }
     740      268534 :     return poGeom;
     741             : }
     742             : 
     743             : /************************************************************************/
     744             : /*                        OGR_G_CreateGeometry()                        */
     745             : /************************************************************************/
     746             : /**
     747             :  * \brief Create an empty geometry of desired type.
     748             :  *
     749             :  * This is equivalent to allocating the desired geometry with new, but
     750             :  * the allocation is guaranteed to take place in the context of the
     751             :  * GDAL/OGR heap.
     752             :  *
     753             :  * This function is the same as the CPP method
     754             :  * OGRGeometryFactory::createGeometry.
     755             :  *
     756             :  * @param eGeometryType the type code of the geometry to be created.
     757             :  *
     758             :  * @return handle to the newly create geometry or NULL on failure. Should be
     759             :  *         freed with OGR_G_DestroyGeometry() after use.
     760             :  */
     761             : 
     762      166832 : OGRGeometryH OGR_G_CreateGeometry(OGRwkbGeometryType eGeometryType)
     763             : 
     764             : {
     765      166832 :     return OGRGeometry::ToHandle(
     766      166832 :         OGRGeometryFactory::createGeometry(eGeometryType));
     767             : }
     768             : 
     769             : /************************************************************************/
     770             : /*                          destroyGeometry()                           */
     771             : /************************************************************************/
     772             : 
     773             : /**
     774             :  * \brief Destroy geometry object.
     775             :  *
     776             :  * Equivalent to invoking delete on a geometry, but it guaranteed to take
     777             :  * place within the context of the GDAL/OGR heap.
     778             :  *
     779             :  * This method is the same as the C function OGR_G_DestroyGeometry().
     780             :  *
     781             :  * @param poGeom the geometry to deallocate.
     782             :  */
     783             : 
     784           2 : void OGRGeometryFactory::destroyGeometry(OGRGeometry *poGeom)
     785             : 
     786             : {
     787           2 :     delete poGeom;
     788           2 : }
     789             : 
     790             : /************************************************************************/
     791             : /*                       OGR_G_DestroyGeometry()                        */
     792             : /************************************************************************/
     793             : /**
     794             :  * \brief Destroy geometry object.
     795             :  *
     796             :  * Equivalent to invoking delete on a geometry, but it guaranteed to take
     797             :  * place within the context of the GDAL/OGR heap.
     798             :  *
     799             :  * This function is the same as the CPP method
     800             :  * OGRGeometryFactory::destroyGeometry.
     801             :  *
     802             :  * @param hGeom handle to the geometry to delete.
     803             :  */
     804             : 
     805      291435 : void OGR_G_DestroyGeometry(OGRGeometryH hGeom)
     806             : 
     807             : {
     808      291435 :     delete OGRGeometry::FromHandle(hGeom);
     809      291435 : }
     810             : 
     811             : /************************************************************************/
     812             : /*                           forceToPolygon()                           */
     813             : /************************************************************************/
     814             : 
     815             : /**
     816             :  * \brief Convert to polygon.
     817             :  *
     818             :  * Tries to force the provided geometry to be a polygon. This effects a change
     819             :  * on multipolygons.
     820             :  * Curve polygons or closed curves will be changed to polygons.
     821             :  * The passed in geometry is consumed and a new one returned (or
     822             :  * potentially the same one).
     823             :  *
     824             :  * Note: the resulting polygon may break the Simple Features rules for polygons,
     825             :  * for example when converting from a multi-part multipolygon.
     826             :  *
     827             :  * @param poGeom the input geometry - ownership is passed to the method.
     828             :  * @return new geometry, or nullptr in case of error
     829             :  */
     830             : 
     831         153 : OGRGeometry *OGRGeometryFactory::forceToPolygon(OGRGeometry *poGeom)
     832             : 
     833             : {
     834         153 :     if (poGeom == nullptr)
     835           0 :         return nullptr;
     836             : 
     837         153 :     OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
     838             : 
     839         153 :     if (eGeomType == wkbCurvePolygon)
     840             :     {
     841          39 :         OGRCurvePolygon *poCurve = poGeom->toCurvePolygon();
     842             : 
     843          39 :         if (!poGeom->hasCurveGeometry(TRUE))
     844          14 :             return OGRSurface::CastToPolygon(poCurve);
     845             : 
     846          25 :         OGRPolygon *poPoly = poCurve->CurvePolyToPoly();
     847          25 :         delete poGeom;
     848          25 :         return poPoly;
     849             :     }
     850             : 
     851             :     // base polygon or triangle
     852         114 :     if (OGR_GT_IsSubClassOf(eGeomType, wkbPolygon))
     853             :     {
     854           7 :         return OGRSurface::CastToPolygon(poGeom->toSurface());
     855             :     }
     856             : 
     857         107 :     if (OGR_GT_IsCurve(eGeomType))
     858             :     {
     859          60 :         OGRCurve *poCurve = poGeom->toCurve();
     860          60 :         if (poCurve->getNumPoints() >= 3 && poCurve->get_IsClosed())
     861             :         {
     862          40 :             OGRPolygon *poPolygon = new OGRPolygon();
     863          40 :             poPolygon->assignSpatialReference(poGeom->getSpatialReference());
     864             : 
     865          40 :             if (!poGeom->hasCurveGeometry(TRUE))
     866             :             {
     867          26 :                 poPolygon->addRingDirectly(OGRCurve::CastToLinearRing(poCurve));
     868             :             }
     869             :             else
     870             :             {
     871          14 :                 OGRLineString *poLS = poCurve->CurveToLine();
     872          14 :                 poPolygon->addRingDirectly(OGRCurve::CastToLinearRing(poLS));
     873          14 :                 delete poGeom;
     874             :             }
     875          40 :             return poPolygon;
     876             :         }
     877             :     }
     878             : 
     879          67 :     if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
     880             :     {
     881           6 :         OGRPolyhedralSurface *poPS = poGeom->toPolyhedralSurface();
     882           6 :         if (poPS->getNumGeometries() == 1)
     883             :         {
     884           5 :             poGeom = OGRSurface::CastToPolygon(
     885           5 :                 poPS->getGeometryRef(0)->clone()->toSurface());
     886           5 :             delete poPS;
     887           5 :             return poGeom;
     888             :         }
     889             :     }
     890             : 
     891          62 :     if (eGeomType != wkbGeometryCollection && eGeomType != wkbMultiPolygon &&
     892             :         eGeomType != wkbMultiSurface)
     893          38 :         return poGeom;
     894             : 
     895             :     // Build an aggregated polygon from all the polygon rings in the container.
     896          24 :     OGRPolygon *poPolygon = new OGRPolygon();
     897          24 :     OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
     898          24 :     if (poGeom->hasCurveGeometry())
     899             :     {
     900             :         OGRGeometryCollection *poNewGC =
     901           5 :             poGC->getLinearGeometry()->toGeometryCollection();
     902           5 :         delete poGC;
     903           5 :         poGeom = poNewGC;
     904           5 :         poGC = poNewGC;
     905             :     }
     906             : 
     907          24 :     poPolygon->assignSpatialReference(poGeom->getSpatialReference());
     908             : 
     909          53 :     for (int iGeom = 0; iGeom < poGC->getNumGeometries(); iGeom++)
     910             :     {
     911          29 :         if (wkbFlatten(poGC->getGeometryRef(iGeom)->getGeometryType()) !=
     912             :             wkbPolygon)
     913          12 :             continue;
     914             : 
     915          17 :         OGRPolygon *poOldPoly = poGC->getGeometryRef(iGeom)->toPolygon();
     916             : 
     917          17 :         if (poOldPoly->getExteriorRing() == nullptr)
     918           3 :             continue;
     919             : 
     920          14 :         poPolygon->addRingDirectly(poOldPoly->stealExteriorRing());
     921             : 
     922          22 :         for (int iRing = 0; iRing < poOldPoly->getNumInteriorRings(); iRing++)
     923           8 :             poPolygon->addRingDirectly(poOldPoly->stealInteriorRing(iRing));
     924             :     }
     925             : 
     926          24 :     delete poGC;
     927             : 
     928          24 :     return poPolygon;
     929             : }
     930             : 
     931             : /************************************************************************/
     932             : /*                        OGR_G_ForceToPolygon()                        */
     933             : /************************************************************************/
     934             : 
     935             : /**
     936             :  * \brief Convert to polygon.
     937             :  *
     938             :  * This function is the same as the C++ method
     939             :  * OGRGeometryFactory::forceToPolygon().
     940             :  *
     941             :  * @param hGeom handle to the geometry to convert (ownership surrendered).
     942             :  * @return the converted geometry (ownership to caller), or NULL in case of error
     943             :  *
     944             :  * @since GDAL/OGR 1.8.0
     945             :  */
     946             : 
     947          46 : OGRGeometryH OGR_G_ForceToPolygon(OGRGeometryH hGeom)
     948             : 
     949             : {
     950          46 :     return OGRGeometry::ToHandle(
     951          46 :         OGRGeometryFactory::forceToPolygon(OGRGeometry::FromHandle(hGeom)));
     952             : }
     953             : 
     954             : /************************************************************************/
     955             : /*                        forceToMultiPolygon()                         */
     956             : /************************************************************************/
     957             : 
     958             : /**
     959             :  * \brief Convert to multipolygon.
     960             :  *
     961             :  * Tries to force the provided geometry to be a multipolygon.  Currently
     962             :  * this just effects a change on polygons.  The passed in geometry is
     963             :  * consumed and a new one returned (or potentially the same one).
     964             :  *
     965             :  * @return new geometry, or nullptr in case of error
     966             :  */
     967             : 
     968        3765 : OGRGeometry *OGRGeometryFactory::forceToMultiPolygon(OGRGeometry *poGeom)
     969             : 
     970             : {
     971        3765 :     if (poGeom == nullptr)
     972           0 :         return nullptr;
     973             : 
     974        3765 :     OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
     975             : 
     976             :     /* -------------------------------------------------------------------- */
     977             :     /*      If this is already a MultiPolygon, nothing to do                */
     978             :     /* -------------------------------------------------------------------- */
     979        3765 :     if (eGeomType == wkbMultiPolygon)
     980             :     {
     981          38 :         return poGeom;
     982             :     }
     983             : 
     984             :     /* -------------------------------------------------------------------- */
     985             :     /*      If this is already a MultiSurface with compatible content,      */
     986             :     /*      just cast                                                       */
     987             :     /* -------------------------------------------------------------------- */
     988        3727 :     if (eGeomType == wkbMultiSurface)
     989             :     {
     990          13 :         OGRMultiSurface *poMS = poGeom->toMultiSurface();
     991          13 :         if (!poMS->hasCurveGeometry(TRUE))
     992             :         {
     993           4 :             return OGRMultiSurface::CastToMultiPolygon(poMS);
     994             :         }
     995             :     }
     996             : 
     997             :     /* -------------------------------------------------------------------- */
     998             :     /*      Check for the case of a geometrycollection that can be          */
     999             :     /*      promoted to MultiPolygon.                                       */
    1000             :     /* -------------------------------------------------------------------- */
    1001        3723 :     if (eGeomType == wkbGeometryCollection || eGeomType == wkbMultiSurface)
    1002             :     {
    1003          82 :         bool bAllPoly = true;
    1004          82 :         OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    1005          82 :         if (poGeom->hasCurveGeometry())
    1006             :         {
    1007             :             OGRGeometryCollection *poNewGC =
    1008          10 :                 poGC->getLinearGeometry()->toGeometryCollection();
    1009          10 :             delete poGC;
    1010          10 :             poGeom = poNewGC;
    1011          10 :             poGC = poNewGC;
    1012             :         }
    1013             : 
    1014          82 :         bool bCanConvertToMultiPoly = true;
    1015         319 :         for (int iGeom = 0; iGeom < poGC->getNumGeometries(); iGeom++)
    1016             :         {
    1017             :             OGRwkbGeometryType eSubGeomType =
    1018         237 :                 wkbFlatten(poGC->getGeometryRef(iGeom)->getGeometryType());
    1019         237 :             if (eSubGeomType != wkbPolygon)
    1020         166 :                 bAllPoly = false;
    1021         237 :             if (eSubGeomType != wkbMultiPolygon && eSubGeomType != wkbPolygon &&
    1022         130 :                 eSubGeomType != wkbPolyhedralSurface && eSubGeomType != wkbTIN)
    1023             :             {
    1024          16 :                 bCanConvertToMultiPoly = false;
    1025             :             }
    1026             :         }
    1027             : 
    1028          82 :         if (!bCanConvertToMultiPoly)
    1029          12 :             return poGeom;
    1030             : 
    1031          70 :         OGRMultiPolygon *poMP = new OGRMultiPolygon();
    1032          70 :         poMP->assignSpatialReference(poGeom->getSpatialReference());
    1033             : 
    1034         289 :         while (poGC->getNumGeometries() > 0)
    1035             :         {
    1036         219 :             OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
    1037         219 :             poGC->removeGeometry(0, FALSE);
    1038         219 :             if (bAllPoly)
    1039             :             {
    1040          69 :                 poMP->addGeometryDirectly(poSubGeom);
    1041             :             }
    1042             :             else
    1043             :             {
    1044         150 :                 poSubGeom = forceToMultiPolygon(poSubGeom);
    1045         150 :                 OGRMultiPolygon *poSubMP = poSubGeom->toMultiPolygon();
    1046         386 :                 while (poSubMP != nullptr && poSubMP->getNumGeometries() > 0)
    1047             :                 {
    1048         236 :                     poMP->addGeometryDirectly(poSubMP->getGeometryRef(0));
    1049         236 :                     poSubMP->removeGeometry(0, FALSE);
    1050             :                 }
    1051         150 :                 delete poSubMP;
    1052             :             }
    1053             :         }
    1054             : 
    1055          70 :         delete poGC;
    1056             : 
    1057          70 :         return poMP;
    1058             :     }
    1059             : 
    1060        3641 :     if (eGeomType == wkbCurvePolygon)
    1061             :     {
    1062           5 :         OGRPolygon *poPoly = poGeom->toCurvePolygon()->CurvePolyToPoly();
    1063           5 :         OGRMultiPolygon *poMP = new OGRMultiPolygon();
    1064           5 :         poMP->assignSpatialReference(poGeom->getSpatialReference());
    1065           5 :         poMP->addGeometryDirectly(poPoly);
    1066           5 :         delete poGeom;
    1067           5 :         return poMP;
    1068             :     }
    1069             : 
    1070             :     /* -------------------------------------------------------------------- */
    1071             :     /*      If it is PolyhedralSurface or TIN, then pretend it is a         */
    1072             :     /*      multipolygon.                                                   */
    1073             :     /* -------------------------------------------------------------------- */
    1074        3636 :     if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
    1075             :     {
    1076         982 :         return OGRPolyhedralSurface::CastToMultiPolygon(
    1077         982 :             poGeom->toPolyhedralSurface());
    1078             :     }
    1079             : 
    1080        2654 :     if (eGeomType == wkbTriangle)
    1081             :     {
    1082           2 :         return forceToMultiPolygon(forceToPolygon(poGeom));
    1083             :     }
    1084             : 
    1085             :     /* -------------------------------------------------------------------- */
    1086             :     /*      Eventually we should try to split the polygon into component    */
    1087             :     /*      island polygons.  But that is a lot of work and can be put off. */
    1088             :     /* -------------------------------------------------------------------- */
    1089        2652 :     if (eGeomType != wkbPolygon)
    1090          30 :         return poGeom;
    1091             : 
    1092        2622 :     OGRMultiPolygon *poMP = new OGRMultiPolygon();
    1093        2622 :     poMP->assignSpatialReference(poGeom->getSpatialReference());
    1094        2622 :     poMP->addGeometryDirectly(poGeom);
    1095             : 
    1096        2622 :     return poMP;
    1097             : }
    1098             : 
    1099             : /************************************************************************/
    1100             : /*                     OGR_G_ForceToMultiPolygon()                      */
    1101             : /************************************************************************/
    1102             : 
    1103             : /**
    1104             :  * \brief Convert to multipolygon.
    1105             :  *
    1106             :  * This function is the same as the C++ method
    1107             :  * OGRGeometryFactory::forceToMultiPolygon().
    1108             :  *
    1109             :  * @param hGeom handle to the geometry to convert (ownership surrendered).
    1110             :  * @return the converted geometry (ownership to caller), or NULL in case of error
    1111             :  *
    1112             :  * @since GDAL/OGR 1.8.0
    1113             :  */
    1114             : 
    1115          47 : OGRGeometryH OGR_G_ForceToMultiPolygon(OGRGeometryH hGeom)
    1116             : 
    1117             : {
    1118          47 :     return OGRGeometry::ToHandle(OGRGeometryFactory::forceToMultiPolygon(
    1119          47 :         OGRGeometry::FromHandle(hGeom)));
    1120             : }
    1121             : 
    1122             : /************************************************************************/
    1123             : /*                         forceToMultiPoint()                          */
    1124             : /************************************************************************/
    1125             : 
    1126             : /**
    1127             :  * \brief Convert to multipoint.
    1128             :  *
    1129             :  * Tries to force the provided geometry to be a multipoint.  Currently
    1130             :  * this just effects a change on points or collection of points.
    1131             :  * The passed in geometry is
    1132             :  * consumed and a new one returned (or potentially the same one).
    1133             :  *
    1134             :  * @return new geometry.
    1135             :  */
    1136             : 
    1137          67 : OGRGeometry *OGRGeometryFactory::forceToMultiPoint(OGRGeometry *poGeom)
    1138             : 
    1139             : {
    1140          67 :     if (poGeom == nullptr)
    1141           0 :         return nullptr;
    1142             : 
    1143          67 :     OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
    1144             : 
    1145             :     /* -------------------------------------------------------------------- */
    1146             :     /*      If this is already a MultiPoint, nothing to do                  */
    1147             :     /* -------------------------------------------------------------------- */
    1148          67 :     if (eGeomType == wkbMultiPoint)
    1149             :     {
    1150           2 :         return poGeom;
    1151             :     }
    1152             : 
    1153             :     /* -------------------------------------------------------------------- */
    1154             :     /*      Check for the case of a geometrycollection that can be          */
    1155             :     /*      promoted to MultiPoint.                                         */
    1156             :     /* -------------------------------------------------------------------- */
    1157          65 :     if (eGeomType == wkbGeometryCollection)
    1158             :     {
    1159          14 :         OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    1160          18 :         for (const auto &poMember : poGC)
    1161             :         {
    1162          14 :             if (wkbFlatten(poMember->getGeometryType()) != wkbPoint)
    1163          10 :                 return poGeom;
    1164             :         }
    1165             : 
    1166           4 :         OGRMultiPoint *poMP = new OGRMultiPoint();
    1167           4 :         poMP->assignSpatialReference(poGeom->getSpatialReference());
    1168             : 
    1169           8 :         while (poGC->getNumGeometries() > 0)
    1170             :         {
    1171           4 :             poMP->addGeometryDirectly(poGC->getGeometryRef(0));
    1172           4 :             poGC->removeGeometry(0, FALSE);
    1173             :         }
    1174             : 
    1175           4 :         delete poGC;
    1176             : 
    1177           4 :         return poMP;
    1178             :     }
    1179             : 
    1180          51 :     if (eGeomType != wkbPoint)
    1181          44 :         return poGeom;
    1182             : 
    1183           7 :     OGRMultiPoint *poMP = new OGRMultiPoint();
    1184           7 :     poMP->assignSpatialReference(poGeom->getSpatialReference());
    1185           7 :     poMP->addGeometryDirectly(poGeom);
    1186             : 
    1187           7 :     return poMP;
    1188             : }
    1189             : 
    1190             : /************************************************************************/
    1191             : /*                      OGR_G_ForceToMultiPoint()                       */
    1192             : /************************************************************************/
    1193             : 
    1194             : /**
    1195             :  * \brief Convert to multipoint.
    1196             :  *
    1197             :  * This function is the same as the C++ method
    1198             :  * OGRGeometryFactory::forceToMultiPoint().
    1199             :  *
    1200             :  * @param hGeom handle to the geometry to convert (ownership surrendered).
    1201             :  * @return the converted geometry (ownership to caller).
    1202             :  *
    1203             :  * @since GDAL/OGR 1.8.0
    1204             :  */
    1205             : 
    1206          41 : OGRGeometryH OGR_G_ForceToMultiPoint(OGRGeometryH hGeom)
    1207             : 
    1208             : {
    1209          41 :     return OGRGeometry::ToHandle(
    1210          41 :         OGRGeometryFactory::forceToMultiPoint(OGRGeometry::FromHandle(hGeom)));
    1211             : }
    1212             : 
    1213             : /************************************************************************/
    1214             : /*                       forceToMultiLinestring()                       */
    1215             : /************************************************************************/
    1216             : 
    1217             : /**
    1218             :  * \brief Convert to multilinestring.
    1219             :  *
    1220             :  * Tries to force the provided geometry to be a multilinestring.
    1221             :  *
    1222             :  * - linestrings are placed in a multilinestring.
    1223             :  * - circularstrings and compoundcurves will be approximated and placed in a
    1224             :  * multilinestring.
    1225             :  * - geometry collections will be converted to multilinestring if they only
    1226             :  * contain linestrings.
    1227             :  * - polygons will be changed to a collection of linestrings (one per ring).
    1228             :  * - curvepolygons will be approximated and changed to a collection of
    1229             :  ( linestrings (one per ring).
    1230             :  *
    1231             :  * The passed in geometry is
    1232             :  * consumed and a new one returned (or potentially the same one).
    1233             :  *
    1234             :  * @return new geometry.
    1235             :  */
    1236             : 
    1237        2172 : OGRGeometry *OGRGeometryFactory::forceToMultiLineString(OGRGeometry *poGeom)
    1238             : 
    1239             : {
    1240        2172 :     if (poGeom == nullptr)
    1241           0 :         return nullptr;
    1242             : 
    1243        2172 :     OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
    1244             : 
    1245             :     /* -------------------------------------------------------------------- */
    1246             :     /*      If this is already a MultiLineString, nothing to do             */
    1247             :     /* -------------------------------------------------------------------- */
    1248        2172 :     if (eGeomType == wkbMultiLineString)
    1249             :     {
    1250           2 :         return poGeom;
    1251             :     }
    1252             : 
    1253             :     /* -------------------------------------------------------------------- */
    1254             :     /*      Check for the case of a geometrycollection that can be          */
    1255             :     /*      promoted to MultiLineString.                                    */
    1256             :     /* -------------------------------------------------------------------- */
    1257        2170 :     if (eGeomType == wkbGeometryCollection)
    1258             :     {
    1259          16 :         OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    1260          16 :         if (poGeom->hasCurveGeometry())
    1261             :         {
    1262             :             OGRGeometryCollection *poNewGC =
    1263           1 :                 poGC->getLinearGeometry()->toGeometryCollection();
    1264           1 :             delete poGC;
    1265           1 :             poGeom = poNewGC;
    1266           1 :             poGC = poNewGC;
    1267             :         }
    1268             : 
    1269          24 :         for (auto &&poMember : poGC)
    1270             :         {
    1271          18 :             if (wkbFlatten(poMember->getGeometryType()) != wkbLineString)
    1272             :             {
    1273          10 :                 return poGeom;
    1274             :             }
    1275             :         }
    1276             : 
    1277           6 :         OGRMultiLineString *poMP = new OGRMultiLineString();
    1278           6 :         poMP->assignSpatialReference(poGeom->getSpatialReference());
    1279             : 
    1280          14 :         while (poGC->getNumGeometries() > 0)
    1281             :         {
    1282           8 :             poMP->addGeometryDirectly(poGC->getGeometryRef(0));
    1283           8 :             poGC->removeGeometry(0, FALSE);
    1284             :         }
    1285             : 
    1286           6 :         delete poGC;
    1287             : 
    1288           6 :         return poMP;
    1289             :     }
    1290             : 
    1291             :     /* -------------------------------------------------------------------- */
    1292             :     /*      Turn a linestring into a multilinestring.                       */
    1293             :     /* -------------------------------------------------------------------- */
    1294        2154 :     if (eGeomType == wkbLineString)
    1295             :     {
    1296        2064 :         OGRMultiLineString *poMP = new OGRMultiLineString();
    1297        2064 :         poMP->assignSpatialReference(poGeom->getSpatialReference());
    1298        2064 :         poMP->addGeometryDirectly(poGeom);
    1299        2064 :         return poMP;
    1300             :     }
    1301             : 
    1302             :     /* -------------------------------------------------------------------- */
    1303             :     /*      Convert polygons into a multilinestring.                        */
    1304             :     /* -------------------------------------------------------------------- */
    1305          90 :     if (OGR_GT_IsSubClassOf(eGeomType, wkbCurvePolygon))
    1306             :     {
    1307          28 :         OGRMultiLineString *poMLS = new OGRMultiLineString();
    1308          28 :         poMLS->assignSpatialReference(poGeom->getSpatialReference());
    1309             : 
    1310          57 :         const auto AddRingFromSrcPoly = [poMLS](const OGRPolygon *poPoly)
    1311             :         {
    1312          61 :             for (int iRing = 0; iRing < poPoly->getNumInteriorRings() + 1;
    1313             :                  iRing++)
    1314             :             {
    1315             :                 const OGRLineString *poLR;
    1316             : 
    1317          35 :                 if (iRing == 0)
    1318             :                 {
    1319          28 :                     poLR = poPoly->getExteriorRing();
    1320          28 :                     if (poLR == nullptr)
    1321           2 :                         break;
    1322             :                 }
    1323             :                 else
    1324           7 :                     poLR = poPoly->getInteriorRing(iRing - 1);
    1325             : 
    1326          33 :                 if (poLR == nullptr || poLR->getNumPoints() == 0)
    1327           4 :                     continue;
    1328             : 
    1329          29 :                 auto poNewLS = new OGRLineString();
    1330          29 :                 poNewLS->addSubLineString(poLR);
    1331          29 :                 poMLS->addGeometryDirectly(poNewLS);
    1332             :             }
    1333          28 :         };
    1334             : 
    1335          28 :         if (OGR_GT_IsSubClassOf(eGeomType, wkbPolygon))
    1336             :         {
    1337          24 :             AddRingFromSrcPoly(poGeom->toPolygon());
    1338             :         }
    1339             :         else
    1340             :         {
    1341             :             auto poTmpPoly = std::unique_ptr<OGRPolygon>(
    1342           8 :                 poGeom->toCurvePolygon()->CurvePolyToPoly());
    1343           4 :             AddRingFromSrcPoly(poTmpPoly.get());
    1344             :         }
    1345             : 
    1346          28 :         delete poGeom;
    1347             : 
    1348          28 :         return poMLS;
    1349             :     }
    1350             : 
    1351             :     /* -------------------------------------------------------------------- */
    1352             :     /*      If it is PolyhedralSurface or TIN, then pretend it is a         */
    1353             :     /*      multipolygon.                                                   */
    1354             :     /* -------------------------------------------------------------------- */
    1355          62 :     if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
    1356             :     {
    1357           0 :         poGeom = CPLAssertNotNull(forceToMultiPolygon(poGeom));
    1358           0 :         eGeomType = wkbMultiPolygon;
    1359             :     }
    1360             : 
    1361             :     /* -------------------------------------------------------------------- */
    1362             :     /*      Convert multi-polygons into a multilinestring.                  */
    1363             :     /* -------------------------------------------------------------------- */
    1364          62 :     if (eGeomType == wkbMultiPolygon || eGeomType == wkbMultiSurface)
    1365             :     {
    1366           9 :         OGRMultiLineString *poMLS = new OGRMultiLineString();
    1367           9 :         poMLS->assignSpatialReference(poGeom->getSpatialReference());
    1368             : 
    1369          22 :         const auto AddRingFromSrcMP = [poMLS](const OGRMultiPolygon *poSrcMP)
    1370             :         {
    1371          21 :             for (auto &&poPoly : poSrcMP)
    1372             :             {
    1373          27 :                 for (auto &&poLR : poPoly)
    1374             :                 {
    1375          15 :                     if (poLR->IsEmpty())
    1376           2 :                         continue;
    1377             : 
    1378          13 :                     OGRLineString *poNewLS = new OGRLineString();
    1379          13 :                     poNewLS->addSubLineString(poLR);
    1380          13 :                     poMLS->addGeometryDirectly(poNewLS);
    1381             :                 }
    1382             :             }
    1383           9 :         };
    1384             : 
    1385           9 :         if (eGeomType == wkbMultiPolygon)
    1386             :         {
    1387           6 :             AddRingFromSrcMP(poGeom->toMultiPolygon());
    1388             :         }
    1389             :         else
    1390             :         {
    1391             :             auto poTmpMPoly = std::unique_ptr<OGRMultiPolygon>(
    1392           6 :                 poGeom->getLinearGeometry()->toMultiPolygon());
    1393           3 :             AddRingFromSrcMP(poTmpMPoly.get());
    1394             :         }
    1395             : 
    1396           9 :         delete poGeom;
    1397           9 :         return poMLS;
    1398             :     }
    1399             : 
    1400             :     /* -------------------------------------------------------------------- */
    1401             :     /*      If it is a curve line, approximate it and wrap in a multilinestring
    1402             :      */
    1403             :     /* -------------------------------------------------------------------- */
    1404          53 :     if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
    1405             :     {
    1406          20 :         OGRMultiLineString *poMP = new OGRMultiLineString();
    1407          20 :         poMP->assignSpatialReference(poGeom->getSpatialReference());
    1408          20 :         poMP->addGeometryDirectly(poGeom->toCurve()->CurveToLine());
    1409          20 :         delete poGeom;
    1410          20 :         return poMP;
    1411             :     }
    1412             : 
    1413             :     /* -------------------------------------------------------------------- */
    1414             :     /*      If this is already a MultiCurve with compatible content,        */
    1415             :     /*      just cast                                                       */
    1416             :     /* -------------------------------------------------------------------- */
    1417          46 :     if (eGeomType == wkbMultiCurve &&
    1418          13 :         !poGeom->toMultiCurve()->hasCurveGeometry(TRUE))
    1419             :     {
    1420           3 :         return OGRMultiCurve::CastToMultiLineString(poGeom->toMultiCurve());
    1421             :     }
    1422             : 
    1423             :     /* -------------------------------------------------------------------- */
    1424             :     /*      If it is a multicurve, call getLinearGeometry()                */
    1425             :     /* -------------------------------------------------------------------- */
    1426          30 :     if (eGeomType == wkbMultiCurve)
    1427             :     {
    1428          10 :         OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
    1429          10 :         CPLAssert(wkbFlatten(poNewGeom->getGeometryType()) ==
    1430             :                   wkbMultiLineString);
    1431          10 :         delete poGeom;
    1432          10 :         return poNewGeom->toMultiLineString();
    1433             :     }
    1434             : 
    1435          20 :     return poGeom;
    1436             : }
    1437             : 
    1438             : /************************************************************************/
    1439             : /*                    OGR_G_ForceToMultiLineString()                    */
    1440             : /************************************************************************/
    1441             : 
    1442             : /**
    1443             :  * \brief Convert to multilinestring.
    1444             :  *
    1445             :  * This function is the same as the C++ method
    1446             :  * OGRGeometryFactory::forceToMultiLineString().
    1447             :  *
    1448             :  * @param hGeom handle to the geometry to convert (ownership surrendered).
    1449             :  * @return the converted geometry (ownership to caller).
    1450             :  *
    1451             :  * @since GDAL/OGR 1.8.0
    1452             :  */
    1453             : 
    1454          50 : OGRGeometryH OGR_G_ForceToMultiLineString(OGRGeometryH hGeom)
    1455             : 
    1456             : {
    1457          50 :     return OGRGeometry::ToHandle(OGRGeometryFactory::forceToMultiLineString(
    1458          50 :         OGRGeometry::FromHandle(hGeom)));
    1459             : }
    1460             : 
    1461             : /************************************************************************/
    1462             : /*                    removeLowerDimensionSubGeoms()                    */
    1463             : /************************************************************************/
    1464             : 
    1465             : /** \brief Remove sub-geometries from a geometry collection that do not have
    1466             :  *         the maximum topological dimensionality of the collection.
    1467             :  *
    1468             :  * This is typically to be used as a cleanup phase after running
    1469             :  * OGRGeometry::MakeValid()
    1470             :  *
    1471             :  * For example, MakeValid() on a polygon can return a geometry collection of
    1472             :  * polygons and linestrings. Calling this method will return either a polygon
    1473             :  * or multipolygon by dropping those linestrings.
    1474             :  *
    1475             :  * On a non-geometry collection, this will return a clone of the passed
    1476             :  * geometry.
    1477             :  *
    1478             :  * @param poGeom input geometry
    1479             :  * @return a new geometry.
    1480             :  *
    1481             :  * @since GDAL 3.1.0
    1482             :  */
    1483             : OGRGeometry *
    1484          33 : OGRGeometryFactory::removeLowerDimensionSubGeoms(const OGRGeometry *poGeom)
    1485             : {
    1486          33 :     if (poGeom == nullptr)
    1487           0 :         return nullptr;
    1488          48 :     if (wkbFlatten(poGeom->getGeometryType()) != wkbGeometryCollection ||
    1489          15 :         poGeom->IsEmpty())
    1490             :     {
    1491          19 :         return poGeom->clone();
    1492             :     }
    1493          14 :     const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    1494          14 :     int nMaxDim = 0;
    1495          14 :     bool bHasCurve = false;
    1496          39 :     for (const auto poSubGeom : *poGC)
    1497             :     {
    1498          25 :         nMaxDim = std::max(nMaxDim, poSubGeom->getDimension());
    1499          25 :         bHasCurve |= poSubGeom->hasCurveGeometry();
    1500             :     }
    1501          14 :     int nCountAtMaxDim = 0;
    1502          14 :     const OGRGeometry *poGeomAtMaxDim = nullptr;
    1503          39 :     for (const auto poSubGeom : *poGC)
    1504             :     {
    1505          25 :         if (poSubGeom->getDimension() == nMaxDim)
    1506             :         {
    1507          19 :             poGeomAtMaxDim = poSubGeom;
    1508          19 :             nCountAtMaxDim++;
    1509             :         }
    1510             :     }
    1511          14 :     if (nCountAtMaxDim == 1 && poGeomAtMaxDim != nullptr)
    1512             :     {
    1513           9 :         return poGeomAtMaxDim->clone();
    1514             :     }
    1515             :     OGRGeometryCollection *poRet =
    1516           5 :         (nMaxDim == 0)
    1517          10 :             ? static_cast<OGRGeometryCollection *>(new OGRMultiPoint())
    1518           5 :         : (nMaxDim == 1)
    1519          10 :             ? (!bHasCurve
    1520           4 :                    ? static_cast<OGRGeometryCollection *>(
    1521           1 :                          new OGRMultiLineString())
    1522           1 :                    : static_cast<OGRGeometryCollection *>(new OGRMultiCurve()))
    1523           3 :         : (nMaxDim == 2 && !bHasCurve)
    1524           6 :             ? static_cast<OGRGeometryCollection *>(new OGRMultiPolygon())
    1525           1 :             : static_cast<OGRGeometryCollection *>(new OGRMultiSurface());
    1526          15 :     for (const auto poSubGeom : *poGC)
    1527             :     {
    1528          10 :         if (poSubGeom->getDimension() == nMaxDim)
    1529             :         {
    1530          10 :             if (OGR_GT_IsSubClassOf(poSubGeom->getGeometryType(),
    1531          10 :                                     wkbGeometryCollection))
    1532             :             {
    1533             :                 const OGRGeometryCollection *poSubGeomAsGC =
    1534           1 :                     poSubGeom->toGeometryCollection();
    1535           2 :                 for (const auto poSubSubGeom : *poSubGeomAsGC)
    1536             :                 {
    1537           1 :                     if (poSubSubGeom->getDimension() == nMaxDim)
    1538             :                     {
    1539           1 :                         poRet->addGeometryDirectly(poSubSubGeom->clone());
    1540             :                     }
    1541             :                 }
    1542             :             }
    1543             :             else
    1544             :             {
    1545           9 :                 poRet->addGeometryDirectly(poSubGeom->clone());
    1546             :             }
    1547             :         }
    1548             :     }
    1549           5 :     return poRet;
    1550             : }
    1551             : 
    1552             : /************************************************************************/
    1553             : /*                 OGR_G_RemoveLowerDimensionSubGeoms()                 */
    1554             : /************************************************************************/
    1555             : 
    1556             : /** \brief Remove sub-geometries from a geometry collection that do not have
    1557             :  *         the maximum topological dimensionality of the collection.
    1558             :  *
    1559             :  * This function is the same as the C++ method
    1560             :  * OGRGeometryFactory::removeLowerDimensionSubGeoms().
    1561             :  *
    1562             :  * @param hGeom handle to the geometry to convert
    1563             :  * @return a new geometry.
    1564             :  *
    1565             :  * @since GDAL 3.1.0
    1566             :  */
    1567             : 
    1568          18 : OGRGeometryH OGR_G_RemoveLowerDimensionSubGeoms(const OGRGeometryH hGeom)
    1569             : 
    1570             : {
    1571          18 :     return OGRGeometry::ToHandle(
    1572             :         OGRGeometryFactory::removeLowerDimensionSubGeoms(
    1573          36 :             OGRGeometry::FromHandle(hGeom)));
    1574             : }
    1575             : 
    1576             : /************************************************************************/
    1577             : /*                          organizePolygons()                          */
    1578             : /************************************************************************/
    1579             : 
    1580      103687 : struct sPolyExtended
    1581             : {
    1582             :     CPL_DISALLOW_COPY_ASSIGN(sPolyExtended)
    1583       67773 :     sPolyExtended() = default;
    1584      156185 :     sPolyExtended(sPolyExtended &&) = default;
    1585             :     sPolyExtended &operator=(sPolyExtended &&) = default;
    1586             : 
    1587             :     std::unique_ptr<OGRCurvePolygon> poCurvePolygon{};  // always not null
    1588             :     std::unique_ptr<OGRPolygon> poPolygonForTest{};     // may be null
    1589             :     OGREnvelope sEnvelope{};
    1590             :     OGRPoint sPoint{};
    1591             :     size_t nInitialIndex = 0;
    1592             :     OGRCurvePolygon *poEnclosingPolygon = nullptr;
    1593             :     double dfArea = 0.0;
    1594             :     bool bIsTopLevel = false;
    1595             :     bool bIsCW = false;
    1596             : 
    1597        4675 :     inline const OGRLinearRing *getExteriorLinearRing() const
    1598             :     {
    1599        4675 :         if (poPolygonForTest)
    1600             :         {
    1601          37 :             return poPolygonForTest->getExteriorRingCurve()->toLinearRing();
    1602             :         }
    1603             :         else
    1604             :         {
    1605             :             const auto *poPoly =
    1606        4638 :                 dynamic_cast<const OGRPolygon *>(poCurvePolygon.get());
    1607        4638 :             CPLAssert(poPoly);
    1608        4638 :             return poPoly->getExteriorRingCurve()->toLinearRing();
    1609             :         }
    1610             :     }
    1611             : 
    1612      113271 :     static void GetBoundsFromPolyEx(const void *hFeature, CPLRectObj *pBounds)
    1613             :     {
    1614      113271 :         const auto *poPolyEx = static_cast<const sPolyExtended *>(hFeature);
    1615      113271 :         pBounds->minx = poPolyEx->sEnvelope.MinX;
    1616      113271 :         pBounds->miny = poPolyEx->sEnvelope.MinY;
    1617      113271 :         pBounds->maxx = poPolyEx->sEnvelope.MaxX;
    1618      113271 :         pBounds->maxy = poPolyEx->sEnvelope.MaxY;
    1619      113271 :     }
    1620             : };
    1621             : 
    1622       11671 : static bool OGRGeometryFactoryCompareAreaDescending(const sPolyExtended &sPoly1,
    1623             :                                                     const sPolyExtended &sPoly2)
    1624             : {
    1625       11671 :     return sPoly1.dfArea > sPoly2.dfArea;
    1626             : }
    1627             : 
    1628      614489 : static bool OGRGeometryFactoryCompareByIndex(const sPolyExtended &sPoly1,
    1629             :                                              const sPolyExtended &sPoly2)
    1630             : {
    1631      614489 :     return sPoly1.nInitialIndex < sPoly2.nInitialIndex;
    1632             : }
    1633             : 
    1634             : constexpr int N_CRITICAL_PART_NUMBER = 100;
    1635             : 
    1636             : enum OrganizePolygonMethod
    1637             : {
    1638             :     METHOD_NORMAL,
    1639             :     METHOD_SKIP,
    1640             :     METHOD_ONLY_CCW,
    1641             :     METHOD_CCW_INNER_JUST_AFTER_CW_OUTER
    1642             : };
    1643             : 
    1644             : /**
    1645             :  * \brief Organize polygons based on geometries.
    1646             :  *
    1647             :  * Analyse a set of rings (passed as simple polygons), and based on a
    1648             :  * geometric analysis convert them into a polygon with inner rings,
    1649             :  * (or a MultiPolygon if dealing with more than one polygon) that follow the
    1650             :  * OGC Simple Feature specification.
    1651             :  *
    1652             :  * All the input geometries must be OGRPolygon/OGRCurvePolygon with only a valid
    1653             :  * exterior ring (at least 4 points) and no interior rings.
    1654             :  *
    1655             :  * The passed in geometries become the responsibility of the method, but the
    1656             :  * papoPolygons "pointer array" remains owned by the caller.
    1657             :  *
    1658             :  * For faster computation, a polygon is considered to be inside
    1659             :  * another one if a single point of its external ring is included into the other
    1660             :  * one. (unless 'OGR_DEBUG_ORGANIZE_POLYGONS' configuration option is set to
    1661             :  * TRUE. In that case, a slower algorithm that tests exact topological
    1662             :  * relationships is used if GEOS is available.)
    1663             :  *
    1664             :  * In cases where a big number of polygons is passed to this function, the
    1665             :  * default processing may be really slow. You can skip the processing by adding
    1666             :  * METHOD=SKIP to the option list (the result of the function will be a
    1667             :  * multi-polygon with all polygons as toplevel polygons) or only make it analyze
    1668             :  * counterclockwise polygons by adding METHOD=ONLY_CCW to the option list if you
    1669             :  * can assume that the outline of holes is counterclockwise defined (this is the
    1670             :  * convention for example in shapefiles, Personal Geodatabases or File
    1671             :  * Geodatabases).
    1672             :  *
    1673             :  * For FileGDB, in most cases, but not always, a faster method than ONLY_CCW can
    1674             :  * be used. It is CCW_INNER_JUST_AFTER_CW_OUTER. When using it, inner rings are
    1675             :  * assumed to be counterclockwise oriented, and following immediately the outer
    1676             :  * ring (clockwise oriented) that they belong to. If that assumption is not met,
    1677             :  * an inner ring could be attached to the wrong outer ring, so this method must
    1678             :  * be used with care.
    1679             :  *
    1680             :  * If the OGR_ORGANIZE_POLYGONS configuration option is defined, its value will
    1681             :  * override the value of the METHOD option of papszOptions (useful to modify the
    1682             :  * behavior of the shapefile driver)
    1683             :  *
    1684             :  * @param papoPolygons array of geometry pointers - should all be OGRPolygons
    1685             :  * or OGRCurvePolygons. Ownership of the geometries is passed, but not of the
    1686             :  * array itself.
    1687             :  * @param nPolygonCount number of items in papoPolygons
    1688             :  * @param pbIsValidGeometry value may be set to FALSE if an invalid result is
    1689             :  * detected. Validity checks vary according to the method used and are are limited
    1690             :  * to what is needed to link inner rings to outer rings, so a result of TRUE
    1691             :  * does not mean that OGRGeometry::IsValid() returns TRUE.
    1692             :  * @param papszOptions a list of strings for passing options
    1693             :  *
    1694             :  * @return a single resulting geometry (either OGRPolygon, OGRCurvePolygon,
    1695             :  * OGRMultiPolygon, OGRMultiSurface or OGRGeometryCollection). Returns a
    1696             :  * POLYGON EMPTY in the case of nPolygonCount being 0.
    1697             :  *
    1698             :  * @deprecated since 3.13. Use variant
    1699             :  * that accepts a std::vector&lt;std::unique_ptr&lt;OGRGeometry&gt;&gt;&amp; instead.
    1700             :  */
    1701             : 
    1702          33 : OGRGeometry *OGRGeometryFactory::organizePolygons(OGRGeometry **papoPolygons,
    1703             :                                                   int nPolygonCount,
    1704             :                                                   int *pbIsValidGeometry,
    1705             :                                                   CSLConstList papszOptions)
    1706             : {
    1707             :     std::vector<std::unique_ptr<OGRGeometry>> apoPolygons(
    1708          66 :         papoPolygons, papoPolygons + nPolygonCount);
    1709          33 :     bool bIsValidGeometry = false;
    1710             :     auto poGeometry = OGRGeometryFactory::organizePolygons(
    1711          66 :         apoPolygons, &bIsValidGeometry, papszOptions);
    1712          33 :     if (pbIsValidGeometry)
    1713           0 :         *pbIsValidGeometry = bIsValidGeometry;
    1714          66 :     return poGeometry.release();
    1715             : }
    1716             : 
    1717             : /**
    1718             :  * \brief Organize polygons based on geometries.
    1719             :  *
    1720             :  * Analyse a set of rings (passed as simple polygons), and based on a
    1721             :  * geometric analysis convert them into a polygon with inner rings,
    1722             :  * (or a MultiPolygon if dealing with more than one polygon) that follow the
    1723             :  * OGC Simple Feature specification.
    1724             :  *
    1725             :  * All the input geometries must be OGRPolygon/OGRCurvePolygon with only a valid
    1726             :  * exterior ring (at least 4 points) and no interior rings.
    1727             :  *
    1728             :  * The passed in geometries become the responsibility of the method.
    1729             :  *
    1730             :  * For faster computation, a polygon is considered to be inside
    1731             :  * another one if a single point of its external ring is included into the other
    1732             :  * one. (unless 'OGR_DEBUG_ORGANIZE_POLYGONS' configuration option is set to
    1733             :  * TRUE. In that case, a slower algorithm that tests exact topological
    1734             :  * relationships is used if GEOS is available.)
    1735             :  *
    1736             :  * In cases where a big number of polygons is passed to this function, the
    1737             :  * default processing may be really slow. You can skip the processing by adding
    1738             :  * METHOD=SKIP to the option list (the result of the function will be a
    1739             :  * multi-polygon with all polygons as toplevel polygons) or only make it analyze
    1740             :  * counterclockwise polygons by adding METHOD=ONLY_CCW to the option list if you
    1741             :  * can assume that the outline of holes is counterclockwise defined (this is the
    1742             :  * convention for example in shapefiles, Personal Geodatabases or File
    1743             :  * Geodatabases).
    1744             :  *
    1745             :  * For FileGDB, in most cases, but not always, a faster method than ONLY_CCW can
    1746             :  * be used. It is CCW_INNER_JUST_AFTER_CW_OUTER. When using it, inner rings are
    1747             :  * assumed to be counterclockwise oriented, and following immediately the outer
    1748             :  * ring (clockwise oriented) that they belong to. If that assumption is not met,
    1749             :  * an inner ring could be attached to the wrong outer ring, so this method must
    1750             :  * be used with care.
    1751             :  *
    1752             :  * If the OGR_ORGANIZE_POLYGONS configuration option is defined, its value will
    1753             :  * override the value of the METHOD option of papszOptions (useful to modify the
    1754             :  * behavior of the shapefile driver)
    1755             :  *
    1756             :  * @param apoPolygons array of geometries - should all be OGRPolygons
    1757             :  * or OGRCurvePolygons. Ownership of the geometries is passed.
    1758             :  * @param pbIsValidGeometry value may be set to FALSE if an invalid result is
    1759             :  * detected. Validity checks vary according to the method used and are are limited
    1760             :  * to what is needed to link inner rings to outer rings, so a result of TRUE
    1761             :  * does not mean that OGRGeometry::IsValid() returns TRUE.
    1762             :  * @param papszOptions a list of strings for passing options
    1763             :  *
    1764             :  * @return a single resulting geometry (either OGRPolygon, OGRCurvePolygon,
    1765             :  * OGRMultiPolygon, OGRMultiSurface or OGRGeometryCollection). Returns a
    1766             :  * POLYGON EMPTY in the case of nPolygonCount being 0.
    1767             :  *
    1768             :  * @since 3.13
    1769             :  */
    1770             : 
    1771       52310 : std::unique_ptr<OGRGeometry> OGRGeometryFactory::organizePolygons(
    1772             :     std::vector<std::unique_ptr<OGRGeometry>> &apoPolygons,
    1773             :     bool *pbIsValidGeometry, CSLConstList papszOptions)
    1774             : {
    1775       52310 :     if (apoPolygons.empty())
    1776             :     {
    1777           4 :         if (pbIsValidGeometry)
    1778           3 :             *pbIsValidGeometry = true;
    1779             : 
    1780           4 :         return std::make_unique<OGRPolygon>();
    1781             :     }
    1782             : 
    1783       52306 :     std::unique_ptr<OGRGeometry> geom;
    1784       52306 :     OrganizePolygonMethod method = METHOD_NORMAL;
    1785       52306 :     bool bHasCurves = false;
    1786             : 
    1787             :     /* -------------------------------------------------------------------- */
    1788             :     /*      Trivial case of a single polygon.                               */
    1789             :     /* -------------------------------------------------------------------- */
    1790       52306 :     if (apoPolygons.size() == 1)
    1791             :     {
    1792             :         OGRwkbGeometryType eType =
    1793       36709 :             wkbFlatten(apoPolygons[0]->getGeometryType());
    1794             : 
    1795       36709 :         bool bIsValid = true;
    1796             : 
    1797       36709 :         if (eType != wkbPolygon && eType != wkbCurvePolygon)
    1798             :         {
    1799           3 :             CPLError(CE_Warning, CPLE_AppDefined,
    1800             :                      "organizePolygons() received a non-Polygon geometry.");
    1801           3 :             bIsValid = false;
    1802           3 :             apoPolygons[0].reset();
    1803           3 :             geom = std::make_unique<OGRPolygon>();
    1804             :         }
    1805             :         else
    1806             :         {
    1807       36706 :             geom = std::move(apoPolygons[0]);
    1808             :         }
    1809             : 
    1810       36709 :         if (pbIsValidGeometry)
    1811           9 :             *pbIsValidGeometry = bIsValid;
    1812             : 
    1813       36709 :         return geom;
    1814             :     }
    1815             : 
    1816       15597 :     bool bUseFastVersion = true;
    1817       15597 :     if (CPLTestBool(CPLGetConfigOption("OGR_DEBUG_ORGANIZE_POLYGONS", "NO")))
    1818             :     {
    1819             :         /* ------------------------------------------------------------------ */
    1820             :         /*      A wee bit of a warning.                                       */
    1821             :         /* ------------------------------------------------------------------ */
    1822           0 :         bUseFastVersion = !haveGEOS();
    1823             :         // cppcheck-suppress knownConditionTrueFalse
    1824           0 :         if (bUseFastVersion)
    1825             :         {
    1826           0 :             CPLDebugOnce(
    1827             :                 "OGR",
    1828             :                 "In OGR_DEBUG_ORGANIZE_POLYGONS mode, GDAL should be built "
    1829             :                 "with GEOS support enabled in order "
    1830             :                 "OGRGeometryFactory::organizePolygons to provide reliable "
    1831             :                 "results on complex polygons.");
    1832             :         }
    1833             :     }
    1834             : 
    1835             :     /* -------------------------------------------------------------------- */
    1836             :     /*      Setup per polygon envelope and area information.                */
    1837             :     /* -------------------------------------------------------------------- */
    1838       31194 :     std::vector<sPolyExtended> asPolyEx;
    1839       15597 :     asPolyEx.reserve(apoPolygons.size());
    1840             : 
    1841       15597 :     bool bValidTopology = true;
    1842       15597 :     bool bMixedUpGeometries = false;
    1843       15597 :     bool bFoundCCW = false;
    1844             : 
    1845       15597 :     const char *pszMethodValue = CSLFetchNameValue(papszOptions, "METHOD");
    1846             :     const char *pszMethodValueOption =
    1847       15597 :         CPLGetConfigOption("OGR_ORGANIZE_POLYGONS", nullptr);
    1848       15597 :     if (pszMethodValueOption != nullptr && pszMethodValueOption[0] != '\0')
    1849       13944 :         pszMethodValue = pszMethodValueOption;
    1850             : 
    1851       15597 :     if (pszMethodValue != nullptr)
    1852             :     {
    1853       14843 :         if (EQUAL(pszMethodValue, "SKIP"))
    1854             :         {
    1855       13948 :             method = METHOD_SKIP;
    1856       13948 :             bMixedUpGeometries = true;
    1857             :         }
    1858         895 :         else if (EQUAL(pszMethodValue, "ONLY_CCW"))
    1859             :         {
    1860         292 :             method = METHOD_ONLY_CCW;
    1861             :         }
    1862         603 :         else if (EQUAL(pszMethodValue, "CCW_INNER_JUST_AFTER_CW_OUTER"))
    1863             :         {
    1864           0 :             method = METHOD_CCW_INNER_JUST_AFTER_CW_OUTER;
    1865             :         }
    1866         603 :         else if (!EQUAL(pszMethodValue, "DEFAULT"))
    1867             :         {
    1868           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1869             :                      "Unrecognized value for METHOD option : %s",
    1870             :                      pszMethodValue);
    1871             :         }
    1872             :     }
    1873             : 
    1874       15597 :     size_t nCountCWPolygon = 0;
    1875       15597 :     constexpr size_t INVALID_INDEX = static_cast<size_t>(-1);
    1876       15597 :     size_t indexOfCWPolygon = INVALID_INDEX;
    1877       15597 :     OGREnvelope sGlobalEnvelope;
    1878             : 
    1879             :     const auto AddRingToPolyOrCurvePoly =
    1880        2626 :         [](OGRCurvePolygon *poDst, std::unique_ptr<OGRCurve> poRing)
    1881             :     {
    1882             :         const bool bIsCurvePoly =
    1883        2626 :             wkbFlatten(poDst->getGeometryType()) == wkbCurvePolygon;
    1884             :         const OGRLinearRing *poLinearRing =
    1885        2626 :             dynamic_cast<const OGRLinearRing *>(poRing.get());
    1886        2626 :         if (bIsCurvePoly)
    1887             :         {
    1888          16 :             if (poLinearRing)
    1889           0 :                 poDst->addRing(std::make_unique<OGRLineString>(*poLinearRing));
    1890             :             else
    1891          16 :                 poDst->addRing(std::move(poRing));
    1892             :         }
    1893             :         else
    1894             :         {
    1895        2610 :             if (poLinearRing)
    1896             :             {
    1897        2610 :                 poDst->addRing(std::move(poRing));
    1898             :             }
    1899             :             else
    1900             :             {
    1901           0 :                 CPLAssert(wkbFlatten(poRing->getGeometryType()) ==
    1902             :                           wkbLineString);
    1903             :                 const OGRLineString *poLS =
    1904           0 :                     cpl::down_cast<const OGRLineString *>(poRing.get());
    1905           0 :                 CPLAssert(poLS->get_IsClosed());
    1906           0 :                 auto poNewLR = std::make_unique<OGRLinearRing>();
    1907           0 :                 poNewLR->addSubLineString(poLS);
    1908           0 :                 poDst->addRing(std::move(poNewLR));
    1909             :             }
    1910             :         }
    1911        2626 :     };
    1912             : 
    1913       78275 :     for (size_t i = 0; !bHasCurves && i < apoPolygons.size(); ++i)
    1914             :     {
    1915             :         const OGRwkbGeometryType eType =
    1916       62678 :             wkbFlatten(apoPolygons[i]->getGeometryType());
    1917       62678 :         if (eType == wkbCurvePolygon)
    1918          16 :             bHasCurves = true;
    1919             :     }
    1920             : 
    1921       15597 :     bool bIncrementINextIter = true;
    1922             :     // Size of apoPolygons might increase during the loop
    1923      151149 :     for (size_t i = 0; i < apoPolygons.size(); bIncrementINextIter ? ++i : 0)
    1924             :     {
    1925       67776 :         bIncrementINextIter = true;
    1926             : 
    1927             :         const OGRwkbGeometryType eType =
    1928       67776 :             wkbFlatten(apoPolygons[i]->getGeometryType());
    1929             : 
    1930       67776 :         if (eType != wkbPolygon && eType != wkbCurvePolygon)
    1931             :         {
    1932             :             // Ignore any points or lines that find their way in here.
    1933           3 :             CPLError(CE_Warning, CPLE_AppDefined,
    1934             :                      "organizePolygons() received a non-Polygon geometry.");
    1935           3 :             apoPolygons[i].reset();
    1936          41 :             continue;
    1937             :         }
    1938             : 
    1939       67773 :         sPolyExtended sPolyEx;
    1940             : 
    1941       67773 :         sPolyEx.nInitialIndex = i;
    1942       67773 :         sPolyEx.poCurvePolygon.reset(
    1943       67773 :             apoPolygons[i].release()->toCurvePolygon());
    1944             : 
    1945             : #ifdef HAVE_GEOS
    1946             :         {
    1947             :             // This method may be called with ESRI geometries whose validity
    1948             :             // rules are different from OGC ones. So do a cheap test to detect
    1949             :             // potential invalidity with repeated points (excluding initial and final
    1950             :             // one), and do the real one after.
    1951       67773 :             bool bLikelySimpleFeaturesInvalid = false;
    1952             : 
    1953       67773 :             std::set<std::pair<double, double>> xyPairSet;
    1954             :             const auto *poExteriorRing =
    1955       67773 :                 sPolyEx.poCurvePolygon->getExteriorRingCurve();
    1956             :             const auto *poLS =
    1957       67773 :                 dynamic_cast<const OGRLineString *>(poExteriorRing);
    1958       67773 :             if (poLS)
    1959             :             {
    1960       67750 :                 const int nNumPoints = poLS->getNumPoints();
    1961     3576540 :                 for (int iPnt = 0; iPnt < nNumPoints - 1; ++iPnt)
    1962             :                 {
    1963     3514210 :                     if (!xyPairSet.insert({poLS->getX(iPnt), poLS->getY(iPnt)})
    1964     3514210 :                              .second)
    1965             :                     {
    1966        5426 :                         bLikelySimpleFeaturesInvalid = true;
    1967        5426 :                         break;
    1968             :                     }
    1969             :                 }
    1970             :             }
    1971             : 
    1972       67773 :             bool bSelfTouchingRingFormingHole = false;
    1973       67773 :             if (bLikelySimpleFeaturesInvalid)
    1974             :             {
    1975       10852 :                 CPLErrorStateBackuper oErrorBackuper(CPLQuietErrorHandler);
    1976        5426 :                 auto geosContext = OGRGeometry::createGEOSContext();
    1977             :                 GEOSGeometry *poGeosGeom =
    1978        5426 :                     sPolyEx.poCurvePolygon->exportToGEOS(geosContext);
    1979        5426 :                 if (poGeosGeom)
    1980             :                 {
    1981        5426 :                     bSelfTouchingRingFormingHole =
    1982        5426 :                         (GEOSisValidDetail_r(
    1983             :                              geosContext, poGeosGeom,
    1984             :                              GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE,
    1985             :                              nullptr, nullptr) == 1);
    1986        5426 :                     GEOSGeom_destroy_r(geosContext, poGeosGeom);
    1987             :                 }
    1988        5426 :                 finishGEOS_r(geosContext);
    1989             :             }
    1990             : 
    1991       67773 :             if (bSelfTouchingRingFormingHole)
    1992             :             {
    1993             :                 // Make it a valid one and insert all new rings in apoPolygons[]
    1994             :                 auto poValid = std::unique_ptr<OGRGeometry>(
    1995          41 :                     sPolyEx.poCurvePolygon->MakeValid());
    1996          41 :                 if (poValid)
    1997             :                 {
    1998          41 :                     if (method == METHOD_ONLY_CCW ||
    1999             :                         method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
    2000             :                     {
    2001           2 :                         CPLDebug("OGR", "organizePolygons(): switch to NORMAL "
    2002             :                                         "mode due to invalid geometries");
    2003           2 :                         method = METHOD_NORMAL;
    2004             :                     }
    2005             : 
    2006             :                     const auto InsertRings =
    2007        5112 :                         [&apoPolygons](const OGRCurvePolygon *poCurvePoly)
    2008             :                     {
    2009             :                         const bool bIsCurvePoly =
    2010          38 :                             wkbFlatten(poCurvePoly->getGeometryType()) ==
    2011          38 :                             wkbCurvePolygon;
    2012        5112 :                         for (const auto *ring : poCurvePoly)
    2013             :                         {
    2014        5074 :                             if (bIsCurvePoly)
    2015             :                             {
    2016             :                                 auto poTmpPoly =
    2017           0 :                                     std::make_unique<OGRCurvePolygon>();
    2018           0 :                                 if (const OGRLinearRing *poLinearRing =
    2019           0 :                                         dynamic_cast<const OGRLinearRing *>(
    2020           0 :                                             ring))
    2021           0 :                                     poTmpPoly->addRing(
    2022           0 :                                         std::make_unique<OGRLineString>(
    2023             :                                             *poLinearRing));
    2024             :                                 else
    2025           0 :                                     poTmpPoly->addRing(ring);
    2026           0 :                                 apoPolygons.push_back(std::move(poTmpPoly));
    2027             :                             }
    2028             :                             else
    2029             :                             {
    2030        5074 :                                 auto poTmpPoly = std::make_unique<OGRPolygon>();
    2031        5074 :                                 poTmpPoly->addRing(ring);
    2032        5074 :                                 apoPolygons.push_back(std::move(poTmpPoly));
    2033             :                             }
    2034             :                         }
    2035          38 :                     };
    2036             : 
    2037             :                     const auto eValidGeometryType =
    2038          41 :                         wkbFlatten(poValid->getGeometryType());
    2039          41 :                     if (eValidGeometryType == wkbPolygon ||
    2040             :                         eValidGeometryType == wkbCurvePolygon)
    2041             :                     {
    2042             :                         std::unique_ptr<OGRCurvePolygon> poValidPoly(
    2043             :                             cpl::down_cast<OGRCurvePolygon *>(
    2044          41 :                                 poValid.release()));
    2045          41 :                         if (poValidPoly->getNumInteriorRings() == 0)
    2046             :                         {
    2047           3 :                             sPolyEx.poCurvePolygon = std::move(poValidPoly);
    2048             :                         }
    2049             :                         else
    2050             :                         {
    2051          38 :                             InsertRings(poValidPoly.get());
    2052          38 :                             apoPolygons.erase(apoPolygons.begin() + i);
    2053          38 :                             bIncrementINextIter = false;
    2054          38 :                             continue;
    2055           3 :                         }
    2056             :                     }
    2057           0 :                     else if (OGR_GT_IsSubClassOf(eValidGeometryType,
    2058           0 :                                                  wkbGeometryCollection))
    2059             :                     {
    2060             :                         const auto *poGeomColl =
    2061           0 :                             cpl::down_cast<OGRGeometryCollection *>(
    2062             :                                 poValid.get());
    2063           0 :                         for (const auto *poPart : *poGeomColl)
    2064             :                         {
    2065             :                             const auto ePartGeometryType =
    2066           0 :                                 wkbFlatten(poPart->getGeometryType());
    2067           0 :                             if (ePartGeometryType == wkbPolygon ||
    2068             :                                 ePartGeometryType == wkbCurvePolygon)
    2069             :                             {
    2070             :                                 const auto *poPartCP =
    2071           0 :                                     cpl::down_cast<const OGRCurvePolygon *>(
    2072             :                                         poPart);
    2073           0 :                                 InsertRings(poPartCP);
    2074             :                             }
    2075             :                         }
    2076           0 :                         apoPolygons.erase(apoPolygons.begin() + i);
    2077           0 :                         bIncrementINextIter = false;
    2078           0 :                         continue;
    2079             :                     }
    2080             :                 }
    2081             :             }
    2082             :         }
    2083             : #endif
    2084             : 
    2085       67735 :         sPolyEx.poCurvePolygon->getEnvelope(&sPolyEx.sEnvelope);
    2086       67735 :         sGlobalEnvelope.Merge(sPolyEx.sEnvelope);
    2087             : 
    2088       67735 :         if (bUseFastVersion)
    2089             :         {
    2090       67735 :             if (eType == wkbCurvePolygon)
    2091             :             {
    2092          39 :                 sPolyEx.poPolygonForTest.reset(
    2093          39 :                     cpl::down_cast<const OGRCurvePolygon *>(
    2094             :                         sPolyEx.poCurvePolygon.get())
    2095          39 :                         ->CurvePolyToPoly());
    2096             : 
    2097             :                 // Above CurvePolyToPoly() can fail on non-closed rings
    2098          78 :                 if (sPolyEx.poPolygonForTest == nullptr ||
    2099          39 :                     sPolyEx.poPolygonForTest->IsEmpty())
    2100             :                 {
    2101           0 :                     apoPolygons[i].reset();
    2102           0 :                     continue;
    2103             :                 }
    2104             :             }
    2105       67696 :             else if (bHasCurves)
    2106             :             {
    2107           5 :                 CPLAssert(eType == wkbPolygon);
    2108           5 :                 sPolyEx.poPolygonForTest.reset(
    2109           5 :                     cpl::down_cast<const OGRPolygon *>(
    2110             :                         sPolyEx.poCurvePolygon.get())
    2111           5 :                         ->clone());
    2112             :             }
    2113             :         }
    2114             : 
    2115             :         // If the final geometry is a CurvePolygon or a MultiSurface, we
    2116             :         // need to promote regular Polygon to CurvePolygon, as they may contain
    2117             :         // curve rings.
    2118       67735 :         if (bHasCurves && eType == wkbPolygon)
    2119             :         {
    2120           5 :             sPolyEx.poCurvePolygon.reset(cpl::down_cast<OGRCurvePolygon *>(
    2121          10 :                 OGRGeometryFactory::forceTo(std::move(sPolyEx.poCurvePolygon),
    2122             :                                             wkbCurvePolygon)
    2123             :                     .release()));
    2124             :         }
    2125             : 
    2126       67735 :         if (!sPolyEx.poCurvePolygon->IsEmpty() &&
    2127      135470 :             sPolyEx.poCurvePolygon->getNumInteriorRings() == 0 &&
    2128       67735 :             sPolyEx.poCurvePolygon->getExteriorRingCurve()->getNumPoints() >= 4)
    2129             :         {
    2130       67733 :             if (method != METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
    2131       67733 :                 sPolyEx.dfArea = sPolyEx.poCurvePolygon->get_Area();
    2132             :             const auto *poExteriorRing =
    2133       67733 :                 sPolyEx.poCurvePolygon->getExteriorRingCurve();
    2134       67733 :             sPolyEx.bIsCW = poExteriorRing->isClockwise();
    2135       67733 :             poExteriorRing->StartPoint(&sPolyEx.sPoint);
    2136       67733 :             if (sPolyEx.bIsCW)
    2137             :             {
    2138       18038 :                 indexOfCWPolygon = i;
    2139       18038 :                 nCountCWPolygon++;
    2140             :             }
    2141       67733 :             if (!bFoundCCW)
    2142       30637 :                 bFoundCCW = !(sPolyEx.bIsCW);
    2143             :         }
    2144             :         else
    2145             :         {
    2146           2 :             if (!bMixedUpGeometries)
    2147             :             {
    2148           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2149             :                          "organizePolygons() received an unexpected geometry.  "
    2150             :                          "Either a polygon with interior rings, or a polygon "
    2151             :                          "with less than 4 points, or a non-Polygon geometry.  "
    2152             :                          "Return arguments as a collection.");
    2153           0 :                 bMixedUpGeometries = true;
    2154             :             }
    2155             :         }
    2156             : 
    2157       67735 :         asPolyEx.push_back(std::move(sPolyEx));
    2158             :     }
    2159       15597 :     if (asPolyEx.empty())
    2160           0 :         return std::make_unique<OGRPolygon>();
    2161             : 
    2162             :     // If we are in ONLY_CCW mode and that we have found that there is only one
    2163             :     // outer ring, then it is pretty easy : we can assume that all other rings
    2164             :     // are inside.
    2165       15597 :     if ((method == METHOD_ONLY_CCW ||
    2166         290 :          method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER) &&
    2167         108 :         nCountCWPolygon == 1 && bUseFastVersion)
    2168             :     {
    2169         108 :         assert(indexOfCWPolygon != INVALID_INDEX);
    2170         216 :         auto poCP = std::move(asPolyEx[indexOfCWPolygon].poCurvePolygon);
    2171         325 :         for (size_t i = 0; i < asPolyEx.size(); i++)
    2172             :         {
    2173         217 :             if (i != indexOfCWPolygon)
    2174             :             {
    2175             :                 std::unique_ptr<OGRCurve> poRing(
    2176         109 :                     asPolyEx[i].poCurvePolygon->stealExteriorRingCurve());
    2177         109 :                 AddRingToPolyOrCurvePoly(poCP.get(), std::move(poRing));
    2178             :             }
    2179             :         }
    2180             : 
    2181         108 :         if (pbIsValidGeometry)
    2182         108 :             *pbIsValidGeometry = TRUE;
    2183         108 :         return poCP;
    2184             :     }
    2185             : 
    2186       15489 :     if (method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER && asPolyEx[0].bIsCW)
    2187             :     {
    2188             :         // Inner rings are CCW oriented and follow immediately the outer
    2189             :         // ring (that is CW oriented) in which they are included.
    2190           0 :         std::unique_ptr<OGRMultiSurface> poMulti;
    2191           0 :         auto poOuterCurvePoly = std::move(asPolyEx[0].poCurvePolygon);
    2192             : 
    2193             :         // We have already checked that the first ring is CW.
    2194           0 :         const OGREnvelope *psEnvelope = &(asPolyEx[0].sEnvelope);
    2195           0 :         for (std::size_t i = 1; i < asPolyEx.size(); i++)
    2196             :         {
    2197           0 :             if (asPolyEx[i].bIsCW)
    2198             :             {
    2199           0 :                 if (!poMulti)
    2200             :                 {
    2201           0 :                     if (bHasCurves)
    2202           0 :                         poMulti = std::make_unique<OGRMultiSurface>();
    2203             :                     else
    2204           0 :                         poMulti = std::make_unique<OGRMultiPolygon>();
    2205           0 :                     poMulti->addGeometry(std::move(poOuterCurvePoly));
    2206             :                 }
    2207           0 :                 poMulti->addGeometry(std::move(asPolyEx[i].poCurvePolygon));
    2208           0 :                 psEnvelope = &(asPolyEx[i].sEnvelope);
    2209             :             }
    2210             :             else
    2211             :             {
    2212             :                 auto poExteriorRing = std::unique_ptr<OGRCurve>(
    2213           0 :                     asPolyEx[i].poCurvePolygon->stealExteriorRingCurve());
    2214             :                 auto poCurCurvePoly =
    2215             :                     poOuterCurvePoly
    2216           0 :                         ? poOuterCurvePoly.get()
    2217             :                         : poMulti
    2218           0 :                               ->getGeometryRef(poMulti->getNumGeometries() - 1)
    2219           0 :                               ->toCurvePolygon();
    2220           0 :                 AddRingToPolyOrCurvePoly(poCurCurvePoly,
    2221           0 :                                          std::move(poExteriorRing));
    2222           0 :                 if (!(asPolyEx[i].sPoint.getX() >= psEnvelope->MinX &&
    2223           0 :                       asPolyEx[i].sPoint.getX() <= psEnvelope->MaxX &&
    2224           0 :                       asPolyEx[i].sPoint.getY() >= psEnvelope->MinY &&
    2225           0 :                       asPolyEx[i].sPoint.getY() <= psEnvelope->MaxY))
    2226             :                 {
    2227           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    2228             :                              "Part %d does not respect "
    2229             :                              "CCW_INNER_JUST_AFTER_CW_OUTER rule",
    2230             :                              static_cast<int>(i));
    2231             :                 }
    2232             :             }
    2233             :         }
    2234             : 
    2235           0 :         if (pbIsValidGeometry)
    2236           0 :             *pbIsValidGeometry = true;
    2237             :         // cppcheck-suppress accessMoved
    2238           0 :         if (poOuterCurvePoly)
    2239             :         {
    2240             :             // cppcheck-suppress accessMoved
    2241           0 :             return poOuterCurvePoly;
    2242             :         }
    2243             :         else
    2244           0 :             return poMulti;
    2245             :     }
    2246       15489 :     else if (method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
    2247             :     {
    2248           0 :         method = METHOD_ONLY_CCW;
    2249           0 :         for (std::size_t i = 0; i < asPolyEx.size(); i++)
    2250           0 :             asPolyEx[i].dfArea = asPolyEx[i].poCurvePolygon->get_Area();
    2251             :     }
    2252             : 
    2253             :     // Emits a warning if the number of parts is sufficiently big to anticipate
    2254             :     // for very long computation time, and the user didn't specify an explicit
    2255             :     // method.
    2256       15500 :     if (apoPolygons.size() > N_CRITICAL_PART_NUMBER &&
    2257       15500 :         method == METHOD_NORMAL && pszMethodValue == nullptr)
    2258             :     {
    2259           2 :         if (bFoundCCW)
    2260             :         {
    2261           2 :             CPLErrorOnce(
    2262             :                 CE_Warning, CPLE_AppDefined,
    2263             :                 "organizePolygons() received a polygon with more than %d "
    2264             :                 "parts. The processing may be really slow.  "
    2265             :                 "You can skip the processing by setting METHOD=SKIP, "
    2266             :                 "or only make it analyze counter-clock wise parts by "
    2267             :                 "setting METHOD=ONLY_CCW if you can assume that the "
    2268             :                 "outline of holes is counter-clock wise defined",
    2269             :                 N_CRITICAL_PART_NUMBER);
    2270             :         }
    2271             :         else
    2272             :         {
    2273           0 :             CPLErrorOnce(
    2274             :                 CE_Warning, CPLE_AppDefined,
    2275             :                 "organizePolygons() received a polygon with more than %d "
    2276             :                 "parts.  The processing may be really slow.  "
    2277             :                 "You can skip the processing by setting METHOD=SKIP.",
    2278             :                 N_CRITICAL_PART_NUMBER);
    2279             :         }
    2280             :     }
    2281             : 
    2282             :     /* This a nulti-step algorithm :
    2283             :        1) Sort polygons by descending areas
    2284             :        2) For each polygon of rank i, find its smallest enclosing polygon
    2285             :           among the polygons of rank [i-1 ... 0]. If there are no such polygon,
    2286             :           this is a top-level polygon. Otherwise, depending on if the enclosing
    2287             :           polygon is top-level or not, we can decide if we are top-level or not
    2288             :        3) Re-sort the polygons to retrieve their initial order (nicer for
    2289             :           some applications)
    2290             :        4) For each non top-level polygon (= inner ring), add it to its
    2291             :           outer ring
    2292             :        5) Add the top-level polygons to the multipolygon
    2293             : 
    2294             :        Complexity : O(nPolygonCount^2)
    2295             :     */
    2296             : 
    2297             :     /* Compute how each polygon relate to the other ones
    2298             :        To save a bit of computation we always begin the computation by a test
    2299             :        on the envelope. We also take into account the areas to avoid some
    2300             :        useless tests.  (A contains B implies envelop(A) contains envelop(B)
    2301             :        and area(A) > area(B)) In practice, we can hope that few full geometry
    2302             :        intersection of inclusion test is done:
    2303             :        * if the polygons are well separated geographically (a set of islands
    2304             :        for example), no full geometry intersection or inclusion test is done.
    2305             :        (the envelopes don't intersect each other)
    2306             : 
    2307             :        * if the polygons are 'lake inside an island inside a lake inside an
    2308             :        area' and that each polygon is much smaller than its enclosing one,
    2309             :        their bounding boxes are strictly contained into each other, and thus,
    2310             :        no full geometry intersection or inclusion test is done
    2311             :     */
    2312             : 
    2313       15489 :     if (!bMixedUpGeometries)
    2314             :     {
    2315             :         // STEP 1: Sort polygons by descending area.
    2316        1541 :         std::sort(asPolyEx.begin(), asPolyEx.end(),
    2317             :                   OGRGeometryFactoryCompareAreaDescending);
    2318             :     }
    2319             : 
    2320             :     /* -------------------------------------------------------------------- */
    2321             :     /*      Build a quadtree of polygons that can be exterior rings.        */
    2322             :     /* -------------------------------------------------------------------- */
    2323             : 
    2324             :     CPLRectObj sRect;
    2325       15489 :     sRect.minx = sGlobalEnvelope.MinX;
    2326       15489 :     sRect.miny = sGlobalEnvelope.MinY;
    2327       15489 :     sRect.maxx = sGlobalEnvelope.MaxX;
    2328       15489 :     sRect.maxy = sGlobalEnvelope.MaxY;
    2329             :     std::unique_ptr<CPLQuadTree, decltype(&CPLQuadTreeDestroy)> poQuadTree(
    2330             :         CPLQuadTreeCreate(&sRect, sPolyExtended::GetBoundsFromPolyEx),
    2331       30978 :         CPLQuadTreeDestroy);
    2332       83007 :     for (auto &sPolyEx : asPolyEx)
    2333             :     {
    2334       67518 :         if (method == METHOD_ONLY_CCW && sPolyEx.bIsCW == false)
    2335             :         {
    2336             :             // In that mode, we are interested only in indexing clock-wise
    2337             :             // polygons, which are the exterior rings
    2338         260 :             continue;
    2339             :         }
    2340             : 
    2341       67258 :         CPLQuadTreeInsert(poQuadTree.get(), &sPolyEx);
    2342             :     }
    2343             : 
    2344             :     /* -------------------------------------------------------------------- */
    2345             :     /*      Compute relationships, if things seem well structured.          */
    2346             :     /* -------------------------------------------------------------------- */
    2347             : 
    2348             :     // The first (largest) polygon is necessarily top-level.
    2349       15489 :     asPolyEx[0].bIsTopLevel = true;
    2350       15489 :     asPolyEx[0].poEnclosingPolygon = nullptr;
    2351             : 
    2352       15489 :     size_t nCountTopLevel = 1;
    2353             : 
    2354             :     // STEP 2.
    2355       18823 :     for (size_t i = 1;
    2356       18823 :          !bMixedUpGeometries && bValidTopology && i < asPolyEx.size(); i++)
    2357             :     {
    2358        3334 :         auto &thisPoly = asPolyEx[i];
    2359             : 
    2360        3334 :         if (method == METHOD_ONLY_CCW && thisPoly.bIsCW)
    2361             :         {
    2362         321 :             nCountTopLevel++;
    2363         321 :             thisPoly.bIsTopLevel = true;
    2364         321 :             thisPoly.poEnclosingPolygon = nullptr;
    2365         321 :             continue;
    2366             :         }
    2367             : 
    2368             :         // Look for candidate rings that intersect the current ring
    2369             :         CPLRectObj aoi;
    2370        3013 :         aoi.minx = thisPoly.sEnvelope.MinX;
    2371        3013 :         aoi.miny = thisPoly.sEnvelope.MinY;
    2372        3013 :         aoi.maxx = thisPoly.sEnvelope.MaxX;
    2373        3013 :         aoi.maxy = thisPoly.sEnvelope.MaxY;
    2374        3013 :         int nCandidates = 0;
    2375             :         std::unique_ptr<const sPolyExtended *, decltype(&CPLFree)>
    2376             :             aphCandidateShells(
    2377             :                 const_cast<const sPolyExtended **>(
    2378        3013 :                     reinterpret_cast<sPolyExtended **>(CPLQuadTreeSearch(
    2379        3013 :                         poQuadTree.get(), &aoi, &nCandidates))),
    2380        9039 :                 CPLFree);
    2381             : 
    2382             :         // Sort candidate outer rings by increasing area
    2383        3013 :         if (nCandidates)
    2384             :         {
    2385        3010 :             std::sort(
    2386             :                 aphCandidateShells.get(),
    2387        3010 :                 aphCandidateShells.get() + nCandidates,
    2388        2746 :                 [](const sPolyExtended *psPoly1, const sPolyExtended *psPoly2)
    2389        2746 :                 { return psPoly1->dfArea < psPoly2->dfArea; });
    2390             :         }
    2391             : 
    2392        3013 :         int j = 0;
    2393        6119 :         for (; bValidTopology && j < nCandidates; j++)
    2394             :         {
    2395        5624 :             const auto &otherPoly = *(aphCandidateShells.get()[j]);
    2396             : 
    2397        5624 :             if (method == METHOD_ONLY_CCW && otherPoly.bIsCW == false)
    2398             :             {
    2399             :                 // In that mode, this which is CCW if we reach here can only be
    2400             :                 // included in a CW polygon.
    2401           0 :                 continue;
    2402             :             }
    2403        5624 :             if (otherPoly.dfArea < thisPoly.dfArea || &otherPoly == &thisPoly)
    2404             :             {
    2405        2927 :                 continue;
    2406             :             }
    2407             : 
    2408        2697 :             bool thisInsideOther = false;
    2409        2697 :             if (otherPoly.sEnvelope.Contains(thisPoly.sEnvelope))
    2410             :             {
    2411        2563 :                 if (bUseFastVersion)
    2412             :                 {
    2413        2816 :                     if (method == METHOD_ONLY_CCW &&
    2414         253 :                         (&otherPoly) == (&asPolyEx[0]))
    2415             :                     {
    2416             :                         // We are testing if a CCW ring is in the biggest CW
    2417             :                         // ring. It *must* be inside as this is the last
    2418             :                         // candidate, otherwise the winding order rules is
    2419             :                         // broken.
    2420         235 :                         thisInsideOther = true;
    2421             :                     }
    2422        2328 :                     else if (otherPoly.getExteriorLinearRing()
    2423        2328 :                                  ->isPointOnRingBoundary(&thisPoly.sPoint,
    2424             :                                                          FALSE))
    2425             :                     {
    2426             :                         const OGRLinearRing *poLR_this =
    2427          19 :                             thisPoly.getExteriorLinearRing();
    2428             :                         const OGRLinearRing *poLR_other =
    2429          19 :                             otherPoly.getExteriorLinearRing();
    2430             : 
    2431             :                         // If the point of i is on the boundary of other, we will
    2432             :                         // iterate over the other points of this.
    2433          19 :                         const int nPoints = poLR_this->getNumPoints();
    2434          19 :                         int k = 1;  // Used after for.
    2435          38 :                         OGRPoint previousPoint = thisPoly.sPoint;
    2436          34 :                         for (; k < nPoints; k++)
    2437             :                         {
    2438          33 :                             OGRPoint point;
    2439          33 :                             poLR_this->getPoint(k, &point);
    2440          35 :                             if (point.getX() == previousPoint.getX() &&
    2441           2 :                                 point.getY() == previousPoint.getY())
    2442             :                             {
    2443           0 :                                 continue;
    2444             :                             }
    2445          33 :                             if (poLR_other->isPointOnRingBoundary(&point,
    2446             :                                                                   FALSE))
    2447             :                             {
    2448             :                                 // If it is on the boundary of other, iterate again.
    2449             :                             }
    2450          18 :                             else if (poLR_other->isPointInRing(&point, FALSE))
    2451             :                             {
    2452             :                                 // If then point is strictly included in other, then
    2453             :                                 // this is considered inside other.
    2454          16 :                                 thisInsideOther = true;
    2455          16 :                                 break;
    2456             :                             }
    2457             :                             else
    2458             :                             {
    2459             :                                 // If it is outside, then this cannot be inside other.
    2460           2 :                                 break;
    2461             :                             }
    2462          15 :                             previousPoint = std::move(point);
    2463             :                         }
    2464          19 :                         if (!thisInsideOther && k == nPoints && nPoints > 2)
    2465             :                         {
    2466             :                             // All points of this are on the boundary of other.
    2467             :                             // Take a point in the middle of a segment of this and
    2468             :                             // test it against other.
    2469           1 :                             poLR_this->getPoint(0, &previousPoint);
    2470           2 :                             for (k = 1; k < nPoints; k++)
    2471             :                             {
    2472           2 :                                 OGRPoint point;
    2473           2 :                                 poLR_this->getPoint(k, &point);
    2474           2 :                                 if (point.getX() == previousPoint.getX() &&
    2475           0 :                                     point.getY() == previousPoint.getY())
    2476             :                                 {
    2477           0 :                                     continue;
    2478             :                                 }
    2479           2 :                                 OGRPoint pointMiddle;
    2480           2 :                                 pointMiddle.setX(
    2481           2 :                                     (point.getX() + previousPoint.getX()) / 2);
    2482           2 :                                 pointMiddle.setY(
    2483           2 :                                     (point.getY() + previousPoint.getY()) / 2);
    2484           2 :                                 if (poLR_other->isPointOnRingBoundary(
    2485             :                                         &pointMiddle, FALSE))
    2486             :                                 {
    2487             :                                     // If it is on the boundary of other, iterate
    2488             :                                     // again.
    2489             :                                 }
    2490           1 :                                 else if (poLR_other->isPointInRing(&pointMiddle,
    2491             :                                                                    FALSE))
    2492             :                                 {
    2493             :                                     // If then point is strictly included in other,
    2494             :                                     // then this is considered inside other.
    2495           1 :                                     thisInsideOther = true;
    2496           1 :                                     break;
    2497             :                                 }
    2498             :                                 else
    2499             :                                 {
    2500             :                                     // If it is outside, then this cannot be inside
    2501             :                                     // other.
    2502           0 :                                     break;
    2503             :                                 }
    2504           1 :                                 previousPoint = std::move(point);
    2505             :                             }
    2506             :                         }
    2507             :                     }
    2508             :                     // Note that isPointInRing only test strict inclusion in the
    2509             :                     // ring.
    2510        4618 :                     else if (otherPoly.getExteriorLinearRing()->isPointInRing(
    2511        2309 :                                  &thisPoly.sPoint, FALSE))
    2512             :                     {
    2513        2266 :                         thisInsideOther = true;
    2514             :                     }
    2515             :                 }
    2516           0 :                 else if (otherPoly.poCurvePolygon->Contains(
    2517           0 :                              thisPoly.poCurvePolygon.get()))
    2518             :                 {
    2519           0 :                     thisInsideOther = true;
    2520             :                 }
    2521             :             }
    2522             : 
    2523        2697 :             if (thisInsideOther)
    2524             :             {
    2525        2518 :                 if (otherPoly.bIsTopLevel)
    2526             :                 {
    2527             :                     // We are a lake.
    2528        2517 :                     thisPoly.bIsTopLevel = false;
    2529        2517 :                     thisPoly.poEnclosingPolygon =
    2530        2517 :                         otherPoly.poCurvePolygon.get();
    2531             :                 }
    2532             :                 else
    2533             :                 {
    2534             :                     // We are included in a something not toplevel (a lake),
    2535             :                     // so in OGCSF we are considered as toplevel too.
    2536           1 :                     nCountTopLevel++;
    2537           1 :                     thisPoly.bIsTopLevel = true;
    2538           1 :                     thisPoly.poEnclosingPolygon = nullptr;
    2539             :                 }
    2540        2518 :                 break;
    2541             :             }
    2542             :             // Use Overlaps instead of Intersects to be more
    2543             :             // tolerant about touching polygons.
    2544         179 :             else if (bUseFastVersion || !thisPoly.poCurvePolygon->Overlaps(
    2545           0 :                                             otherPoly.poCurvePolygon.get()))
    2546             :             {
    2547             :             }
    2548             :             else
    2549             :             {
    2550             :                 // Bad... The polygons are intersecting but no one is
    2551             :                 // contained inside the other one. This is a really broken
    2552             :                 // case. We just make a multipolygon with the whole set of
    2553             :                 // polygons.
    2554           0 :                 bValidTopology = false;
    2555             : #ifdef DEBUG
    2556           0 :                 char *wkt1 = nullptr;
    2557           0 :                 char *wkt2 = nullptr;
    2558           0 :                 thisPoly.poCurvePolygon->exportToWkt(&wkt1);
    2559           0 :                 otherPoly.poCurvePolygon->exportToWkt(&wkt2);
    2560           0 :                 const int realJ = static_cast<int>(&otherPoly - &asPolyEx[0]);
    2561           0 :                 CPLDebug("OGR",
    2562             :                          "Bad intersection for polygons %d and %d\n"
    2563             :                          "geom %d: %s\n"
    2564             :                          "geom %d: %s",
    2565             :                          static_cast<int>(i), realJ, static_cast<int>(i), wkt1,
    2566             :                          realJ, wkt2);
    2567           0 :                 CPLFree(wkt1);
    2568           0 :                 CPLFree(wkt2);
    2569             : #endif
    2570             :             }
    2571             :         }
    2572             : 
    2573        3013 :         if (j == nCandidates)
    2574             :         {
    2575             :             // We come here because we are not included in anything.
    2576             :             // We are toplevel.
    2577         495 :             nCountTopLevel++;
    2578         495 :             thisPoly.bIsTopLevel = true;
    2579         495 :             thisPoly.poEnclosingPolygon = nullptr;
    2580             :         }
    2581             :     }
    2582             : 
    2583       15489 :     if (pbIsValidGeometry)
    2584         216 :         *pbIsValidGeometry = bValidTopology && !bMixedUpGeometries;
    2585             : 
    2586             :     /* --------------------------------------------------------------------- */
    2587             :     /*      Things broke down - just mark everything as top-level so it gets */
    2588             :     /*      turned into a multipolygon.                                      */
    2589             :     /* --------------------------------------------------------------------- */
    2590       15489 :     if (!bValidTopology || bMixedUpGeometries)
    2591             :     {
    2592       76591 :         for (auto &sPolyEx : asPolyEx)
    2593             :         {
    2594       62643 :             sPolyEx.bIsTopLevel = true;
    2595             :         }
    2596       13948 :         nCountTopLevel = asPolyEx.size();
    2597             :     }
    2598             : 
    2599             :     /* -------------------------------------------------------------------- */
    2600             :     /*      Try to turn into one or more polygons based on the ring         */
    2601             :     /*      relationships.                                                  */
    2602             :     /* -------------------------------------------------------------------- */
    2603             :     // STEP 3: Sort again in initial order.
    2604       15489 :     std::sort(asPolyEx.begin(), asPolyEx.end(),
    2605             :               OGRGeometryFactoryCompareByIndex);
    2606             : 
    2607             :     // STEP 4: Add holes as rings of their enclosing polygon.
    2608       83007 :     for (auto &sPolyEx : asPolyEx)
    2609             :     {
    2610       67518 :         if (!sPolyEx.bIsTopLevel)
    2611             :         {
    2612        2517 :             AddRingToPolyOrCurvePoly(
    2613             :                 sPolyEx.poEnclosingPolygon,
    2614        5034 :                 std::unique_ptr<OGRCurve>(
    2615             :                     sPolyEx.poCurvePolygon->stealExteriorRingCurve()));
    2616        2517 :             sPolyEx.poCurvePolygon.reset();
    2617             :         }
    2618       65001 :         else if (nCountTopLevel == 1)
    2619             :         {
    2620         874 :             geom = std::move(sPolyEx.poCurvePolygon);
    2621             :         }
    2622             :     }
    2623             : 
    2624             :     // STEP 5: Add toplevel polygons.
    2625       15489 :     if (nCountTopLevel > 1)
    2626             :     {
    2627       14615 :         std::unique_ptr<OGRMultiSurface> poMS;
    2628       14615 :         if (bHasCurves)
    2629           9 :             poMS = std::make_unique<OGRMultiSurface>();
    2630             :         else
    2631       14606 :             poMS = std::make_unique<OGRMultiPolygon>();
    2632       79460 :         for (auto &sPolyEx : asPolyEx)
    2633             :         {
    2634       64845 :             if (sPolyEx.bIsTopLevel)
    2635             :             {
    2636       64127 :                 poMS->addGeometry(std::move(sPolyEx.poCurvePolygon));
    2637             :             }
    2638             :         }
    2639       14615 :         geom = std::move(poMS);
    2640             :     }
    2641             : 
    2642       15489 :     return geom;
    2643             : }
    2644             : 
    2645             : /************************************************************************/
    2646             : /*                           createFromGML()                            */
    2647             : /************************************************************************/
    2648             : 
    2649             : /**
    2650             :  * \brief Create geometry from GML.
    2651             :  *
    2652             :  * This method translates a fragment of GML containing only the geometry
    2653             :  * portion into a corresponding OGRGeometry.  There are many limitations
    2654             :  * on the forms of GML geometries supported by this parser, but they are
    2655             :  * too numerous to list here.
    2656             :  *
    2657             :  * The following GML2 elements are parsed : Point, LineString, Polygon,
    2658             :  * MultiPoint, MultiLineString, MultiPolygon, MultiGeometry.
    2659             :  *
    2660             :  * The following GML3 elements are parsed : Surface,
    2661             :  * MultiSurface, PolygonPatch, Triangle, Rectangle, Curve, MultiCurve,
    2662             :  * LineStringSegment, Arc, Circle, CompositeSurface, OrientableSurface, Solid,
    2663             :  * Shell, Tin, TriangulatedSurface.
    2664             :  *
    2665             :  * Arc and Circle elements are returned as curves by default. Stroking to
    2666             :  * linestrings can be done with
    2667             :  * OGR_G_ForceTo(hGeom, OGR_GT_GetLinear(OGR_G_GetGeometryType(hGeom)), NULL).
    2668             :  * A 4 degrees step is used by default, unless the user
    2669             :  * has overridden the value with the OGR_ARC_STEPSIZE configuration variable.
    2670             :  *
    2671             :  * The C function OGR_G_CreateFromGML() is the same as this method.
    2672             :  *
    2673             :  * @param pszData The GML fragment for the geometry.
    2674             :  *
    2675             :  * @return a geometry on success, or NULL on error.
    2676             :  *
    2677             :  * @see OGR_G_ForceTo()
    2678             :  * @see OGR_GT_GetLinear()
    2679             :  * @see OGR_G_GetGeometryType()
    2680             :  */
    2681             : 
    2682           0 : OGRGeometry *OGRGeometryFactory::createFromGML(const char *pszData)
    2683             : 
    2684             : {
    2685             :     OGRGeometryH hGeom;
    2686             : 
    2687           0 :     hGeom = OGR_G_CreateFromGML(pszData);
    2688             : 
    2689           0 :     return OGRGeometry::FromHandle(hGeom);
    2690             : }
    2691             : 
    2692             : /************************************************************************/
    2693             : /*                           createFromGEOS()                           */
    2694             : /************************************************************************/
    2695             : 
    2696             : /** Builds a OGRGeometry* from a GEOSGeom.
    2697             :  * @param hGEOSCtxt GEOS context
    2698             :  * @param geosGeom GEOS geometry
    2699             :  * @return a OGRGeometry*
    2700             :  */
    2701        4218 : OGRGeometry *OGRGeometryFactory::createFromGEOS(
    2702             :     UNUSED_IF_NO_GEOS GEOSContextHandle_t hGEOSCtxt,
    2703             :     UNUSED_IF_NO_GEOS GEOSGeom geosGeom)
    2704             : 
    2705             : {
    2706             : #ifndef HAVE_GEOS
    2707             : 
    2708             :     CPLError(CE_Failure, CPLE_NotSupported, "GEOS support not enabled.");
    2709             :     return nullptr;
    2710             : 
    2711             : #else
    2712             : 
    2713        4218 :     size_t nSize = 0;
    2714        4218 :     unsigned char *pabyBuf = nullptr;
    2715        4218 :     OGRGeometry *poGeometry = nullptr;
    2716             : 
    2717             :     // Special case as POINT EMPTY cannot be translated to WKB.
    2718        4488 :     if (GEOSGeomTypeId_r(hGEOSCtxt, geosGeom) == GEOS_POINT &&
    2719         270 :         GEOSisEmpty_r(hGEOSCtxt, geosGeom))
    2720          14 :         return new OGRPoint();
    2721             : 
    2722             :     const int nCoordDim =
    2723        4204 :         GEOSGeom_getCoordinateDimension_r(hGEOSCtxt, geosGeom);
    2724        4204 :     GEOSWKBWriter *wkbwriter = GEOSWKBWriter_create_r(hGEOSCtxt);
    2725        4204 :     GEOSWKBWriter_setOutputDimension_r(hGEOSCtxt, wkbwriter, nCoordDim);
    2726        4204 :     pabyBuf = GEOSWKBWriter_write_r(hGEOSCtxt, wkbwriter, geosGeom, &nSize);
    2727        4204 :     GEOSWKBWriter_destroy_r(hGEOSCtxt, wkbwriter);
    2728             : 
    2729        4204 :     if (pabyBuf == nullptr || nSize == 0)
    2730             :     {
    2731           0 :         return nullptr;
    2732             :     }
    2733             : 
    2734        4204 :     if (OGRGeometryFactory::createFromWkb(pabyBuf, nullptr, &poGeometry,
    2735        4204 :                                           static_cast<int>(nSize)) !=
    2736             :         OGRERR_NONE)
    2737             :     {
    2738           0 :         poGeometry = nullptr;
    2739             :     }
    2740             : 
    2741        4204 :     GEOSFree_r(hGEOSCtxt, pabyBuf);
    2742             : 
    2743        4204 :     return poGeometry;
    2744             : 
    2745             : #endif  // HAVE_GEOS
    2746             : }
    2747             : 
    2748             : /************************************************************************/
    2749             : /*                              haveGEOS()                              */
    2750             : /************************************************************************/
    2751             : 
    2752             : /**
    2753             :  * \brief Test if GEOS enabled.
    2754             :  *
    2755             :  * This static method returns TRUE if GEOS support is built into OGR,
    2756             :  * otherwise it returns FALSE.
    2757             :  *
    2758             :  * @return TRUE if available, otherwise FALSE.
    2759             :  */
    2760             : 
    2761       34673 : bool OGRGeometryFactory::haveGEOS()
    2762             : 
    2763             : {
    2764             : #ifndef HAVE_GEOS
    2765             :     return false;
    2766             : #else
    2767       34673 :     return true;
    2768             : #endif
    2769             : }
    2770             : 
    2771             : /************************************************************************/
    2772             : /*                           createFromFgf()                            */
    2773             : /************************************************************************/
    2774             : 
    2775             : /**
    2776             :  * \brief Create a geometry object of the appropriate type from its FGF (FDO
    2777             :  * Geometry Format) binary representation.
    2778             :  *
    2779             :  * Also note that this is a static method, and that there
    2780             :  * is no need to instantiate an OGRGeometryFactory object.
    2781             :  *
    2782             :  * The C function OGR_G_CreateFromFgf() is the same as this method.
    2783             :  *
    2784             :  * @param pabyData pointer to the input BLOB data.
    2785             :  * @param poSR pointer to the spatial reference to be assigned to the
    2786             :  *             created geometry object.  This may be NULL.
    2787             :  * @param ppoReturn the newly created geometry object will be assigned to the
    2788             :  *                  indicated pointer on return.  This will be NULL in case
    2789             :  *                  of failure, but NULL might be a valid return for a NULL
    2790             :  * shape.
    2791             :  * @param nBytes the number of bytes available in pabyData.
    2792             :  * @param pnBytesConsumed if not NULL, it will be set to the number of bytes
    2793             :  * consumed (at most nBytes).
    2794             :  *
    2795             :  * @return OGRERR_NONE if all goes well, otherwise any of
    2796             :  * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
    2797             :  * OGRERR_CORRUPT_DATA may be returned.
    2798             :  */
    2799             : 
    2800         296 : OGRErr OGRGeometryFactory::createFromFgf(const void *pabyData,
    2801             :                                          OGRSpatialReference *poSR,
    2802             :                                          OGRGeometry **ppoReturn, int nBytes,
    2803             :                                          int *pnBytesConsumed)
    2804             : 
    2805             : {
    2806         296 :     return createFromFgfInternal(static_cast<const GByte *>(pabyData), poSR,
    2807         296 :                                  ppoReturn, nBytes, pnBytesConsumed, 0);
    2808             : }
    2809             : 
    2810             : /************************************************************************/
    2811             : /*                       createFromFgfInternal()                        */
    2812             : /************************************************************************/
    2813             : 
    2814         299 : OGRErr OGRGeometryFactory::createFromFgfInternal(
    2815             :     const unsigned char *pabyData, OGRSpatialReference *poSR,
    2816             :     OGRGeometry **ppoReturn, int nBytes, int *pnBytesConsumed, int nRecLevel)
    2817             : {
    2818             :     // Arbitrary value, but certainly large enough for reasonable usages.
    2819         299 :     if (nRecLevel == 32)
    2820             :     {
    2821           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2822             :                  "Too many recursion levels (%d) while parsing FGF geometry.",
    2823             :                  nRecLevel);
    2824           0 :         return OGRERR_CORRUPT_DATA;
    2825             :     }
    2826             : 
    2827         299 :     *ppoReturn = nullptr;
    2828             : 
    2829         299 :     if (nBytes < 4)
    2830         109 :         return OGRERR_NOT_ENOUGH_DATA;
    2831             : 
    2832             :     /* -------------------------------------------------------------------- */
    2833             :     /*      Decode the geometry type.                                       */
    2834             :     /* -------------------------------------------------------------------- */
    2835         190 :     GInt32 nGType = 0;
    2836         190 :     memcpy(&nGType, pabyData + 0, 4);
    2837         190 :     CPL_LSBPTR32(&nGType);
    2838             : 
    2839         190 :     if (nGType < 0 || nGType > 13)
    2840         176 :         return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
    2841             : 
    2842             :     /* -------------------------------------------------------------------- */
    2843             :     /*      Decode the dimensionality if appropriate.                       */
    2844             :     /* -------------------------------------------------------------------- */
    2845          14 :     int nTupleSize = 0;
    2846          14 :     GInt32 nGDim = 0;
    2847             : 
    2848             :     // TODO: Why is this a switch?
    2849          14 :     switch (nGType)
    2850             :     {
    2851           9 :         case 1:  // Point
    2852             :         case 2:  // LineString
    2853             :         case 3:  // Polygon
    2854           9 :             if (nBytes < 8)
    2855           0 :                 return OGRERR_NOT_ENOUGH_DATA;
    2856             : 
    2857           9 :             memcpy(&nGDim, pabyData + 4, 4);
    2858           9 :             CPL_LSBPTR32(&nGDim);
    2859             : 
    2860           9 :             if (nGDim < 0 || nGDim > 3)
    2861           0 :                 return OGRERR_CORRUPT_DATA;
    2862             : 
    2863           9 :             nTupleSize = 2;
    2864           9 :             if (nGDim & 0x01)  // Z
    2865           1 :                 nTupleSize++;
    2866           9 :             if (nGDim & 0x02)  // M
    2867           0 :                 nTupleSize++;
    2868             : 
    2869           9 :             break;
    2870             : 
    2871           5 :         default:
    2872           5 :             break;
    2873             :     }
    2874             : 
    2875          14 :     OGRGeometry *poGeom = nullptr;
    2876             : 
    2877             :     /* -------------------------------------------------------------------- */
    2878             :     /*      None                                                            */
    2879             :     /* -------------------------------------------------------------------- */
    2880          14 :     if (nGType == 0)
    2881             :     {
    2882           0 :         if (pnBytesConsumed)
    2883           0 :             *pnBytesConsumed = 4;
    2884             :     }
    2885             : 
    2886             :     /* -------------------------------------------------------------------- */
    2887             :     /*      Point                                                           */
    2888             :     /* -------------------------------------------------------------------- */
    2889          14 :     else if (nGType == 1)
    2890             :     {
    2891           3 :         if (nBytes < nTupleSize * 8 + 8)
    2892           0 :             return OGRERR_NOT_ENOUGH_DATA;
    2893             : 
    2894           3 :         double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
    2895           3 :         memcpy(adfTuple, pabyData + 8, nTupleSize * 8);
    2896             : #ifdef CPL_MSB
    2897             :         for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
    2898             :             CPL_SWAP64PTR(adfTuple + iOrdinal);
    2899             : #endif
    2900           3 :         if (nTupleSize > 2)
    2901           1 :             poGeom = new OGRPoint(adfTuple[0], adfTuple[1], adfTuple[2]);
    2902             :         else
    2903           2 :             poGeom = new OGRPoint(adfTuple[0], adfTuple[1]);
    2904             : 
    2905           3 :         if (pnBytesConsumed)
    2906           1 :             *pnBytesConsumed = 8 + nTupleSize * 8;
    2907             :     }
    2908             : 
    2909             :     /* -------------------------------------------------------------------- */
    2910             :     /*      LineString                                                      */
    2911             :     /* -------------------------------------------------------------------- */
    2912          11 :     else if (nGType == 2)
    2913             :     {
    2914           2 :         if (nBytes < 12)
    2915           0 :             return OGRERR_NOT_ENOUGH_DATA;
    2916             : 
    2917           2 :         GInt32 nPointCount = 0;
    2918           2 :         memcpy(&nPointCount, pabyData + 8, 4);
    2919           2 :         CPL_LSBPTR32(&nPointCount);
    2920             : 
    2921           2 :         if (nPointCount < 0 || nPointCount > INT_MAX / (nTupleSize * 8))
    2922           0 :             return OGRERR_CORRUPT_DATA;
    2923             : 
    2924           2 :         if (nBytes - 12 < nTupleSize * 8 * nPointCount)
    2925           0 :             return OGRERR_NOT_ENOUGH_DATA;
    2926             : 
    2927           2 :         OGRLineString *poLS = new OGRLineString();
    2928           2 :         poGeom = poLS;
    2929           2 :         poLS->setNumPoints(nPointCount);
    2930             : 
    2931           4 :         for (int iPoint = 0; iPoint < nPointCount; iPoint++)
    2932             :         {
    2933           2 :             double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
    2934           2 :             memcpy(adfTuple, pabyData + 12 + 8 * nTupleSize * iPoint,
    2935           2 :                    nTupleSize * 8);
    2936             : #ifdef CPL_MSB
    2937             :             for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
    2938             :                 CPL_SWAP64PTR(adfTuple + iOrdinal);
    2939             : #endif
    2940           2 :             if (nTupleSize > 2)
    2941           0 :                 poLS->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
    2942             :             else
    2943           2 :                 poLS->setPoint(iPoint, adfTuple[0], adfTuple[1]);
    2944             :         }
    2945             : 
    2946           2 :         if (pnBytesConsumed)
    2947           0 :             *pnBytesConsumed = 12 + nTupleSize * 8 * nPointCount;
    2948             :     }
    2949             : 
    2950             :     /* -------------------------------------------------------------------- */
    2951             :     /*      Polygon                                                         */
    2952             :     /* -------------------------------------------------------------------- */
    2953           9 :     else if (nGType == 3)
    2954             :     {
    2955           4 :         if (nBytes < 12)
    2956           0 :             return OGRERR_NOT_ENOUGH_DATA;
    2957             : 
    2958           4 :         GInt32 nRingCount = 0;
    2959           4 :         memcpy(&nRingCount, pabyData + 8, 4);
    2960           4 :         CPL_LSBPTR32(&nRingCount);
    2961             : 
    2962           4 :         if (nRingCount < 0 || nRingCount > INT_MAX / 4)
    2963           0 :             return OGRERR_CORRUPT_DATA;
    2964             : 
    2965             :         // Each ring takes at least 4 bytes.
    2966           4 :         if (nBytes - 12 < nRingCount * 4)
    2967           0 :             return OGRERR_NOT_ENOUGH_DATA;
    2968             : 
    2969           4 :         int nNextByte = 12;
    2970             : 
    2971           4 :         OGRPolygon *poPoly = new OGRPolygon();
    2972           4 :         poGeom = poPoly;
    2973             : 
    2974          10 :         for (int iRing = 0; iRing < nRingCount; iRing++)
    2975             :         {
    2976           6 :             if (nBytes - nNextByte < 4)
    2977             :             {
    2978           0 :                 delete poGeom;
    2979           0 :                 return OGRERR_NOT_ENOUGH_DATA;
    2980             :             }
    2981             : 
    2982           6 :             GInt32 nPointCount = 0;
    2983           6 :             memcpy(&nPointCount, pabyData + nNextByte, 4);
    2984           6 :             CPL_LSBPTR32(&nPointCount);
    2985             : 
    2986           6 :             if (nPointCount < 0 || nPointCount > INT_MAX / (nTupleSize * 8))
    2987             :             {
    2988           0 :                 delete poGeom;
    2989           0 :                 return OGRERR_CORRUPT_DATA;
    2990             :             }
    2991             : 
    2992           6 :             nNextByte += 4;
    2993             : 
    2994           6 :             if (nBytes - nNextByte < nTupleSize * 8 * nPointCount)
    2995             :             {
    2996           0 :                 delete poGeom;
    2997           0 :                 return OGRERR_NOT_ENOUGH_DATA;
    2998             :             }
    2999             : 
    3000           6 :             OGRLinearRing *poLR = new OGRLinearRing();
    3001           6 :             poLR->setNumPoints(nPointCount);
    3002             : 
    3003          12 :             for (int iPoint = 0; iPoint < nPointCount; iPoint++)
    3004             :             {
    3005           6 :                 double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
    3006           6 :                 memcpy(adfTuple, pabyData + nNextByte, nTupleSize * 8);
    3007           6 :                 nNextByte += nTupleSize * 8;
    3008             : 
    3009             : #ifdef CPL_MSB
    3010             :                 for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
    3011             :                     CPL_SWAP64PTR(adfTuple + iOrdinal);
    3012             : #endif
    3013           6 :                 if (nTupleSize > 2)
    3014           0 :                     poLR->setPoint(iPoint, adfTuple[0], adfTuple[1],
    3015             :                                    adfTuple[2]);
    3016             :                 else
    3017           6 :                     poLR->setPoint(iPoint, adfTuple[0], adfTuple[1]);
    3018             :             }
    3019             : 
    3020           6 :             poPoly->addRingDirectly(poLR);
    3021             :         }
    3022             : 
    3023           4 :         if (pnBytesConsumed)
    3024           2 :             *pnBytesConsumed = nNextByte;
    3025             :     }
    3026             : 
    3027             :     /* -------------------------------------------------------------------- */
    3028             :     /*      GeometryCollections of various kinds.                           */
    3029             :     /* -------------------------------------------------------------------- */
    3030           5 :     else if (nGType == 4      // MultiPoint
    3031           5 :              || nGType == 5   // MultiLineString
    3032           5 :              || nGType == 6   // MultiPolygon
    3033           3 :              || nGType == 7)  // MultiGeometry
    3034             :     {
    3035           5 :         if (nBytes < 8)
    3036           3 :             return OGRERR_NOT_ENOUGH_DATA;
    3037             : 
    3038           5 :         GInt32 nGeomCount = 0;
    3039           5 :         memcpy(&nGeomCount, pabyData + 4, 4);
    3040           5 :         CPL_LSBPTR32(&nGeomCount);
    3041             : 
    3042           5 :         if (nGeomCount < 0 || nGeomCount > INT_MAX / 4)
    3043           0 :             return OGRERR_CORRUPT_DATA;
    3044             : 
    3045             :         // Each geometry takes at least 4 bytes.
    3046           5 :         if (nBytes - 8 < 4 * nGeomCount)
    3047           2 :             return OGRERR_NOT_ENOUGH_DATA;
    3048             : 
    3049           3 :         OGRGeometryCollection *poGC = nullptr;
    3050           3 :         if (nGType == 4)
    3051           0 :             poGC = new OGRMultiPoint();
    3052           3 :         else if (nGType == 5)
    3053           0 :             poGC = new OGRMultiLineString();
    3054           3 :         else if (nGType == 6)
    3055           1 :             poGC = new OGRMultiPolygon();
    3056           2 :         else if (nGType == 7)
    3057           2 :             poGC = new OGRGeometryCollection();
    3058             : 
    3059           3 :         int nBytesUsed = 8;
    3060             : 
    3061           5 :         for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
    3062             :         {
    3063           3 :             int nThisGeomSize = 0;
    3064           3 :             OGRGeometry *poThisGeom = nullptr;
    3065             : 
    3066           6 :             const OGRErr eErr = createFromFgfInternal(
    3067           3 :                 pabyData + nBytesUsed, poSR, &poThisGeom, nBytes - nBytesUsed,
    3068             :                 &nThisGeomSize, nRecLevel + 1);
    3069           3 :             if (eErr != OGRERR_NONE)
    3070             :             {
    3071           0 :                 delete poGC;
    3072           1 :                 return eErr;
    3073             :             }
    3074             : 
    3075           3 :             nBytesUsed += nThisGeomSize;
    3076           3 :             if (poThisGeom != nullptr)
    3077             :             {
    3078           3 :                 const OGRErr eErr2 = poGC->addGeometryDirectly(poThisGeom);
    3079           3 :                 if (eErr2 != OGRERR_NONE)
    3080             :                 {
    3081           1 :                     delete poGC;
    3082           1 :                     delete poThisGeom;
    3083           1 :                     return eErr2;
    3084             :                 }
    3085             :             }
    3086             :         }
    3087             : 
    3088           2 :         poGeom = poGC;
    3089           2 :         if (pnBytesConsumed)
    3090           2 :             *pnBytesConsumed = nBytesUsed;
    3091             :     }
    3092             : 
    3093             :     /* -------------------------------------------------------------------- */
    3094             :     /*      Currently unsupported geometry.                                 */
    3095             :     /*                                                                      */
    3096             :     /*      We need to add 10/11/12/13 curve types in some fashion.         */
    3097             :     /* -------------------------------------------------------------------- */
    3098             :     else
    3099             :     {
    3100           0 :         return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
    3101             :     }
    3102             : 
    3103             :     /* -------------------------------------------------------------------- */
    3104             :     /*      Assign spatial reference system.                                */
    3105             :     /* -------------------------------------------------------------------- */
    3106          11 :     if (poGeom != nullptr && poSR)
    3107           0 :         poGeom->assignSpatialReference(poSR);
    3108          11 :     *ppoReturn = poGeom;
    3109             : 
    3110          11 :     return OGRERR_NONE;
    3111             : }
    3112             : 
    3113             : /************************************************************************/
    3114             : /*                        OGR_G_CreateFromFgf()                         */
    3115             : /************************************************************************/
    3116             : 
    3117             : /**
    3118             :  * \brief Create a geometry object of the appropriate type from its FGF
    3119             :  * (FDO Geometry Format) binary representation.
    3120             :  *
    3121             :  * See OGRGeometryFactory::createFromFgf() */
    3122           0 : OGRErr CPL_DLL OGR_G_CreateFromFgf(const void *pabyData,
    3123             :                                    OGRSpatialReferenceH hSRS,
    3124             :                                    OGRGeometryH *phGeometry, int nBytes,
    3125             :                                    int *pnBytesConsumed)
    3126             : 
    3127             : {
    3128           0 :     return OGRGeometryFactory::createFromFgf(
    3129             :         pabyData, OGRSpatialReference::FromHandle(hSRS),
    3130           0 :         reinterpret_cast<OGRGeometry **>(phGeometry), nBytes, pnBytesConsumed);
    3131             : }
    3132             : 
    3133             : /************************************************************************/
    3134             : /*                     SplitLineStringAtDateline()                      */
    3135             : /************************************************************************/
    3136             : 
    3137           8 : static void SplitLineStringAtDateline(OGRGeometryCollection *poMulti,
    3138             :                                       const OGRLineString *poLS,
    3139             :                                       double dfDateLineOffset, double dfXOffset)
    3140             : {
    3141           8 :     const double dfLeftBorderX = 180 - dfDateLineOffset;
    3142           8 :     const double dfRightBorderX = -180 + dfDateLineOffset;
    3143           8 :     const double dfDiffSpace = 360 - dfDateLineOffset;
    3144             : 
    3145           8 :     const bool bIs3D = poLS->getCoordinateDimension() == 3;
    3146           8 :     OGRLineString *poNewLS = new OGRLineString();
    3147           8 :     poMulti->addGeometryDirectly(poNewLS);
    3148          35 :     for (int i = 0; i < poLS->getNumPoints(); i++)
    3149             :     {
    3150          27 :         const double dfX = poLS->getX(i) + dfXOffset;
    3151          27 :         if (i > 0 && fabs(dfX - (poLS->getX(i - 1) + dfXOffset)) > dfDiffSpace)
    3152             :         {
    3153           9 :             double dfX1 = poLS->getX(i - 1) + dfXOffset;
    3154           9 :             double dfY1 = poLS->getY(i - 1);
    3155           9 :             double dfZ1 = poLS->getY(i - 1);
    3156           9 :             double dfX2 = poLS->getX(i) + dfXOffset;
    3157           9 :             double dfY2 = poLS->getY(i);
    3158           9 :             double dfZ2 = poLS->getY(i);
    3159             : 
    3160           8 :             if (dfX1 > -180 && dfX1 < dfRightBorderX && dfX2 == 180 &&
    3161           0 :                 i + 1 < poLS->getNumPoints() &&
    3162          17 :                 poLS->getX(i + 1) + dfXOffset > -180 &&
    3163           0 :                 poLS->getX(i + 1) + dfXOffset < dfRightBorderX)
    3164             :             {
    3165           0 :                 if (bIs3D)
    3166           0 :                     poNewLS->addPoint(-180, poLS->getY(i), poLS->getZ(i));
    3167             :                 else
    3168           0 :                     poNewLS->addPoint(-180, poLS->getY(i));
    3169             : 
    3170           0 :                 i++;
    3171             : 
    3172           0 :                 if (bIs3D)
    3173           0 :                     poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i),
    3174             :                                       poLS->getZ(i));
    3175             :                 else
    3176           0 :                     poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i));
    3177           0 :                 continue;
    3178             :             }
    3179           4 :             else if (dfX1 > dfLeftBorderX && dfX1 < 180 && dfX2 == -180 &&
    3180           0 :                      i + 1 < poLS->getNumPoints() &&
    3181          13 :                      poLS->getX(i + 1) + dfXOffset > dfLeftBorderX &&
    3182           0 :                      poLS->getX(i + 1) + dfXOffset < 180)
    3183             :             {
    3184           0 :                 if (bIs3D)
    3185           0 :                     poNewLS->addPoint(180, poLS->getY(i), poLS->getZ(i));
    3186             :                 else
    3187           0 :                     poNewLS->addPoint(180, poLS->getY(i));
    3188             : 
    3189           0 :                 i++;
    3190             : 
    3191           0 :                 if (bIs3D)
    3192           0 :                     poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i),
    3193             :                                       poLS->getZ(i));
    3194             :                 else
    3195           0 :                     poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i));
    3196           0 :                 continue;
    3197             :             }
    3198             : 
    3199           9 :             if (dfX1 < dfRightBorderX && dfX2 > dfLeftBorderX)
    3200             :             {
    3201           5 :                 std::swap(dfX1, dfX2);
    3202           5 :                 std::swap(dfY1, dfY2);
    3203           5 :                 std::swap(dfZ1, dfZ2);
    3204             :             }
    3205           9 :             if (dfX1 > dfLeftBorderX && dfX2 < dfRightBorderX)
    3206           9 :                 dfX2 += 360;
    3207             : 
    3208           9 :             if (dfX1 <= 180 && dfX2 >= 180 && dfX1 < dfX2)
    3209             :             {
    3210           9 :                 const double dfRatio = (180 - dfX1) / (dfX2 - dfX1);
    3211           9 :                 const double dfY = dfRatio * dfY2 + (1 - dfRatio) * dfY1;
    3212           9 :                 const double dfZ = dfRatio * dfZ2 + (1 - dfRatio) * dfZ1;
    3213             :                 double dfNewX =
    3214           9 :                     poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? 180 : -180;
    3215          18 :                 if (poNewLS->getNumPoints() == 0 ||
    3216          18 :                     poNewLS->getX(poNewLS->getNumPoints() - 1) != dfNewX ||
    3217           2 :                     poNewLS->getY(poNewLS->getNumPoints() - 1) != dfY)
    3218             :                 {
    3219           7 :                     if (bIs3D)
    3220           0 :                         poNewLS->addPoint(dfNewX, dfY, dfZ);
    3221             :                     else
    3222           7 :                         poNewLS->addPoint(dfNewX, dfY);
    3223             :                 }
    3224           9 :                 poNewLS = new OGRLineString();
    3225           9 :                 if (bIs3D)
    3226           0 :                     poNewLS->addPoint(
    3227           0 :                         poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? -180
    3228             :                                                                       : 180,
    3229             :                         dfY, dfZ);
    3230             :                 else
    3231           9 :                     poNewLS->addPoint(
    3232           9 :                         poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? -180
    3233             :                                                                       : 180,
    3234             :                         dfY);
    3235           9 :                 poMulti->addGeometryDirectly(poNewLS);
    3236             :             }
    3237             :             else
    3238             :             {
    3239           0 :                 poNewLS = new OGRLineString();
    3240           0 :                 poMulti->addGeometryDirectly(poNewLS);
    3241             :             }
    3242             :         }
    3243          27 :         if (bIs3D)
    3244           0 :             poNewLS->addPoint(dfX, poLS->getY(i), poLS->getZ(i));
    3245             :         else
    3246          27 :             poNewLS->addPoint(dfX, poLS->getY(i));
    3247             :     }
    3248           8 : }
    3249             : 
    3250             : /************************************************************************/
    3251             : /*                  FixPolygonCoordinatesAtDateLine()                   */
    3252             : /************************************************************************/
    3253             : 
    3254             : #ifdef HAVE_GEOS
    3255           4 : static void FixPolygonCoordinatesAtDateLine(OGRPolygon *poPoly,
    3256             :                                             double dfDateLineOffset)
    3257             : {
    3258           4 :     const double dfLeftBorderX = 180 - dfDateLineOffset;
    3259           4 :     const double dfRightBorderX = -180 + dfDateLineOffset;
    3260           4 :     const double dfDiffSpace = 360 - dfDateLineOffset;
    3261             : 
    3262           8 :     for (int iPart = 0; iPart < 1 + poPoly->getNumInteriorRings(); iPart++)
    3263             :     {
    3264           4 :         OGRLineString *poLS = (iPart == 0) ? poPoly->getExteriorRing()
    3265           4 :                                            : poPoly->getInteriorRing(iPart - 1);
    3266           4 :         bool bGoEast = false;
    3267           4 :         const bool bIs3D = poLS->getCoordinateDimension() == 3;
    3268          36 :         for (int i = 1; i < poLS->getNumPoints(); i++)
    3269             :         {
    3270          32 :             double dfX = poLS->getX(i);
    3271          32 :             const double dfPrevX = poLS->getX(i - 1);
    3272          32 :             const double dfDiffLong = fabs(dfX - dfPrevX);
    3273          32 :             if (dfDiffLong > dfDiffSpace)
    3274             :             {
    3275          18 :                 if ((dfPrevX > dfLeftBorderX && dfX < dfRightBorderX) ||
    3276           6 :                     (dfX < 0 && bGoEast))
    3277             :                 {
    3278          16 :                     dfX += 360;
    3279          16 :                     bGoEast = true;
    3280          16 :                     if (bIs3D)
    3281           0 :                         poLS->setPoint(i, dfX, poLS->getY(i), poLS->getZ(i));
    3282             :                     else
    3283          16 :                         poLS->setPoint(i, dfX, poLS->getY(i));
    3284             :                 }
    3285           2 :                 else if (dfPrevX < dfRightBorderX && dfX > dfLeftBorderX)
    3286             :                 {
    3287           8 :                     for (int j = i - 1; j >= 0; j--)
    3288             :                     {
    3289           6 :                         dfX = poLS->getX(j);
    3290           6 :                         if (dfX < 0)
    3291             :                         {
    3292           6 :                             if (bIs3D)
    3293           0 :                                 poLS->setPoint(j, dfX + 360, poLS->getY(j),
    3294             :                                                poLS->getZ(j));
    3295             :                             else
    3296           6 :                                 poLS->setPoint(j, dfX + 360, poLS->getY(j));
    3297             :                         }
    3298             :                     }
    3299           2 :                     bGoEast = false;
    3300             :                 }
    3301             :                 else
    3302             :                 {
    3303           0 :                     bGoEast = false;
    3304             :                 }
    3305             :             }
    3306             :         }
    3307             :     }
    3308           4 : }
    3309             : #endif
    3310             : 
    3311             : /************************************************************************/
    3312             : /*                           AddOffsetToLon()                           */
    3313             : /************************************************************************/
    3314             : 
    3315          17 : static void AddOffsetToLon(OGRGeometry *poGeom, double dfOffset)
    3316             : {
    3317          17 :     switch (wkbFlatten(poGeom->getGeometryType()))
    3318             :     {
    3319           7 :         case wkbPolygon:
    3320             :         {
    3321          14 :             for (auto poSubGeom : *(poGeom->toPolygon()))
    3322             :             {
    3323           7 :                 AddOffsetToLon(poSubGeom, dfOffset);
    3324             :             }
    3325             : 
    3326           7 :             break;
    3327             :         }
    3328             : 
    3329           0 :         case wkbMultiLineString:
    3330             :         case wkbMultiPolygon:
    3331             :         case wkbGeometryCollection:
    3332             :         {
    3333           0 :             for (auto poSubGeom : *(poGeom->toGeometryCollection()))
    3334             :             {
    3335           0 :                 AddOffsetToLon(poSubGeom, dfOffset);
    3336             :             }
    3337             : 
    3338           0 :             break;
    3339             :         }
    3340             : 
    3341          10 :         case wkbLineString:
    3342             :         {
    3343          10 :             OGRLineString *poLineString = poGeom->toLineString();
    3344          10 :             const int nPointCount = poLineString->getNumPoints();
    3345          10 :             const int nCoordDim = poLineString->getCoordinateDimension();
    3346          63 :             for (int iPoint = 0; iPoint < nPointCount; iPoint++)
    3347             :             {
    3348          53 :                 if (nCoordDim == 2)
    3349         106 :                     poLineString->setPoint(
    3350          53 :                         iPoint, poLineString->getX(iPoint) + dfOffset,
    3351             :                         poLineString->getY(iPoint));
    3352             :                 else
    3353           0 :                     poLineString->setPoint(
    3354           0 :                         iPoint, poLineString->getX(iPoint) + dfOffset,
    3355             :                         poLineString->getY(iPoint), poLineString->getZ(iPoint));
    3356             :             }
    3357          10 :             break;
    3358             :         }
    3359             : 
    3360           0 :         default:
    3361           0 :             break;
    3362             :     }
    3363          17 : }
    3364             : 
    3365             : /************************************************************************/
    3366             : /*                        AddSimpleGeomToMulti()                        */
    3367             : /************************************************************************/
    3368             : 
    3369             : #ifdef HAVE_GEOS
    3370          12 : static void AddSimpleGeomToMulti(OGRGeometryCollection *poMulti,
    3371             :                                  const OGRGeometry *poGeom)
    3372             : {
    3373          12 :     switch (wkbFlatten(poGeom->getGeometryType()))
    3374             :     {
    3375          12 :         case wkbPolygon:
    3376             :         case wkbLineString:
    3377          12 :             poMulti->addGeometry(poGeom);
    3378          12 :             break;
    3379             : 
    3380           0 :         case wkbMultiLineString:
    3381             :         case wkbMultiPolygon:
    3382             :         case wkbGeometryCollection:
    3383             :         {
    3384           0 :             for (const auto poSubGeom : *(poGeom->toGeometryCollection()))
    3385             :             {
    3386           0 :                 AddSimpleGeomToMulti(poMulti, poSubGeom);
    3387             :             }
    3388           0 :             break;
    3389             :         }
    3390             : 
    3391           0 :         default:
    3392           0 :             break;
    3393             :     }
    3394          12 : }
    3395             : #endif  // #ifdef HAVE_GEOS
    3396             : 
    3397             : /************************************************************************/
    3398             : /*                         WrapPointDateLine()                          */
    3399             : /************************************************************************/
    3400             : 
    3401          14 : static void WrapPointDateLine(OGRPoint *poPoint)
    3402             : {
    3403          14 :     if (poPoint->getX() > 180)
    3404             :     {
    3405           2 :         poPoint->setX(fmod(poPoint->getX() + 180, 360) - 180);
    3406             :     }
    3407          12 :     else if (poPoint->getX() < -180)
    3408             :     {
    3409           3 :         poPoint->setX(-(fmod(-poPoint->getX() + 180, 360) - 180));
    3410             :     }
    3411          14 : }
    3412             : 
    3413             : /************************************************************************/
    3414             : /*                 CutGeometryOnDateLineAndAddToMulti()                 */
    3415             : /************************************************************************/
    3416             : 
    3417          73 : static void CutGeometryOnDateLineAndAddToMulti(OGRGeometryCollection *poMulti,
    3418             :                                                const OGRGeometry *poGeom,
    3419             :                                                double dfDateLineOffset)
    3420             : {
    3421          73 :     const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
    3422          73 :     switch (eGeomType)
    3423             :     {
    3424           1 :         case wkbPoint:
    3425             :         {
    3426           1 :             auto poPoint = poGeom->toPoint()->clone();
    3427           1 :             WrapPointDateLine(poPoint);
    3428           1 :             poMulti->addGeometryDirectly(poPoint);
    3429           1 :             break;
    3430             :         }
    3431             : 
    3432          57 :         case wkbPolygon:
    3433             :         case wkbLineString:
    3434             :         {
    3435          57 :             bool bSplitLineStringAtDateline = false;
    3436          57 :             OGREnvelope oEnvelope;
    3437             : 
    3438          57 :             poGeom->getEnvelope(&oEnvelope);
    3439          57 :             const bool bAroundMinus180 = (oEnvelope.MinX < -180.0);
    3440             : 
    3441             :             // Naive heuristics... Place to improve.
    3442             : #ifdef HAVE_GEOS
    3443          57 :             std::unique_ptr<OGRGeometry> poDupGeom;
    3444          57 :             bool bWrapDateline = false;
    3445             : #endif
    3446             : 
    3447          57 :             const double dfLeftBorderX = 180 - dfDateLineOffset;
    3448          57 :             const double dfRightBorderX = -180 + dfDateLineOffset;
    3449          57 :             const double dfDiffSpace = 360 - dfDateLineOffset;
    3450             : 
    3451          57 :             const double dfXOffset = (bAroundMinus180) ? 360.0 : 0.0;
    3452          57 :             if (oEnvelope.MinX < -180 || oEnvelope.MaxX > 180 ||
    3453          55 :                 (oEnvelope.MinX + dfXOffset > dfLeftBorderX &&
    3454          12 :                  oEnvelope.MaxX + dfXOffset > 180))
    3455             :             {
    3456             : #ifndef HAVE_GEOS
    3457             :                 CPLError(CE_Failure, CPLE_NotSupported,
    3458             :                          "GEOS support not enabled.");
    3459             : #else
    3460           2 :                 bWrapDateline = true;
    3461             : #endif
    3462             :             }
    3463             :             else
    3464             :             {
    3465             :                 auto poLS = eGeomType == wkbPolygon
    3466          55 :                                 ? poGeom->toPolygon()->getExteriorRing()
    3467          14 :                                 : poGeom->toLineString();
    3468          55 :                 if (poLS)
    3469             :                 {
    3470          55 :                     double dfMaxSmallDiffLong = 0;
    3471          55 :                     bool bHasBigDiff = false;
    3472             :                     // Detect big gaps in longitude.
    3473         317 :                     for (int i = 1; i < poLS->getNumPoints(); i++)
    3474             :                     {
    3475         262 :                         const double dfPrevX = poLS->getX(i - 1) + dfXOffset;
    3476         262 :                         const double dfX = poLS->getX(i) + dfXOffset;
    3477         262 :                         const double dfDiffLong = fabs(dfX - dfPrevX);
    3478             : 
    3479         262 :                         if (dfDiffLong > dfDiffSpace &&
    3480          11 :                             ((dfX > dfLeftBorderX &&
    3481          10 :                               dfPrevX < dfRightBorderX) ||
    3482          10 :                              (dfPrevX > dfLeftBorderX && dfX < dfRightBorderX)))
    3483             :                         {
    3484          21 :                             constexpr double EPSILON = 1e-5;
    3485          25 :                             if (!(std::fabs(dfDiffLong - 360) < EPSILON &&
    3486           4 :                                   std::fabs(std::fabs(poLS->getY(i)) - 90) <
    3487             :                                       EPSILON))
    3488             :                             {
    3489          17 :                                 bHasBigDiff = true;
    3490          21 :                             }
    3491             :                         }
    3492         241 :                         else if (dfDiffLong > dfMaxSmallDiffLong)
    3493          55 :                             dfMaxSmallDiffLong = dfDiffLong;
    3494             :                     }
    3495          55 :                     if (bHasBigDiff && dfMaxSmallDiffLong < dfDateLineOffset)
    3496             :                     {
    3497          12 :                         if (eGeomType == wkbLineString)
    3498           8 :                             bSplitLineStringAtDateline = true;
    3499             :                         else
    3500             :                         {
    3501             : #ifndef HAVE_GEOS
    3502             :                             CPLError(CE_Failure, CPLE_NotSupported,
    3503             :                                      "GEOS support not enabled.");
    3504             : #else
    3505           4 :                             poDupGeom.reset(poGeom->clone());
    3506           4 :                             FixPolygonCoordinatesAtDateLine(
    3507             :                                 poDupGeom->toPolygon(), dfDateLineOffset);
    3508             : 
    3509           4 :                             OGREnvelope sEnvelope;
    3510           4 :                             poDupGeom->getEnvelope(&sEnvelope);
    3511           4 :                             bWrapDateline = sEnvelope.MinX != sEnvelope.MaxX;
    3512             : #endif
    3513             :                         }
    3514             :                     }
    3515             :                 }
    3516             :             }
    3517             : 
    3518          57 :             if (bSplitLineStringAtDateline)
    3519             :             {
    3520           8 :                 SplitLineStringAtDateline(poMulti, poGeom->toLineString(),
    3521             :                                           dfDateLineOffset,
    3522             :                                           (bAroundMinus180) ? 360.0 : 0.0);
    3523             :             }
    3524             : #ifdef HAVE_GEOS
    3525          49 :             else if (bWrapDateline)
    3526             :             {
    3527             :                 const OGRGeometry *poWorkGeom =
    3528           6 :                     poDupGeom ? poDupGeom.get() : poGeom;
    3529           6 :                 assert(poWorkGeom);
    3530           6 :                 OGRGeometry *poRectangle1 = nullptr;
    3531           6 :                 OGRGeometry *poRectangle2 = nullptr;
    3532           6 :                 const char *pszWKT1 =
    3533             :                     !bAroundMinus180
    3534           6 :                         ? "POLYGON((-180 90,180 90,180 -90,-180 -90,-180 90))"
    3535             :                         : "POLYGON((180 90,-180 90,-180 -90,180 -90,180 90))";
    3536           6 :                 const char *pszWKT2 =
    3537             :                     !bAroundMinus180
    3538           6 :                         ? "POLYGON((180 90,360 90,360 -90,180 -90,180 90))"
    3539             :                         : "POLYGON((-180 90,-360 90,-360 -90,-180 -90,-180 "
    3540             :                           "90))";
    3541           6 :                 OGRGeometryFactory::createFromWkt(pszWKT1, nullptr,
    3542             :                                                   &poRectangle1);
    3543           6 :                 OGRGeometryFactory::createFromWkt(pszWKT2, nullptr,
    3544             :                                                   &poRectangle2);
    3545             :                 auto poGeom1 = std::unique_ptr<OGRGeometry>(
    3546          12 :                     poWorkGeom->Intersection(poRectangle1));
    3547             :                 auto poGeom2 = std::unique_ptr<OGRGeometry>(
    3548          12 :                     poWorkGeom->Intersection(poRectangle2));
    3549           6 :                 delete poRectangle1;
    3550           6 :                 delete poRectangle2;
    3551             : 
    3552           6 :                 if (poGeom1 != nullptr && poGeom2 != nullptr)
    3553             :                 {
    3554           6 :                     AddSimpleGeomToMulti(poMulti, poGeom1.get());
    3555           6 :                     AddOffsetToLon(poGeom2.get(),
    3556             :                                    !bAroundMinus180 ? -360.0 : 360.0);
    3557           6 :                     AddSimpleGeomToMulti(poMulti, poGeom2.get());
    3558             :                 }
    3559             :                 else
    3560             :                 {
    3561           0 :                     AddSimpleGeomToMulti(poMulti, poGeom);
    3562             :                 }
    3563             :             }
    3564             : #endif
    3565             :             else
    3566             :             {
    3567          43 :                 poMulti->addGeometry(poGeom);
    3568             :             }
    3569          57 :             break;
    3570             :         }
    3571             : 
    3572          15 :         case wkbMultiLineString:
    3573             :         case wkbMultiPolygon:
    3574             :         case wkbGeometryCollection:
    3575             :         {
    3576          48 :             for (const auto poSubGeom : *(poGeom->toGeometryCollection()))
    3577             :             {
    3578          33 :                 CutGeometryOnDateLineAndAddToMulti(poMulti, poSubGeom,
    3579             :                                                    dfDateLineOffset);
    3580             :             }
    3581          15 :             break;
    3582             :         }
    3583             : 
    3584           0 :         default:
    3585           0 :             break;
    3586             :     }
    3587          73 : }
    3588             : 
    3589             : #ifdef HAVE_GEOS
    3590             : 
    3591             : /************************************************************************/
    3592             : /*                            RemovePoint()                             */
    3593             : /************************************************************************/
    3594             : 
    3595           9 : static void RemovePoint(OGRGeometry *poGeom, const OGRPoint *poPoint)
    3596             : {
    3597           9 :     const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
    3598           9 :     switch (eType)
    3599             :     {
    3600           4 :         case wkbLineString:
    3601             :         {
    3602           4 :             OGRLineString *poLS = poGeom->toLineString();
    3603           4 :             const bool bIs3D = (poLS->getCoordinateDimension() == 3);
    3604           4 :             int j = 0;
    3605          32 :             for (int i = 0; i < poLS->getNumPoints(); i++)
    3606             :             {
    3607          31 :                 if (poLS->getX(i) != poPoint->getX() ||
    3608           3 :                     poLS->getY(i) != poPoint->getY())
    3609             :                 {
    3610          25 :                     if (i > j)
    3611             :                     {
    3612           5 :                         if (bIs3D)
    3613             :                         {
    3614           0 :                             poLS->setPoint(j, poLS->getX(i), poLS->getY(i),
    3615             :                                            poLS->getZ(i));
    3616             :                         }
    3617             :                         else
    3618             :                         {
    3619           5 :                             poLS->setPoint(j, poLS->getX(i), poLS->getY(i));
    3620             :                         }
    3621             :                     }
    3622          25 :                     j++;
    3623             :                 }
    3624             :             }
    3625           4 :             poLS->setNumPoints(j);
    3626           4 :             break;
    3627             :         }
    3628             : 
    3629           4 :         case wkbPolygon:
    3630             :         {
    3631           4 :             OGRPolygon *poPoly = poGeom->toPolygon();
    3632           8 :             for (auto *poRing : *poPoly)
    3633             :             {
    3634           4 :                 RemovePoint(poRing, poPoint);
    3635             :             }
    3636           4 :             poPoly->closeRings();
    3637           4 :             break;
    3638             :         }
    3639             : 
    3640           1 :         case wkbMultiLineString:
    3641             :         case wkbMultiPolygon:
    3642             :         case wkbGeometryCollection:
    3643             :         {
    3644           1 :             OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    3645           3 :             for (auto *poPart : *poGC)
    3646             :             {
    3647           2 :                 RemovePoint(poPart, poPoint);
    3648             :             }
    3649           1 :             break;
    3650             :         }
    3651             : 
    3652           0 :         default:
    3653           0 :             break;
    3654             :     }
    3655           9 : }
    3656             : 
    3657             : /************************************************************************/
    3658             : /*                              GetDist()                               */
    3659             : /************************************************************************/
    3660             : 
    3661          78 : static double GetDist(double dfDeltaX, double dfDeltaY)
    3662             : {
    3663          78 :     return sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY);
    3664             : }
    3665             : 
    3666             : /************************************************************************/
    3667             : /*                             AlterPole()                              */
    3668             : /*                                                                      */
    3669             : /* Replace and point at the pole by points really close to the pole,    */
    3670             : /* but on the previous and later segments.                              */
    3671             : /************************************************************************/
    3672             : 
    3673           5 : static void AlterPole(OGRGeometry *poGeom, OGRPoint *poPole,
    3674             :                       bool bIsRing = false)
    3675             : {
    3676           5 :     const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
    3677           5 :     switch (eType)
    3678             :     {
    3679           2 :         case wkbLineString:
    3680             :         {
    3681           2 :             if (!bIsRing)
    3682           0 :                 return;
    3683           2 :             OGRLineString *poLS = poGeom->toLineString();
    3684           2 :             const int nNumPoints = poLS->getNumPoints();
    3685           2 :             if (nNumPoints >= 4)
    3686             :             {
    3687           2 :                 const bool bIs3D = (poLS->getCoordinateDimension() == 3);
    3688           4 :                 std::vector<OGRRawPoint> aoPoints;
    3689           4 :                 std::vector<double> adfZ;
    3690           2 :                 bool bMustClose = false;
    3691          10 :                 for (int i = 0; i < nNumPoints; i++)
    3692             :                 {
    3693           8 :                     const double dfX = poLS->getX(i);
    3694           8 :                     const double dfY = poLS->getY(i);
    3695           8 :                     if (dfX == poPole->getX() && dfY == poPole->getY())
    3696             :                     {
    3697             :                         // Replace the pole by points really close to it
    3698           2 :                         if (i == 0)
    3699           0 :                             bMustClose = true;
    3700           2 :                         if (i == nNumPoints - 1)
    3701           0 :                             continue;
    3702           2 :                         const int iBefore = i > 0 ? i - 1 : nNumPoints - 2;
    3703           2 :                         double dfXBefore = poLS->getX(iBefore);
    3704           2 :                         double dfYBefore = poLS->getY(iBefore);
    3705             :                         double dfNorm =
    3706           2 :                             GetDist(dfXBefore - dfX, dfYBefore - dfY);
    3707           2 :                         double dfXInterp =
    3708           2 :                             dfX + (dfXBefore - dfX) / dfNorm * 1.0e-7;
    3709           2 :                         double dfYInterp =
    3710           2 :                             dfY + (dfYBefore - dfY) / dfNorm * 1.0e-7;
    3711           2 :                         OGRRawPoint oPoint;
    3712           2 :                         oPoint.x = dfXInterp;
    3713           2 :                         oPoint.y = dfYInterp;
    3714           2 :                         aoPoints.push_back(oPoint);
    3715           2 :                         adfZ.push_back(poLS->getZ(i));
    3716             : 
    3717           2 :                         const int iAfter = i + 1;
    3718           2 :                         double dfXAfter = poLS->getX(iAfter);
    3719           2 :                         double dfYAfter = poLS->getY(iAfter);
    3720           2 :                         dfNorm = GetDist(dfXAfter - dfX, dfYAfter - dfY);
    3721           2 :                         dfXInterp = dfX + (dfXAfter - dfX) / dfNorm * 1e-7;
    3722           2 :                         dfYInterp = dfY + (dfYAfter - dfY) / dfNorm * 1e-7;
    3723           2 :                         oPoint.x = dfXInterp;
    3724           2 :                         oPoint.y = dfYInterp;
    3725           2 :                         aoPoints.push_back(oPoint);
    3726           2 :                         adfZ.push_back(poLS->getZ(i));
    3727             :                     }
    3728             :                     else
    3729             :                     {
    3730           6 :                         OGRRawPoint oPoint;
    3731           6 :                         oPoint.x = dfX;
    3732           6 :                         oPoint.y = dfY;
    3733           6 :                         aoPoints.push_back(oPoint);
    3734           6 :                         adfZ.push_back(poLS->getZ(i));
    3735             :                     }
    3736             :                 }
    3737           2 :                 if (bMustClose)
    3738             :                 {
    3739           0 :                     aoPoints.push_back(aoPoints[0]);
    3740           0 :                     adfZ.push_back(adfZ[0]);
    3741             :                 }
    3742             : 
    3743           4 :                 poLS->setPoints(static_cast<int>(aoPoints.size()),
    3744           2 :                                 &(aoPoints[0]), bIs3D ? &adfZ[0] : nullptr);
    3745             :             }
    3746           2 :             break;
    3747             :         }
    3748             : 
    3749           2 :         case wkbPolygon:
    3750             :         {
    3751           2 :             OGRPolygon *poPoly = poGeom->toPolygon();
    3752           2 :             if (poPoly->getExteriorRing() != nullptr)
    3753             :             {
    3754           2 :                 AlterPole(poPoly->getExteriorRing(), poPole, true);
    3755           2 :                 for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
    3756             :                 {
    3757           0 :                     AlterPole(poPoly->getInteriorRing(i), poPole, true);
    3758             :                 }
    3759             :             }
    3760           2 :             break;
    3761             :         }
    3762             : 
    3763           1 :         case wkbMultiLineString:
    3764             :         case wkbMultiPolygon:
    3765             :         case wkbGeometryCollection:
    3766             :         {
    3767           1 :             OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    3768           2 :             for (int i = 0; i < poGC->getNumGeometries(); ++i)
    3769             :             {
    3770           1 :                 AlterPole(poGC->getGeometryRef(i), poPole);
    3771             :             }
    3772           1 :             break;
    3773             :         }
    3774             : 
    3775           0 :         default:
    3776           0 :             break;
    3777             :     }
    3778             : }
    3779             : 
    3780             : /************************************************************************/
    3781             : /*                        IsPolarToGeographic()                         */
    3782             : /*                                                                      */
    3783             : /* Returns true if poCT transforms from a projection that includes one  */
    3784             : /* of the pole in a continuous way.                                     */
    3785             : /************************************************************************/
    3786             : 
    3787          26 : static bool IsPolarToGeographic(OGRCoordinateTransformation *poCT,
    3788             :                                 OGRCoordinateTransformation *poRevCT,
    3789             :                                 bool &bIsNorthPolarOut)
    3790             : {
    3791          26 :     bool bIsNorthPolar = false;
    3792          26 :     bool bIsSouthPolar = false;
    3793          26 :     double x = 0.0;
    3794          26 :     double y = 90.0;
    3795             : 
    3796          26 :     CPLErrorStateBackuper oErrorBackuper(CPLQuietErrorHandler);
    3797             : 
    3798          26 :     const bool bBackupEmitErrors = poCT->GetEmitErrors();
    3799          26 :     poRevCT->SetEmitErrors(false);
    3800          26 :     poCT->SetEmitErrors(false);
    3801             : 
    3802          26 :     if (poRevCT->Transform(1, &x, &y) &&
    3803             :         // Surprisingly, pole south projects correctly back &
    3804             :         // forth for antarctic polar stereographic.  Therefore, check that
    3805             :         // the projected value is not too big.
    3806          26 :         fabs(x) < 1e10 && fabs(y) < 1e10)
    3807             :     {
    3808          24 :         double x_tab[] = {x, x - 1e5, x + 1e5};
    3809          24 :         double y_tab[] = {y, y - 1e5, y + 1e5};
    3810          24 :         if (poCT->Transform(3, x_tab, y_tab) &&
    3811          24 :             fabs(y_tab[0] - (90.0)) < 1e-10 &&
    3812          71 :             fabs(x_tab[2] - x_tab[1]) > 170 &&
    3813          23 :             fabs(y_tab[2] - y_tab[1]) < 1e-10)
    3814             :         {
    3815          23 :             bIsNorthPolar = true;
    3816             :         }
    3817             :     }
    3818             : 
    3819          26 :     x = 0.0;
    3820          26 :     y = -90.0;
    3821          26 :     if (poRevCT->Transform(1, &x, &y) && fabs(x) < 1e10 && fabs(y) < 1e10)
    3822             :     {
    3823          15 :         double x_tab[] = {x, x - 1e5, x + 1e5};
    3824          15 :         double y_tab[] = {y, y - 1e5, y + 1e5};
    3825          15 :         if (poCT->Transform(3, x_tab, y_tab) &&
    3826          15 :             fabs(y_tab[0] - (-90.0)) < 1e-10 &&
    3827          44 :             fabs(x_tab[2] - x_tab[1]) > 170 &&
    3828          14 :             fabs(y_tab[2] - y_tab[1]) < 1e-10)
    3829             :         {
    3830          14 :             bIsSouthPolar = true;
    3831             :         }
    3832             :     }
    3833             : 
    3834          26 :     poCT->SetEmitErrors(bBackupEmitErrors);
    3835             : 
    3836          26 :     if (bIsNorthPolar && bIsSouthPolar)
    3837             :     {
    3838          13 :         bIsNorthPolar = false;
    3839          13 :         bIsSouthPolar = false;
    3840             :     }
    3841             : 
    3842          26 :     bIsNorthPolarOut = bIsNorthPolar;
    3843          52 :     return bIsNorthPolar || bIsSouthPolar;
    3844             : }
    3845             : 
    3846             : /************************************************************************/
    3847             : /*                            ContainsPole()                            */
    3848             : /************************************************************************/
    3849             : 
    3850          14 : static bool ContainsPole(const OGRGeometry *poGeom, const OGRPoint *poPole)
    3851             : {
    3852          14 :     switch (wkbFlatten(poGeom->getGeometryType()))
    3853             :     {
    3854          12 :         case wkbPolygon:
    3855             :         case wkbCurvePolygon:
    3856             :         {
    3857          12 :             const auto poPoly = poGeom->toCurvePolygon();
    3858          12 :             if (poPoly->getNumInteriorRings() > 0)
    3859             :             {
    3860           3 :                 const auto poRing = poPoly->getExteriorRingCurve();
    3861           6 :                 OGRPolygon oPolygon;
    3862           3 :                 oPolygon.addRing(poRing);
    3863           3 :                 return oPolygon.Contains(poPole);
    3864             :             }
    3865             : 
    3866           9 :             return poGeom->Contains(poPole);
    3867             :         }
    3868             : 
    3869           2 :         case wkbMultiPolygon:
    3870             :         case wkbMultiSurface:
    3871             :         case wkbGeometryCollection:
    3872             :         {
    3873           3 :             for (const auto *poSubGeom : poGeom->toGeometryCollection())
    3874             :             {
    3875           2 :                 if (ContainsPole(poSubGeom, poPole))
    3876           1 :                     return true;
    3877             :             }
    3878           1 :             return false;
    3879             :         }
    3880             : 
    3881           0 :         default:
    3882           0 :             break;
    3883             :     }
    3884           0 :     return poGeom->Contains(poPole);
    3885             : }
    3886             : 
    3887             : /************************************************************************/
    3888             : /*                 TransformBeforePolarToGeographic()                   */
    3889             : /*                                                                      */
    3890             : /* Transform the geometry (by intersection), so as to cut each geometry */
    3891             : /* that crosses the pole, in 2 parts. Do also tricks for geometries     */
    3892             : /* that just touch the pole.                                            */
    3893             : /************************************************************************/
    3894             : 
    3895          12 : static std::unique_ptr<OGRGeometry> TransformBeforePolarToGeographic(
    3896             :     OGRCoordinateTransformation *poRevCT, bool bIsNorthPolar,
    3897             :     std::unique_ptr<OGRGeometry> poDstGeom, bool &bNeedPostCorrectionOut)
    3898             : {
    3899          12 :     const int nSign = (bIsNorthPolar) ? 1 : -1;
    3900             : 
    3901             :     // Does the geometry fully contains the pole ? */
    3902          12 :     double dfXPole = 0.0;
    3903          12 :     double dfYPole = nSign * 90.0;
    3904          12 :     poRevCT->Transform(1, &dfXPole, &dfYPole);
    3905          24 :     OGRPoint oPole(dfXPole, dfYPole);
    3906          12 :     const bool bContainsPole = ContainsPole(poDstGeom.get(), &oPole);
    3907             : 
    3908          12 :     const double EPS = 1e-9;
    3909             : 
    3910             :     // Does the geometry touches the pole and intersects the antimeridian ?
    3911          12 :     double dfNearPoleAntiMeridianX = 180.0;
    3912          12 :     double dfNearPoleAntiMeridianY = nSign * (90.0 - EPS);
    3913          12 :     poRevCT->Transform(1, &dfNearPoleAntiMeridianX, &dfNearPoleAntiMeridianY);
    3914             :     OGRPoint oNearPoleAntimeridian(dfNearPoleAntiMeridianX,
    3915          24 :                                    dfNearPoleAntiMeridianY);
    3916             :     const bool bContainsNearPoleAntimeridian =
    3917          12 :         poDstGeom->Contains(&oNearPoleAntimeridian);
    3918             : 
    3919             :     // Does the geometry intersects the antimeridian ?
    3920          24 :     OGRLineString oAntiMeridianLine;
    3921          12 :     oAntiMeridianLine.addPoint(180.0, nSign * (90.0 - EPS));
    3922          12 :     oAntiMeridianLine.addPoint(180.0, 0);
    3923          12 :     oAntiMeridianLine.transform(poRevCT);
    3924             :     const bool bIntersectsAntimeridian =
    3925          21 :         bContainsNearPoleAntimeridian ||
    3926           9 :         poDstGeom->Intersects(&oAntiMeridianLine);
    3927             : 
    3928             :     // Does the geometry touches the pole (but not intersect the antimeridian) ?
    3929             :     const bool bRegularTouchesPole =
    3930           7 :         !bContainsPole && !bContainsNearPoleAntimeridian &&
    3931          19 :         !bIntersectsAntimeridian && poDstGeom->Touches(&oPole);
    3932             : 
    3933             :     // Create a polygon of nearly a full hemisphere, but excluding the anti
    3934             :     // meridian and the pole.
    3935          24 :     OGRPolygon oCutter;
    3936          12 :     OGRLinearRing *poRing = new OGRLinearRing();
    3937          12 :     poRing->addPoint(180.0 - EPS, 0);
    3938          12 :     poRing->addPoint(180.0 - EPS, nSign * (90.0 - EPS));
    3939             :     // If the geometry doesn't contain the pole, then we add it to the cutter
    3940             :     // geometry, but will later remove it completely (geometry touching the
    3941             :     // pole but intersecting the antimeridian), or will replace it by 2
    3942             :     // close points (geometry touching the pole without intersecting the
    3943             :     // antimeridian)
    3944          12 :     if (!bContainsPole)
    3945           7 :         poRing->addPoint(180.0, nSign * 90);
    3946          12 :     poRing->addPoint(-180.0 + EPS, nSign * (90.0 - EPS));
    3947          12 :     poRing->addPoint(-180.0 + EPS, 0);
    3948          12 :     poRing->addPoint(180.0 - EPS, 0);
    3949          12 :     oCutter.addRingDirectly(poRing);
    3950             : 
    3951          12 :     if (oCutter.transform(poRevCT) == OGRERR_NONE &&
    3952             :         // Check that longitudes +/- 180 are continuous
    3953             :         // in the polar projection
    3954          19 :         fabs(poRing->getX(0) - poRing->getX(poRing->getNumPoints() - 2)) < 1 &&
    3955           7 :         (bContainsPole || bIntersectsAntimeridian ||
    3956           3 :          bContainsNearPoleAntimeridian || bRegularTouchesPole))
    3957             :     {
    3958          11 :         if (bContainsPole || bIntersectsAntimeridian ||
    3959             :             bContainsNearPoleAntimeridian)
    3960             :         {
    3961             :             auto poNewGeom =
    3962          18 :                 std::unique_ptr<OGRGeometry>(poDstGeom->Difference(&oCutter));
    3963           9 :             if (poNewGeom)
    3964             :             {
    3965           9 :                 if (bContainsNearPoleAntimeridian)
    3966           3 :                     RemovePoint(poNewGeom.get(), &oPole);
    3967           9 :                 poDstGeom = std::move(poNewGeom);
    3968             :             }
    3969             :         }
    3970             : 
    3971          11 :         if (bRegularTouchesPole)
    3972             :         {
    3973           2 :             AlterPole(poDstGeom.get(), &oPole);
    3974             :         }
    3975             : 
    3976          11 :         bNeedPostCorrectionOut = true;
    3977             :     }
    3978          24 :     return poDstGeom;
    3979             : }
    3980             : 
    3981             : /************************************************************************/
    3982             : /*                   IsAntimeridianProjToGeographic()                   */
    3983             : /*                                                                      */
    3984             : /* Returns true if poCT transforms from a projection that includes the  */
    3985             : /* antimeridian in a continuous way.                                    */
    3986             : /************************************************************************/
    3987             : 
    3988          26 : static bool IsAntimeridianProjToGeographic(OGRCoordinateTransformation *poCT,
    3989             :                                            OGRCoordinateTransformation *poRevCT,
    3990             :                                            OGRGeometry *poDstGeometry)
    3991             : {
    3992          26 :     const bool bBackupEmitErrors = poCT->GetEmitErrors();
    3993          26 :     poRevCT->SetEmitErrors(false);
    3994          26 :     poCT->SetEmitErrors(false);
    3995             : 
    3996             :     // Find a reasonable latitude for the geometry
    3997          26 :     OGREnvelope sEnvelope;
    3998          26 :     poDstGeometry->getEnvelope(&sEnvelope);
    3999          52 :     OGRPoint pMean(sEnvelope.MinX, (sEnvelope.MinY + sEnvelope.MaxY) / 2);
    4000          26 :     if (pMean.transform(poCT) != OGRERR_NONE)
    4001             :     {
    4002           0 :         poCT->SetEmitErrors(bBackupEmitErrors);
    4003           0 :         return false;
    4004             :     }
    4005          26 :     const double dfMeanLat = pMean.getY();
    4006             : 
    4007             :     // Check that close points on each side of the antimeridian in (long, lat)
    4008             :     // project to close points in the source projection, and check that they
    4009             :     // roundtrip correctly.
    4010          26 :     const double EPS = 1.0e-8;
    4011          26 :     double x1 = 180 - EPS;
    4012          26 :     double y1 = dfMeanLat;
    4013          26 :     double x2 = -180 + EPS;
    4014          26 :     double y2 = dfMeanLat;
    4015          78 :     if (!poRevCT->Transform(1, &x1, &y1) || !poRevCT->Transform(1, &x2, &y2) ||
    4016          50 :         GetDist(x2 - x1, y2 - y1) > 1 || !poCT->Transform(1, &x1, &y1) ||
    4017          48 :         !poCT->Transform(1, &x2, &y2) ||
    4018          76 :         GetDist(x1 - (180 - EPS), y1 - dfMeanLat) > 2 * EPS ||
    4019          24 :         GetDist(x2 - (-180 + EPS), y2 - dfMeanLat) > 2 * EPS)
    4020             :     {
    4021           2 :         poCT->SetEmitErrors(bBackupEmitErrors);
    4022           2 :         return false;
    4023             :     }
    4024             : 
    4025          24 :     poCT->SetEmitErrors(bBackupEmitErrors);
    4026             : 
    4027          24 :     return true;
    4028             : }
    4029             : 
    4030             : /************************************************************************/
    4031             : /*                      CollectPointsOnAntimeridian()                   */
    4032             : /*                                                                      */
    4033             : /* Collect points that are the intersection of the lines of the geometry*/
    4034             : /* with the antimeridian.                                               */
    4035             : /************************************************************************/
    4036             : 
    4037          21 : static void CollectPointsOnAntimeridian(OGRGeometry *poGeom,
    4038             :                                         OGRCoordinateTransformation *poCT,
    4039             :                                         OGRCoordinateTransformation *poRevCT,
    4040             :                                         std::vector<OGRRawPoint> &aoPoints)
    4041             : {
    4042          21 :     const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
    4043          21 :     switch (eType)
    4044             :     {
    4045          11 :         case wkbLineString:
    4046             :         {
    4047          11 :             OGRLineString *poLS = poGeom->toLineString();
    4048          11 :             const int nNumPoints = poLS->getNumPoints();
    4049          44 :             for (int i = 0; i < nNumPoints - 1; i++)
    4050             :             {
    4051          33 :                 const double dfX = poLS->getX(i);
    4052          33 :                 const double dfY = poLS->getY(i);
    4053          33 :                 const double dfX2 = poLS->getX(i + 1);
    4054          33 :                 const double dfY2 = poLS->getY(i + 1);
    4055          33 :                 double dfXTrans = dfX;
    4056          33 :                 double dfYTrans = dfY;
    4057          33 :                 double dfX2Trans = dfX2;
    4058          33 :                 double dfY2Trans = dfY2;
    4059          33 :                 poCT->Transform(1, &dfXTrans, &dfYTrans);
    4060          33 :                 poCT->Transform(1, &dfX2Trans, &dfY2Trans);
    4061             :                 // Are we crossing the antimeridian ? (detecting by inversion of
    4062             :                 // sign of X)
    4063          33 :                 if ((dfX2 - dfX) * (dfX2Trans - dfXTrans) < 0 ||
    4064          14 :                     (dfX == dfX2 && dfX2Trans * dfXTrans < 0 &&
    4065           1 :                      fabs(fabs(dfXTrans) - 180) < 10 &&
    4066           1 :                      fabs(fabs(dfX2Trans) - 180) < 10))
    4067             :                 {
    4068          17 :                     double dfXStart = dfX;
    4069          17 :                     double dfYStart = dfY;
    4070          17 :                     double dfXEnd = dfX2;
    4071          17 :                     double dfYEnd = dfY2;
    4072          17 :                     double dfXStartTrans = dfXTrans;
    4073          17 :                     double dfXEndTrans = dfX2Trans;
    4074          17 :                     int iIter = 0;
    4075          17 :                     const double EPS = 1e-8;
    4076             :                     // Find point of the segment intersecting the antimeridian
    4077             :                     // by dichotomy
    4078         453 :                     for (;
    4079         470 :                          iIter < 50 && (fabs(fabs(dfXStartTrans) - 180) > EPS ||
    4080          25 :                                         fabs(fabs(dfXEndTrans) - 180) > EPS);
    4081             :                          ++iIter)
    4082             :                     {
    4083         453 :                         double dfXMid = (dfXStart + dfXEnd) / 2;
    4084         453 :                         double dfYMid = (dfYStart + dfYEnd) / 2;
    4085         453 :                         double dfXMidTrans = dfXMid;
    4086         453 :                         double dfYMidTrans = dfYMid;
    4087         453 :                         poCT->Transform(1, &dfXMidTrans, &dfYMidTrans);
    4088         453 :                         if ((dfXMid - dfXStart) *
    4089         453 :                                     (dfXMidTrans - dfXStartTrans) <
    4090         247 :                                 0 ||
    4091          22 :                             (dfXMid == dfXStart &&
    4092          22 :                              dfXMidTrans * dfXStartTrans < 0))
    4093             :                         {
    4094         214 :                             dfXEnd = dfXMid;
    4095         214 :                             dfYEnd = dfYMid;
    4096         214 :                             dfXEndTrans = dfXMidTrans;
    4097             :                         }
    4098             :                         else
    4099             :                         {
    4100         239 :                             dfXStart = dfXMid;
    4101         239 :                             dfYStart = dfYMid;
    4102         239 :                             dfXStartTrans = dfXMidTrans;
    4103             :                         }
    4104             :                     }
    4105          17 :                     if (iIter < 50)
    4106             :                     {
    4107          17 :                         OGRRawPoint oPoint;
    4108          17 :                         oPoint.x = (dfXStart + dfXEnd) / 2;
    4109          17 :                         oPoint.y = (dfYStart + dfYEnd) / 2;
    4110          17 :                         poCT->Transform(1, &(oPoint.x), &(oPoint.y));
    4111          17 :                         oPoint.x = 180.0;
    4112          17 :                         aoPoints.push_back(oPoint);
    4113             :                     }
    4114             :                 }
    4115             :             }
    4116          11 :             break;
    4117             :         }
    4118             : 
    4119           6 :         case wkbPolygon:
    4120             :         {
    4121           6 :             OGRPolygon *poPoly = poGeom->toPolygon();
    4122           6 :             if (poPoly->getExteriorRing() != nullptr)
    4123             :             {
    4124           6 :                 CollectPointsOnAntimeridian(poPoly->getExteriorRing(), poCT,
    4125             :                                             poRevCT, aoPoints);
    4126           6 :                 for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
    4127             :                 {
    4128           0 :                     CollectPointsOnAntimeridian(poPoly->getInteriorRing(i),
    4129             :                                                 poCT, poRevCT, aoPoints);
    4130             :                 }
    4131             :             }
    4132           6 :             break;
    4133             :         }
    4134             : 
    4135           4 :         case wkbMultiLineString:
    4136             :         case wkbMultiPolygon:
    4137             :         case wkbGeometryCollection:
    4138             :         {
    4139           4 :             OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    4140           8 :             for (int i = 0; i < poGC->getNumGeometries(); ++i)
    4141             :             {
    4142           4 :                 CollectPointsOnAntimeridian(poGC->getGeometryRef(i), poCT,
    4143             :                                             poRevCT, aoPoints);
    4144             :             }
    4145           4 :             break;
    4146             :         }
    4147             : 
    4148           0 :         default:
    4149           0 :             break;
    4150             :     }
    4151          21 : }
    4152             : 
    4153             : /************************************************************************/
    4154             : /*                       SortPointsByAscendingY()                       */
    4155             : /************************************************************************/
    4156             : 
    4157             : struct SortPointsByAscendingY
    4158             : {
    4159           8 :     bool operator()(const OGRRawPoint &a, const OGRRawPoint &b)
    4160             :     {
    4161           8 :         return a.y < b.y;
    4162             :     }
    4163             : };
    4164             : 
    4165             : /************************************************************************/
    4166             : /*              TransformBeforeAntimeridianToGeographic()               */
    4167             : /*                                                                      */
    4168             : /* Transform the geometry (by intersection), so as to cut each geometry */
    4169             : /* that crosses the antimeridian, in 2 parts.                           */
    4170             : /************************************************************************/
    4171             : 
    4172          24 : static std::unique_ptr<OGRGeometry> TransformBeforeAntimeridianToGeographic(
    4173             :     OGRCoordinateTransformation *poCT, OGRCoordinateTransformation *poRevCT,
    4174             :     std::unique_ptr<OGRGeometry> poDstGeom, bool &bNeedPostCorrectionOut)
    4175             : {
    4176          24 :     OGREnvelope sEnvelope;
    4177          24 :     poDstGeom->getEnvelope(&sEnvelope);
    4178          48 :     OGRPoint pMean(sEnvelope.MinX, (sEnvelope.MinY + sEnvelope.MaxY) / 2);
    4179          24 :     pMean.transform(poCT);
    4180          24 :     const double dfMeanLat = pMean.getY();
    4181          24 :     pMean.setX(180.0);
    4182          24 :     pMean.setY(dfMeanLat);
    4183          24 :     pMean.transform(poRevCT);
    4184             :     // Check if the antimeridian crosses the bbox of our geometry
    4185          36 :     if (!(pMean.getX() >= sEnvelope.MinX && pMean.getY() >= sEnvelope.MinY &&
    4186          12 :           pMean.getX() <= sEnvelope.MaxX && pMean.getY() <= sEnvelope.MaxY))
    4187             :     {
    4188          13 :         return poDstGeom;
    4189             :     }
    4190             : 
    4191             :     // Collect points that are the intersection of the lines of the geometry
    4192             :     // with the antimeridian
    4193          22 :     std::vector<OGRRawPoint> aoPoints;
    4194          11 :     CollectPointsOnAntimeridian(poDstGeom.get(), poCT, poRevCT, aoPoints);
    4195          11 :     if (aoPoints.empty())
    4196           0 :         return poDstGeom;
    4197             : 
    4198             :     SortPointsByAscendingY sortFunc;
    4199          11 :     std::sort(aoPoints.begin(), aoPoints.end(), sortFunc);
    4200             : 
    4201          11 :     const double EPS = 1e-9;
    4202             : 
    4203             :     // Build a very thin polygon cutting the antimeridian at our points
    4204          11 :     OGRLinearRing *poLR = new OGRLinearRing;
    4205             :     {
    4206          11 :         double x = 180.0 - EPS;
    4207          11 :         double y = aoPoints[0].y - EPS;
    4208          11 :         poRevCT->Transform(1, &x, &y);
    4209          11 :         poLR->addPoint(x, y);
    4210             :     }
    4211          28 :     for (const auto &oPoint : aoPoints)
    4212             :     {
    4213          17 :         double x = 180.0 - EPS;
    4214          17 :         double y = oPoint.y;
    4215          17 :         poRevCT->Transform(1, &x, &y);
    4216          17 :         poLR->addPoint(x, y);
    4217             :     }
    4218             :     {
    4219          11 :         double x = 180.0 - EPS;
    4220          11 :         double y = aoPoints.back().y + EPS;
    4221          11 :         poRevCT->Transform(1, &x, &y);
    4222          11 :         poLR->addPoint(x, y);
    4223             :     }
    4224             :     {
    4225          11 :         double x = 180.0 + EPS;
    4226          11 :         double y = aoPoints.back().y + EPS;
    4227          11 :         poRevCT->Transform(1, &x, &y);
    4228          11 :         poLR->addPoint(x, y);
    4229             :     }
    4230          28 :     for (size_t i = aoPoints.size(); i > 0;)
    4231             :     {
    4232          17 :         --i;
    4233          17 :         const OGRRawPoint &oPoint = aoPoints[i];
    4234          17 :         double x = 180.0 + EPS;
    4235          17 :         double y = oPoint.y;
    4236          17 :         poRevCT->Transform(1, &x, &y);
    4237          17 :         poLR->addPoint(x, y);
    4238             :     }
    4239             :     {
    4240          11 :         double x = 180.0 + EPS;
    4241          11 :         double y = aoPoints[0].y - EPS;
    4242          11 :         poRevCT->Transform(1, &x, &y);
    4243          11 :         poLR->addPoint(x, y);
    4244             :     }
    4245          11 :     poLR->closeRings();
    4246             : 
    4247          22 :     OGRPolygon oPolyToCut;
    4248          11 :     oPolyToCut.addRingDirectly(poLR);
    4249             : 
    4250             : #if DEBUG_VERBOSE
    4251             :     char *pszWKT = NULL;
    4252             :     oPolyToCut.exportToWkt(&pszWKT);
    4253             :     CPLDebug("OGR", "Geometry to cut: %s", pszWKT);
    4254             :     CPLFree(pszWKT);
    4255             : #endif
    4256             : 
    4257             :     // Get the geometry without the antimeridian
    4258             :     auto poInter =
    4259          22 :         std::unique_ptr<OGRGeometry>(poDstGeom->Difference(&oPolyToCut));
    4260          11 :     if (poInter != nullptr)
    4261             :     {
    4262          11 :         poDstGeom = std::move(poInter);
    4263          11 :         bNeedPostCorrectionOut = true;
    4264             :     }
    4265             : 
    4266          11 :     return poDstGeom;
    4267             : }
    4268             : 
    4269             : /************************************************************************/
    4270             : /*                 SnapCoordsCloseToLatLongBounds()                     */
    4271             : /*                                                                      */
    4272             : /* This function snaps points really close to the antimerdian or poles  */
    4273             : /* to their exact longitudes/latitudes.                                 */
    4274             : /************************************************************************/
    4275             : 
    4276          80 : static void SnapCoordsCloseToLatLongBounds(OGRGeometry *poGeom)
    4277             : {
    4278          80 :     const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
    4279          80 :     switch (eType)
    4280             :     {
    4281          37 :         case wkbLineString:
    4282             :         {
    4283          37 :             OGRLineString *poLS = poGeom->toLineString();
    4284          37 :             const double EPS = 1e-8;
    4285         243 :             for (int i = 0; i < poLS->getNumPoints(); i++)
    4286             :             {
    4287         412 :                 OGRPoint p;
    4288         206 :                 poLS->getPoint(i, &p);
    4289         206 :                 if (fabs(p.getX() - 180.0) < EPS)
    4290             :                 {
    4291          54 :                     p.setX(180.0);
    4292          54 :                     poLS->setPoint(i, &p);
    4293             :                 }
    4294         152 :                 else if (fabs(p.getX() - -180.0) < EPS)
    4295             :                 {
    4296          50 :                     p.setX(-180.0);
    4297          50 :                     poLS->setPoint(i, &p);
    4298             :                 }
    4299             : 
    4300         206 :                 if (fabs(p.getY() - 90.0) < EPS)
    4301             :                 {
    4302           8 :                     p.setY(90.0);
    4303           8 :                     poLS->setPoint(i, &p);
    4304             :                 }
    4305         198 :                 else if (fabs(p.getY() - -90.0) < EPS)
    4306             :                 {
    4307           2 :                     p.setY(-90.0);
    4308           2 :                     poLS->setPoint(i, &p);
    4309             :                 }
    4310             :             }
    4311          37 :             break;
    4312             :         }
    4313             : 
    4314          27 :         case wkbPolygon:
    4315             :         {
    4316          27 :             OGRPolygon *poPoly = poGeom->toPolygon();
    4317          27 :             if (poPoly->getExteriorRing() != nullptr)
    4318             :             {
    4319          27 :                 SnapCoordsCloseToLatLongBounds(poPoly->getExteriorRing());
    4320          27 :                 for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
    4321             :                 {
    4322           0 :                     SnapCoordsCloseToLatLongBounds(poPoly->getInteriorRing(i));
    4323             :                 }
    4324             :             }
    4325          27 :             break;
    4326             :         }
    4327             : 
    4328          16 :         case wkbMultiLineString:
    4329             :         case wkbMultiPolygon:
    4330             :         case wkbGeometryCollection:
    4331             :         {
    4332          16 :             OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    4333          47 :             for (int i = 0; i < poGC->getNumGeometries(); ++i)
    4334             :             {
    4335          31 :                 SnapCoordsCloseToLatLongBounds(poGC->getGeometryRef(i));
    4336             :             }
    4337          16 :             break;
    4338             :         }
    4339             : 
    4340           0 :         default:
    4341           0 :             break;
    4342             :     }
    4343          80 : }
    4344             : 
    4345             : #endif
    4346             : 
    4347             : /************************************************************************/
    4348             : /*                  TransformWithOptionsCache::Private                  */
    4349             : /************************************************************************/
    4350             : 
    4351             : struct OGRGeometryFactory::TransformWithOptionsCache::Private
    4352             : {
    4353             :     const OGRSpatialReference *poSourceCRS = nullptr;
    4354             :     const OGRSpatialReference *poTargetCRS = nullptr;
    4355             :     const OGRCoordinateTransformation *poCT = nullptr;
    4356             :     std::unique_ptr<OGRCoordinateTransformation> poRevCT{};
    4357             :     bool bIsPolar = false;
    4358             :     bool bIsNorthPolar = false;
    4359             : 
    4360          73 :     void clear()
    4361             :     {
    4362          73 :         poSourceCRS = nullptr;
    4363          73 :         poTargetCRS = nullptr;
    4364          73 :         poCT = nullptr;
    4365          73 :         poRevCT.reset();
    4366          73 :         bIsPolar = false;
    4367          73 :         bIsNorthPolar = false;
    4368          73 :     }
    4369             : };
    4370             : 
    4371             : /************************************************************************/
    4372             : /*                     TransformWithOptionsCache()                      */
    4373             : /************************************************************************/
    4374             : 
    4375        1384 : OGRGeometryFactory::TransformWithOptionsCache::TransformWithOptionsCache()
    4376        1384 :     : d(new Private())
    4377             : {
    4378        1384 : }
    4379             : 
    4380             : /************************************************************************/
    4381             : /*                     ~TransformWithOptionsCache()                     */
    4382             : /************************************************************************/
    4383             : 
    4384        1384 : OGRGeometryFactory::TransformWithOptionsCache::~TransformWithOptionsCache()
    4385             : {
    4386        1384 : }
    4387             : 
    4388             : /************************************************************************/
    4389             : /*               isTransformWithOptionsRegularTransform()               */
    4390             : /************************************************************************/
    4391             : 
    4392             : #ifdef HAVE_GEOS
    4393          86 : static bool MayBePolarToGeographic(const OGRSpatialReference *poSourceCRS,
    4394             :                                    const OGRSpatialReference *poTargetCRS)
    4395             : {
    4396          86 :     if (poSourceCRS && poTargetCRS && poSourceCRS->IsProjected() &&
    4397          61 :         poTargetCRS->IsGeographic() &&
    4398         224 :         poTargetCRS->GetAxisMappingStrategy() == OAMS_TRADITIONAL_GIS_ORDER &&
    4399             :         // check that angular units is degree
    4400          52 :         std::fabs(poTargetCRS->GetAngularUnits(nullptr) -
    4401          52 :                   CPLAtof(SRS_UA_DEGREE_CONV)) <=
    4402          52 :             1e-8 * CPLAtof(SRS_UA_DEGREE_CONV))
    4403             :     {
    4404          52 :         double dfWestLong = 0.0;
    4405          52 :         double dfSouthLat = 0.0;
    4406          52 :         double dfEastLong = 0.0;
    4407          52 :         double dfNorthLat = 0.0;
    4408          52 :         if (poSourceCRS->GetAreaOfUse(&dfWestLong, &dfSouthLat, &dfEastLong,
    4409          91 :                                       &dfNorthLat, nullptr) &&
    4410          39 :             !(dfSouthLat == -90.0 || dfNorthLat == 90.0 ||
    4411          33 :               dfWestLong == -180.0 || dfEastLong == 180.0 ||
    4412          25 :               dfWestLong > dfEastLong))
    4413             :         {
    4414             :             // Not a global geographic CRS
    4415          25 :             return false;
    4416             :         }
    4417          27 :         return true;
    4418             :     }
    4419          34 :     return false;
    4420             : }
    4421             : #endif
    4422             : 
    4423             : //! @cond Doxygen_Suppress
    4424             : /*static */
    4425          13 : bool OGRGeometryFactory::isTransformWithOptionsRegularTransform(
    4426             :     [[maybe_unused]] const OGRSpatialReference *poSourceCRS,
    4427             :     [[maybe_unused]] const OGRSpatialReference *poTargetCRS,
    4428             :     CSLConstList papszOptions)
    4429             : {
    4430          13 :     if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "WRAPDATELINE", "NO")) &&
    4431          13 :         poTargetCRS && poTargetCRS->IsGeographic())
    4432             :     {
    4433           0 :         return false;
    4434             :     }
    4435             : 
    4436             : #ifdef HAVE_GEOS
    4437          13 :     if (MayBePolarToGeographic(poSourceCRS, poTargetCRS))
    4438             :     {
    4439           1 :         return false;
    4440             :     }
    4441             : #endif
    4442             : 
    4443          12 :     return true;
    4444             : }
    4445             : 
    4446             : //! @endcond
    4447             : 
    4448             : /************************************************************************/
    4449             : /*                        transformWithOptions()                        */
    4450             : /************************************************************************/
    4451             : 
    4452             : /** Transform a geometry.
    4453             :  *
    4454             :  * This is an enhanced version of OGRGeometry::Transform().
    4455             :  *
    4456             :  * When reprojecting geometries from a Polar Stereographic projection or a
    4457             :  * projection naturally crossing the antimeridian (like UTM Zone 60) to a
    4458             :  * geographic CRS, it will cut geometries along the antimeridian. So a
    4459             :  * LineString might be returned as a MultiLineString.
    4460             :  *
    4461             :  * The WRAPDATELINE=YES option might be specified for circumstances to correct
    4462             :  * geometries that incorrectly go from a longitude on a side of the antimeridian
    4463             :  * to the other side, like a LINESTRING(-179 0,179 0) will be transformed to
    4464             :  * a MULTILINESTRING ((-179 0,-180 0),(180 0,179 0)). For that use case, hCT
    4465             :  * might be NULL.
    4466             :  *
    4467             :  * Supported options in papszOptions are:
    4468             :  * <ul>
    4469             :  * <li>WRAPDATELINE=YES</li>
    4470             :  * <li>DATELINEOFFSET=longitude_gap_in_degree. Defaults to 10.</li>
    4471             :  * </ul>
    4472             :  *
    4473             :  * This is the same as the C function OGR_GeomTransformer_Transform().
    4474             :  *
    4475             :  * @param poSrcGeom source geometry
    4476             :  * @param poCT coordinate transformation object, or NULL.
    4477             :  * @param papszOptions NULL terminated list of options, or NULL.
    4478             :  * @param cache Cache. May increase performance if persisted between invocations
    4479             :  * @return (new) transformed geometry.
    4480             :  */
    4481         650 : OGRGeometry *OGRGeometryFactory::transformWithOptions(
    4482             :     const OGRGeometry *poSrcGeom, OGRCoordinateTransformation *poCT,
    4483             :     CSLConstList papszOptions,
    4484             :     CPL_UNUSED const TransformWithOptionsCache &cache)
    4485             : {
    4486        1300 :     auto poDstGeom = std::unique_ptr<OGRGeometry>(poSrcGeom->clone());
    4487         650 :     if (poCT)
    4488             :     {
    4489             : #ifdef HAVE_GEOS
    4490         615 :         bool bNeedPostCorrection = false;
    4491         615 :         const auto poSourceCRS = poCT->GetSourceCS();
    4492         615 :         const auto poTargetCRS = poCT->GetTargetCS();
    4493         615 :         const auto eSrcGeomType = wkbFlatten(poSrcGeom->getGeometryType());
    4494             :         // Check if we are transforming from projected coordinates to
    4495             :         // geographic coordinates, with a chance that there might be polar or
    4496             :         // anti-meridian discontinuities. If so, create the inverse transform.
    4497         810 :         if (eSrcGeomType != wkbPoint && eSrcGeomType != wkbMultiPoint &&
    4498         195 :             (poSourceCRS != cache.d->poSourceCRS ||
    4499         122 :              poTargetCRS != cache.d->poTargetCRS || poCT != cache.d->poCT))
    4500             :         {
    4501          73 :             cache.d->clear();
    4502          73 :             cache.d->poSourceCRS = poSourceCRS;
    4503          73 :             cache.d->poTargetCRS = poTargetCRS;
    4504          73 :             cache.d->poCT = poCT;
    4505          73 :             if (MayBePolarToGeographic(poSourceCRS, poTargetCRS))
    4506             :             {
    4507          26 :                 cache.d->poRevCT.reset(OGRCreateCoordinateTransformation(
    4508             :                     poTargetCRS, poSourceCRS));
    4509          26 :                 cache.d->bIsNorthPolar = false;
    4510          26 :                 cache.d->bIsPolar = false;
    4511          26 :                 cache.d->poRevCT.reset(poCT->GetInverse());
    4512          78 :                 if (cache.d->poRevCT &&
    4513          26 :                     IsPolarToGeographic(poCT, cache.d->poRevCT.get(),
    4514          52 :                                         cache.d->bIsNorthPolar))
    4515             :                 {
    4516          11 :                     cache.d->bIsPolar = true;
    4517             :                 }
    4518             :             }
    4519             :         }
    4520             : 
    4521         615 :         if (auto poRevCT = cache.d->poRevCT.get())
    4522             :         {
    4523          38 :             if (cache.d->bIsPolar)
    4524             :             {
    4525          24 :                 poDstGeom = TransformBeforePolarToGeographic(
    4526          24 :                     poRevCT, cache.d->bIsNorthPolar, std::move(poDstGeom),
    4527          12 :                     bNeedPostCorrection);
    4528             :             }
    4529          26 :             else if (IsAntimeridianProjToGeographic(poCT, poRevCT,
    4530             :                                                     poDstGeom.get()))
    4531             :             {
    4532          48 :                 poDstGeom = TransformBeforeAntimeridianToGeographic(
    4533          48 :                     poCT, poRevCT, std::move(poDstGeom), bNeedPostCorrection);
    4534             :             }
    4535             :         }
    4536             : #endif
    4537         615 :         OGRErr eErr = poDstGeom->transform(poCT);
    4538         615 :         if (eErr != OGRERR_NONE)
    4539             :         {
    4540           4 :             return nullptr;
    4541             :         }
    4542             : #ifdef HAVE_GEOS
    4543         611 :         if (bNeedPostCorrection)
    4544             :         {
    4545          22 :             SnapCoordsCloseToLatLongBounds(poDstGeom.get());
    4546             :         }
    4547             : #endif
    4548             :     }
    4549             : 
    4550         646 :     if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "WRAPDATELINE", "NO")))
    4551             :     {
    4552          55 :         const auto poDstGeomSRS = poDstGeom->getSpatialReference();
    4553          55 :         if (poDstGeomSRS && !poDstGeomSRS->IsGeographic())
    4554             :         {
    4555           1 :             CPLDebugOnce(
    4556             :                 "OGR", "WRAPDATELINE is without effect when reprojecting to a "
    4557             :                        "non-geographic CRS");
    4558           1 :             return poDstGeom.release();
    4559             :         }
    4560             :         // TODO and we should probably also test that the axis order + data axis
    4561             :         // mapping is long-lat...
    4562             :         const OGRwkbGeometryType eType =
    4563          54 :             wkbFlatten(poDstGeom->getGeometryType());
    4564          54 :         if (eType == wkbPoint)
    4565             :         {
    4566           9 :             OGRPoint *poDstPoint = poDstGeom->toPoint();
    4567           9 :             WrapPointDateLine(poDstPoint);
    4568             :         }
    4569          45 :         else if (eType == wkbMultiPoint)
    4570             :         {
    4571           5 :             for (auto *poDstPoint : *(poDstGeom->toMultiPoint()))
    4572             :             {
    4573           4 :                 WrapPointDateLine(poDstPoint);
    4574             :             }
    4575             :         }
    4576             :         else
    4577             :         {
    4578          44 :             OGREnvelope sEnvelope;
    4579          44 :             poDstGeom->getEnvelope(&sEnvelope);
    4580          44 :             if (sEnvelope.MinX >= -360.0 && sEnvelope.MaxX <= -180.0)
    4581           2 :                 AddOffsetToLon(poDstGeom.get(), 360.0);
    4582          42 :             else if (sEnvelope.MinX >= 180.0 && sEnvelope.MaxX <= 360.0)
    4583           2 :                 AddOffsetToLon(poDstGeom.get(), -360.0);
    4584             :             else
    4585             :             {
    4586             :                 OGRwkbGeometryType eNewType;
    4587          40 :                 if (eType == wkbPolygon || eType == wkbMultiPolygon)
    4588          29 :                     eNewType = wkbMultiPolygon;
    4589          11 :                 else if (eType == wkbLineString || eType == wkbMultiLineString)
    4590          10 :                     eNewType = wkbMultiLineString;
    4591             :                 else
    4592           1 :                     eNewType = wkbGeometryCollection;
    4593             : 
    4594             :                 auto poMulti = std::unique_ptr<OGRGeometryCollection>(
    4595          80 :                     createGeometry(eNewType)->toGeometryCollection());
    4596             : 
    4597          40 :                 double dfDateLineOffset = CPLAtofM(
    4598             :                     CSLFetchNameValueDef(papszOptions, "DATELINEOFFSET", "10"));
    4599          40 :                 if (dfDateLineOffset <= 0.0 || dfDateLineOffset >= 360.0)
    4600           0 :                     dfDateLineOffset = 10.0;
    4601             : 
    4602          40 :                 CutGeometryOnDateLineAndAddToMulti(
    4603          40 :                     poMulti.get(), poDstGeom.get(), dfDateLineOffset);
    4604             : 
    4605          40 :                 if (poMulti->getNumGeometries() == 0)
    4606             :                 {
    4607             :                     // do nothing
    4608             :                 }
    4609          41 :                 else if (poMulti->getNumGeometries() == 1 &&
    4610           1 :                          (eType == wkbPolygon || eType == wkbLineString))
    4611             :                 {
    4612          13 :                     poDstGeom = poMulti->stealGeometry(0);
    4613             :                 }
    4614             :                 else
    4615             :                 {
    4616          27 :                     poDstGeom = std::move(poMulti);
    4617             :                 }
    4618             :             }
    4619             :         }
    4620             :     }
    4621             : 
    4622         645 :     return poDstGeom.release();
    4623             : }
    4624             : 
    4625             : /************************************************************************/
    4626             : /*                         OGRGeomTransformer()                         */
    4627             : /************************************************************************/
    4628             : 
    4629             : struct OGRGeomTransformer
    4630             : {
    4631             :     std::unique_ptr<OGRCoordinateTransformation> poCT{};
    4632             :     OGRGeometryFactory::TransformWithOptionsCache cache{};
    4633             :     CPLStringList aosOptions{};
    4634             : 
    4635          10 :     OGRGeomTransformer() = default;
    4636             :     OGRGeomTransformer(const OGRGeomTransformer &) = delete;
    4637             :     OGRGeomTransformer &operator=(const OGRGeomTransformer &) = delete;
    4638             : };
    4639             : 
    4640             : /************************************************************************/
    4641             : /*                     OGR_GeomTransformer_Create()                     */
    4642             : /************************************************************************/
    4643             : 
    4644             : /** Create a geometry transformer.
    4645             :  *
    4646             :  * This is an enhanced version of OGR_G_Transform().
    4647             :  *
    4648             :  * When reprojecting geometries from a Polar Stereographic projection or a
    4649             :  * projection naturally crossing the antimeridian (like UTM Zone 60) to a
    4650             :  * geographic CRS, it will cut geometries along the antimeridian. So a
    4651             :  * LineString might be returned as a MultiLineString.
    4652             :  *
    4653             :  * The WRAPDATELINE=YES option might be specified for circumstances to correct
    4654             :  * geometries that incorrectly go from a longitude on a side of the antimeridian
    4655             :  * to the other side, like a LINESTRING(-179 0,179 0) will be transformed to
    4656             :  * a MULTILINESTRING ((-179 0,-180 0),(180 0,179 0)). For that use case, hCT
    4657             :  * might be NULL.
    4658             :  *
    4659             :  * Supported options in papszOptions are:
    4660             :  * <ul>
    4661             :  * <li>WRAPDATELINE=YES</li>
    4662             :  * <li>DATELINEOFFSET=longitude_gap_in_degree. Defaults to 10.</li>
    4663             :  * </ul>
    4664             :  *
    4665             :  * This is the same as the C++ method OGRGeometryFactory::transformWithOptions().
    4666             : 
    4667             :  * @param hCT Coordinate transformation object (will be cloned) or NULL.
    4668             :  * @param papszOptions NULL terminated list of options, or NULL.
    4669             :  * @return transformer object to free with OGR_GeomTransformer_Destroy()
    4670             :  * @since GDAL 3.1
    4671             :  */
    4672          10 : OGRGeomTransformerH OGR_GeomTransformer_Create(OGRCoordinateTransformationH hCT,
    4673             :                                                CSLConstList papszOptions)
    4674             : {
    4675          10 :     OGRGeomTransformer *transformer = new OGRGeomTransformer;
    4676          10 :     if (hCT)
    4677             :     {
    4678           7 :         transformer->poCT.reset(
    4679           7 :             OGRCoordinateTransformation::FromHandle(hCT)->Clone());
    4680             :     }
    4681          10 :     transformer->aosOptions.Assign(CSLDuplicate(papszOptions));
    4682          10 :     return transformer;
    4683             : }
    4684             : 
    4685             : /************************************************************************/
    4686             : /*                   OGR_GeomTransformer_Transform()                    */
    4687             : /************************************************************************/
    4688             : 
    4689             : /** Transforms a geometry.
    4690             :  *
    4691             :  * @param hTransformer transformer object.
    4692             :  * @param hGeom Source geometry.
    4693             :  * @return a new geometry (or NULL) to destroy with OGR_G_DestroyGeometry()
    4694             :  * @since GDAL 3.1
    4695             :  */
    4696          10 : OGRGeometryH OGR_GeomTransformer_Transform(OGRGeomTransformerH hTransformer,
    4697             :                                            OGRGeometryH hGeom)
    4698             : {
    4699          10 :     VALIDATE_POINTER1(hTransformer, "OGR_GeomTransformer_Transform", nullptr);
    4700          10 :     VALIDATE_POINTER1(hGeom, "OGR_GeomTransformer_Transform", nullptr);
    4701             : 
    4702          20 :     return OGRGeometry::ToHandle(OGRGeometryFactory::transformWithOptions(
    4703          10 :         OGRGeometry::FromHandle(hGeom), hTransformer->poCT.get(),
    4704          20 :         hTransformer->aosOptions.List(), hTransformer->cache));
    4705             : }
    4706             : 
    4707             : /************************************************************************/
    4708             : /*                    OGR_GeomTransformer_Destroy()                     */
    4709             : /************************************************************************/
    4710             : 
    4711             : /** Destroy a geometry transformer allocated with OGR_GeomTransformer_Create()
    4712             :  *
    4713             :  * @param hTransformer transformer object.
    4714             :  * @since GDAL 3.1
    4715             :  */
    4716          10 : void OGR_GeomTransformer_Destroy(OGRGeomTransformerH hTransformer)
    4717             : {
    4718          10 :     delete hTransformer;
    4719          10 : }
    4720             : 
    4721             : /************************************************************************/
    4722             : /*             OGRGeometryFactory::GetDefaultArcStepSize()              */
    4723             : /************************************************************************/
    4724             : 
    4725             : /** Return the default value of the angular step used when stroking curves
    4726             :  * as lines. Defaults to 4 degrees.
    4727             :  * Can be modified by setting the OGR_ARC_STEPSIZE configuration option.
    4728             :  * Valid values are in [1e-2, 180] degree range.
    4729             :  * @since 3.11
    4730             :  */
    4731             : 
    4732             : /* static */
    4733        4443 : double OGRGeometryFactory::GetDefaultArcStepSize()
    4734             : {
    4735        4443 :     const double dfVal = CPLAtofM(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4"));
    4736        4443 :     constexpr double MIN_VAL = 1e-2;
    4737        4443 :     if (dfVal < MIN_VAL)
    4738             :     {
    4739           1 :         CPLErrorOnce(CE_Warning, CPLE_AppDefined,
    4740             :                      "Too small value for OGR_ARC_STEPSIZE. Clamping it to %f",
    4741             :                      MIN_VAL);
    4742           1 :         return MIN_VAL;
    4743             :     }
    4744        4442 :     constexpr double MAX_VAL = 180;
    4745        4442 :     if (dfVal > MAX_VAL)
    4746             :     {
    4747           1 :         CPLErrorOnce(CE_Warning, CPLE_AppDefined,
    4748             :                      "Too large value for OGR_ARC_STEPSIZE. Clamping it to %f",
    4749             :                      MAX_VAL);
    4750           1 :         return MAX_VAL;
    4751             :     }
    4752        4441 :     return dfVal;
    4753             : }
    4754             : 
    4755             : /************************************************************************/
    4756             : /*                              DISTANCE()                              */
    4757             : /************************************************************************/
    4758             : 
    4759      312403 : static inline double DISTANCE(double x1, double y1, double x2, double y2)
    4760             : {
    4761      312403 :     return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
    4762             : }
    4763             : 
    4764             : /************************************************************************/
    4765             : /*                        approximateArcAngles()                        */
    4766             : /************************************************************************/
    4767             : 
    4768             : /**
    4769             :  * Stroke arc to linestring.
    4770             :  *
    4771             :  * Stroke an arc of a circle to a linestring based on a center
    4772             :  * point, radius, start angle and end angle, all angles in degrees.
    4773             :  *
    4774             :  * If the dfMaxAngleStepSizeDegrees is zero, then a default value will be
    4775             :  * used.  This is currently 4 degrees unless the user has overridden the
    4776             :  * value with the OGR_ARC_STEPSIZE configuration variable.
    4777             :  *
    4778             :  * If the OGR_ARC_MAX_GAP configuration variable is set, the straight-line
    4779             :  * distance between adjacent pairs of interpolated points will be limited to
    4780             :  * the specified distance. If the distance between a pair of points exceeds
    4781             :  * this maximum, additional points are interpolated between the two points.
    4782             :  *
    4783             :  * @see CPLSetConfigOption()
    4784             :  *
    4785             :  * @param dfCenterX center X
    4786             :  * @param dfCenterY center Y
    4787             :  * @param dfZ center Z
    4788             :  * @param dfPrimaryRadius X radius of ellipse.
    4789             :  * @param dfSecondaryRadius Y radius of ellipse.
    4790             :  * @param dfRotation rotation of the ellipse clockwise.
    4791             :  * @param dfStartAngle angle to first point on arc (clockwise of X-positive)
    4792             :  * @param dfEndAngle angle to last point on arc (clockwise of X-positive)
    4793             :  * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
    4794             :  * arc, zero to use the default setting.
    4795             :  * @param bUseMaxGap Optional: whether to honor OGR_ARC_MAX_GAP.
    4796             :  *
    4797             :  * @return OGRLineString geometry representing an approximation of the arc.
    4798             :  *
    4799             :  */
    4800             : 
    4801         118 : OGRGeometry *OGRGeometryFactory::approximateArcAngles(
    4802             :     double dfCenterX, double dfCenterY, double dfZ, double dfPrimaryRadius,
    4803             :     double dfSecondaryRadius, double dfRotation, double dfStartAngle,
    4804             :     double dfEndAngle, double dfMaxAngleStepSizeDegrees,
    4805             :     const bool bUseMaxGap /* = false */)
    4806             : 
    4807             : {
    4808         118 :     OGRLineString *poLine = new OGRLineString();
    4809         118 :     const double dfRotationRadians = dfRotation * M_PI / 180.0;
    4810             : 
    4811             :     // Support default arc step setting.
    4812         118 :     if (dfMaxAngleStepSizeDegrees < 1e-6)
    4813             :     {
    4814         117 :         dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
    4815             :     }
    4816             : 
    4817             :     // Determine maximum interpolation gap. This is the largest straight-line
    4818             :     // distance allowed between pairs of interpolated points. Default zero,
    4819             :     // meaning no gap.
    4820             :     // coverity[tainted_data]
    4821             :     const double dfMaxInterpolationGap =
    4822         118 :         bUseMaxGap ? CPLAtofM(CPLGetConfigOption("OGR_ARC_MAX_GAP", "0")) : 0.0;
    4823             : 
    4824             :     // Is this a full circle?
    4825         118 :     const bool bIsFullCircle = fabs(dfEndAngle - dfStartAngle) == 360.0;
    4826             : 
    4827             :     // Switch direction.
    4828         118 :     dfStartAngle *= -1;
    4829         118 :     dfEndAngle *= -1;
    4830             : 
    4831             :     // Figure out the number of slices to make this into.
    4832             :     int nVertexCount =
    4833         236 :         std::max(2, static_cast<int>(ceil(fabs(dfEndAngle - dfStartAngle) /
    4834         118 :                                           dfMaxAngleStepSizeDegrees) +
    4835         118 :                                      1));
    4836         118 :     const double dfSlice = (dfEndAngle - dfStartAngle) / (nVertexCount - 1);
    4837             : 
    4838             :     // If it is a full circle we will work out the last point separately.
    4839         118 :     if (bIsFullCircle)
    4840             :     {
    4841          52 :         nVertexCount--;
    4842             :     }
    4843             : 
    4844             :     /* -------------------------------------------------------------------- */
    4845             :     /*      Compute the interpolated points.                                */
    4846             :     /* -------------------------------------------------------------------- */
    4847         118 :     double dfLastX = 0.0;
    4848         118 :     double dfLastY = 0.0;
    4849         118 :     int nTotalAddPoints = 0;
    4850        7071 :     for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
    4851             :     {
    4852        6953 :         const double dfAngleOnEllipse =
    4853        6953 :             (dfStartAngle + iPoint * dfSlice) * M_PI / 180.0;
    4854             : 
    4855             :         // Compute position on the unrotated ellipse.
    4856        6953 :         const double dfEllipseX = cos(dfAngleOnEllipse) * dfPrimaryRadius;
    4857        6953 :         const double dfEllipseY = sin(dfAngleOnEllipse) * dfSecondaryRadius;
    4858             : 
    4859             :         // Is this point too far from the previous point?
    4860        6953 :         if (iPoint && dfMaxInterpolationGap != 0.0)
    4861             :         {
    4862             :             const double dfDistFromLast =
    4863           1 :                 DISTANCE(dfLastX, dfLastY, dfEllipseX, dfEllipseY);
    4864             : 
    4865           1 :             if (dfDistFromLast > dfMaxInterpolationGap)
    4866             :             {
    4867           1 :                 const int nAddPoints =
    4868           1 :                     static_cast<int>(dfDistFromLast / dfMaxInterpolationGap);
    4869           1 :                 const double dfAddSlice = dfSlice / (nAddPoints + 1);
    4870             : 
    4871             :                 // Interpolate additional points
    4872           3 :                 for (int iAddPoint = 0; iAddPoint < nAddPoints; iAddPoint++)
    4873             :                 {
    4874           2 :                     const double dfAddAngleOnEllipse =
    4875           2 :                         (dfStartAngle + (iPoint - 1) * dfSlice +
    4876           2 :                          (iAddPoint + 1) * dfAddSlice) *
    4877             :                         (M_PI / 180.0);
    4878             : 
    4879           2 :                     poLine->setPoint(
    4880           2 :                         iPoint + nTotalAddPoints + iAddPoint,
    4881           2 :                         cos(dfAddAngleOnEllipse) * dfPrimaryRadius,
    4882           2 :                         sin(dfAddAngleOnEllipse) * dfSecondaryRadius, dfZ);
    4883             :                 }
    4884             : 
    4885           1 :                 nTotalAddPoints += nAddPoints;
    4886             :             }
    4887             :         }
    4888             : 
    4889        6953 :         poLine->setPoint(iPoint + nTotalAddPoints, dfEllipseX, dfEllipseY, dfZ);
    4890        6953 :         dfLastX = dfEllipseX;
    4891        6953 :         dfLastY = dfEllipseY;
    4892             :     }
    4893             : 
    4894             :     /* -------------------------------------------------------------------- */
    4895             :     /*      Rotate and translate the ellipse.                               */
    4896             :     /* -------------------------------------------------------------------- */
    4897         118 :     nVertexCount = poLine->getNumPoints();
    4898        7073 :     for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
    4899             :     {
    4900        6955 :         const double dfEllipseX = poLine->getX(iPoint);
    4901        6955 :         const double dfEllipseY = poLine->getY(iPoint);
    4902             : 
    4903             :         // Rotate this position around the center of the ellipse.
    4904        6955 :         const double dfArcX = dfCenterX + dfEllipseX * cos(dfRotationRadians) +
    4905        6955 :                               dfEllipseY * sin(dfRotationRadians);
    4906        6955 :         const double dfArcY = dfCenterY - dfEllipseX * sin(dfRotationRadians) +
    4907        6955 :                               dfEllipseY * cos(dfRotationRadians);
    4908             : 
    4909        6955 :         poLine->setPoint(iPoint, dfArcX, dfArcY, dfZ);
    4910             :     }
    4911             : 
    4912             :     /* -------------------------------------------------------------------- */
    4913             :     /*      If we're asked to make a full circle, ensure the start and      */
    4914             :     /*      end points coincide exactly, in spite of any rounding error.    */
    4915             :     /* -------------------------------------------------------------------- */
    4916         118 :     if (bIsFullCircle)
    4917             :     {
    4918         104 :         OGRPoint oPoint;
    4919          52 :         poLine->getPoint(0, &oPoint);
    4920          52 :         poLine->setPoint(nVertexCount, &oPoint);
    4921             :     }
    4922             : 
    4923         118 :     return poLine;
    4924             : }
    4925             : 
    4926             : /************************************************************************/
    4927             : /*                     OGR_G_ApproximateArcAngles()                     */
    4928             : /************************************************************************/
    4929             : 
    4930             : /**
    4931             :  * Stroke arc to linestring.
    4932             :  *
    4933             :  * Stroke an arc of a circle to a linestring based on a center
    4934             :  * point, radius, start angle and end angle, all angles in degrees.
    4935             :  *
    4936             :  * If the dfMaxAngleStepSizeDegrees is zero, then a default value will be
    4937             :  * used.  This is currently 4 degrees unless the user has overridden the
    4938             :  * value with the OGR_ARC_STEPSIZE configuration variable.
    4939             :  *
    4940             :  * @see CPLSetConfigOption()
    4941             :  *
    4942             :  * @param dfCenterX center X
    4943             :  * @param dfCenterY center Y
    4944             :  * @param dfZ center Z
    4945             :  * @param dfPrimaryRadius X radius of ellipse.
    4946             :  * @param dfSecondaryRadius Y radius of ellipse.
    4947             :  * @param dfRotation rotation of the ellipse clockwise.
    4948             :  * @param dfStartAngle angle to first point on arc (clockwise of X-positive)
    4949             :  * @param dfEndAngle angle to last point on arc (clockwise of X-positive)
    4950             :  * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
    4951             :  * arc, zero to use the default setting.
    4952             :  *
    4953             :  * @return OGRLineString geometry representing an approximation of the arc.
    4954             :  *
    4955             :  */
    4956             : 
    4957           1 : OGRGeometryH CPL_DLL OGR_G_ApproximateArcAngles(
    4958             :     double dfCenterX, double dfCenterY, double dfZ, double dfPrimaryRadius,
    4959             :     double dfSecondaryRadius, double dfRotation, double dfStartAngle,
    4960             :     double dfEndAngle, double dfMaxAngleStepSizeDegrees)
    4961             : 
    4962             : {
    4963           1 :     return OGRGeometry::ToHandle(OGRGeometryFactory::approximateArcAngles(
    4964             :         dfCenterX, dfCenterY, dfZ, dfPrimaryRadius, dfSecondaryRadius,
    4965           1 :         dfRotation, dfStartAngle, dfEndAngle, dfMaxAngleStepSizeDegrees));
    4966             : }
    4967             : 
    4968             : /************************************************************************/
    4969             : /*                         forceToLineString()                          */
    4970             : /************************************************************************/
    4971             : 
    4972             : /**
    4973             :  * \brief Convert to line string.
    4974             :  *
    4975             :  * Tries to force the provided geometry to be a line string.  This nominally
    4976             :  * effects a change on multilinestrings.
    4977             :  * For polygons or curvepolygons that have a single exterior ring,
    4978             :  * it will return the ring. For circular strings or compound curves, it will
    4979             :  * return an approximated line string.
    4980             :  *
    4981             :  * The passed in geometry is
    4982             :  * consumed and a new one returned (or potentially the same one).
    4983             :  *
    4984             :  * @param poGeom the input geometry - ownership is passed to the method.
    4985             :  * @param bOnlyInOrder flag that, if set to FALSE, indicate that the order of
    4986             :  *                     points in a linestring might be reversed if it enables
    4987             :  *                     to match the extremity of another linestring. If set
    4988             :  *                     to TRUE, the start of a linestring must match the end
    4989             :  *                     of another linestring.
    4990             :  * @return new geometry.
    4991             :  */
    4992             : 
    4993         200 : OGRGeometry *OGRGeometryFactory::forceToLineString(OGRGeometry *poGeom,
    4994             :                                                    bool bOnlyInOrder)
    4995             : 
    4996             : {
    4997         200 :     if (poGeom == nullptr)
    4998           2 :         return nullptr;
    4999             : 
    5000         198 :     const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
    5001             : 
    5002             :     /* -------------------------------------------------------------------- */
    5003             :     /*      If this is already a LineString, nothing to do                  */
    5004             :     /* -------------------------------------------------------------------- */
    5005         198 :     if (eGeomType == wkbLineString)
    5006             :     {
    5007             :         // Except if it is a linearring.
    5008          25 :         poGeom = OGRCurve::CastToLineString(poGeom->toCurve());
    5009             : 
    5010          25 :         return poGeom;
    5011             :     }
    5012             : 
    5013             :     /* -------------------------------------------------------------------- */
    5014             :     /*      If it is a polygon with a single ring, return it                 */
    5015             :     /* -------------------------------------------------------------------- */
    5016         173 :     if (eGeomType == wkbPolygon || eGeomType == wkbCurvePolygon)
    5017             :     {
    5018          30 :         OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
    5019          30 :         if (poCP->getNumInteriorRings() == 0)
    5020             :         {
    5021          28 :             OGRCurve *poRing = poCP->stealExteriorRingCurve();
    5022          28 :             delete poCP;
    5023          28 :             return forceToLineString(poRing);
    5024             :         }
    5025           2 :         return poGeom;
    5026             :     }
    5027             : 
    5028             :     /* -------------------------------------------------------------------- */
    5029             :     /*      If it is a curve line, call CurveToLine()                        */
    5030             :     /* -------------------------------------------------------------------- */
    5031         143 :     if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
    5032             :     {
    5033          92 :         OGRGeometry *poNewGeom = poGeom->toCurve()->CurveToLine();
    5034          92 :         delete poGeom;
    5035          92 :         return poNewGeom;
    5036             :     }
    5037             : 
    5038          51 :     if (eGeomType != wkbGeometryCollection && eGeomType != wkbMultiLineString &&
    5039             :         eGeomType != wkbMultiCurve)
    5040          20 :         return poGeom;
    5041             : 
    5042             :     // Build an aggregated linestring from all the linestrings in the container.
    5043          31 :     OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    5044          31 :     if (poGeom->hasCurveGeometry())
    5045             :     {
    5046             :         OGRGeometryCollection *poNewGC =
    5047           7 :             poGC->getLinearGeometry()->toGeometryCollection();
    5048           7 :         delete poGC;
    5049           7 :         poGC = poNewGC;
    5050             :     }
    5051             : 
    5052          31 :     if (poGC->getNumGeometries() == 0)
    5053             :     {
    5054           3 :         poGeom = new OGRLineString();
    5055           3 :         poGeom->assignSpatialReference(poGC->getSpatialReference());
    5056           3 :         delete poGC;
    5057           3 :         return poGeom;
    5058             :     }
    5059             : 
    5060          28 :     int iGeom0 = 0;
    5061          69 :     while (iGeom0 < poGC->getNumGeometries())
    5062             :     {
    5063          41 :         if (wkbFlatten(poGC->getGeometryRef(iGeom0)->getGeometryType()) !=
    5064             :             wkbLineString)
    5065             :         {
    5066          12 :             iGeom0++;
    5067          26 :             continue;
    5068             :         }
    5069             : 
    5070             :         OGRLineString *poLineString0 =
    5071          29 :             poGC->getGeometryRef(iGeom0)->toLineString();
    5072          29 :         if (poLineString0->getNumPoints() < 2)
    5073             :         {
    5074          14 :             iGeom0++;
    5075          14 :             continue;
    5076             :         }
    5077             : 
    5078          30 :         OGRPoint pointStart0;
    5079          15 :         poLineString0->StartPoint(&pointStart0);
    5080          30 :         OGRPoint pointEnd0;
    5081          15 :         poLineString0->EndPoint(&pointEnd0);
    5082             : 
    5083          15 :         int iGeom1 = iGeom0 + 1;  // Used after for.
    5084          17 :         for (; iGeom1 < poGC->getNumGeometries(); iGeom1++)
    5085             :         {
    5086           6 :             if (wkbFlatten(poGC->getGeometryRef(iGeom1)->getGeometryType()) !=
    5087             :                 wkbLineString)
    5088           1 :                 continue;
    5089             : 
    5090             :             OGRLineString *poLineString1 =
    5091           6 :                 poGC->getGeometryRef(iGeom1)->toLineString();
    5092           6 :             if (poLineString1->getNumPoints() < 2)
    5093           1 :                 continue;
    5094             : 
    5095           5 :             OGRPoint pointStart1;
    5096           5 :             poLineString1->StartPoint(&pointStart1);
    5097           5 :             OGRPoint pointEnd1;
    5098           5 :             poLineString1->EndPoint(&pointEnd1);
    5099             : 
    5100           5 :             if (!bOnlyInOrder && (pointEnd0.Equals(&pointEnd1) ||
    5101           0 :                                   pointStart0.Equals(&pointStart1)))
    5102             :             {
    5103           0 :                 poLineString1->reversePoints();
    5104           0 :                 poLineString1->StartPoint(&pointStart1);
    5105           0 :                 poLineString1->EndPoint(&pointEnd1);
    5106             :             }
    5107             : 
    5108           5 :             if (pointEnd0.Equals(&pointStart1))
    5109             :             {
    5110           4 :                 poLineString0->addSubLineString(poLineString1, 1);
    5111           4 :                 poGC->removeGeometry(iGeom1);
    5112           4 :                 break;
    5113             :             }
    5114             : 
    5115           1 :             if (pointEnd1.Equals(&pointStart0))
    5116             :             {
    5117           0 :                 poLineString1->addSubLineString(poLineString0, 1);
    5118           0 :                 poGC->removeGeometry(iGeom0);
    5119           0 :                 break;
    5120             :             }
    5121             :         }
    5122             : 
    5123          15 :         if (iGeom1 == poGC->getNumGeometries())
    5124             :         {
    5125          14 :             iGeom0++;
    5126             :         }
    5127             :     }
    5128             : 
    5129          28 :     if (poGC->getNumGeometries() == 1)
    5130             :     {
    5131          20 :         OGRGeometry *poSingleGeom = poGC->getGeometryRef(0);
    5132          20 :         poGC->removeGeometry(0, FALSE);
    5133          20 :         delete poGC;
    5134             : 
    5135          20 :         return poSingleGeom;
    5136             :     }
    5137             : 
    5138           8 :     return poGC;
    5139             : }
    5140             : 
    5141             : /************************************************************************/
    5142             : /*                      OGR_G_ForceToLineString()                       */
    5143             : /************************************************************************/
    5144             : 
    5145             : /**
    5146             :  * \brief Convert to line string.
    5147             :  *
    5148             :  * This function is the same as the C++ method
    5149             :  * OGRGeometryFactory::forceToLineString().
    5150             :  *
    5151             :  * @param hGeom handle to the geometry to convert (ownership surrendered).
    5152             :  * @return the converted geometry (ownership to caller).
    5153             :  *
    5154             :  * @since GDAL/OGR 1.10.0
    5155             :  */
    5156             : 
    5157          60 : OGRGeometryH OGR_G_ForceToLineString(OGRGeometryH hGeom)
    5158             : 
    5159             : {
    5160          60 :     return OGRGeometry::ToHandle(
    5161          60 :         OGRGeometryFactory::forceToLineString(OGRGeometry::FromHandle(hGeom)));
    5162             : }
    5163             : 
    5164             : /************************************************************************/
    5165             : /*                              forceTo()                               */
    5166             : /************************************************************************/
    5167             : 
    5168             : /**
    5169             :  * \brief Convert to another geometry type
    5170             :  *
    5171             :  * Tries to force the provided geometry to the specified geometry type.
    5172             :  *
    5173             :  * It can promote 'single' geometry type to their corresponding collection type
    5174             :  * (see OGR_GT_GetCollection()) or the reverse. non-linear geometry type to
    5175             :  * their corresponding linear geometry type (see OGR_GT_GetLinear()), by
    5176             :  * possibly approximating circular arcs they may contain.  Regarding conversion
    5177             :  * from linear geometry types to curve geometry types, only "wrapping" will be
    5178             :  * done. No attempt to retrieve potential circular arcs by de-approximating
    5179             :  * stroking will be done. For that, OGRGeometry::getCurveGeometry() can be used.
    5180             :  *
    5181             :  * The passed in geometry is consumed and a new one returned (or potentially the
    5182             :  * same one).
    5183             :  *
    5184             :  * Starting with GDAL 3.9, this method honours the dimensionality of eTargetType.
    5185             :  *
    5186             :  * @param poGeom the input geometry - ownership is passed to the method.
    5187             :  * @param eTargetType target output geometry type.
    5188             :  * @param papszOptions options as a null-terminated list of strings or NULL.
    5189             :  * @return new geometry, or nullptr in case of error.
    5190             :  *
    5191             :  */
    5192             : 
    5193           0 : OGRGeometry *OGRGeometryFactory::forceTo(OGRGeometry *poGeom,
    5194             :                                          OGRwkbGeometryType eTargetType,
    5195             :                                          const char *const *papszOptions)
    5196             : {
    5197           0 :     return forceTo(std::unique_ptr<OGRGeometry>(poGeom), eTargetType,
    5198             :                    papszOptions)
    5199           0 :         .release();
    5200             : }
    5201             : 
    5202             : /**
    5203             :  * \brief Convert to another geometry type
    5204             :  *
    5205             :  * Tries to force the provided geometry to the specified geometry type.
    5206             :  *
    5207             :  * It can promote 'single' geometry type to their corresponding collection type
    5208             :  * (see OGR_GT_GetCollection()) or the reverse. non-linear geometry type to
    5209             :  * their corresponding linear geometry type (see OGR_GT_GetLinear()), by
    5210             :  * possibly approximating circular arcs they may contain.  Regarding conversion
    5211             :  * from linear geometry types to curve geometry types, only "wrapping" will be
    5212             :  * done. No attempt to retrieve potential circular arcs by de-approximating
    5213             :  * stroking will be done. For that, OGRGeometry::getCurveGeometry() can be used.
    5214             :  *
    5215             :  * The passed in geometry is consumed and a new one returned (or potentially the
    5216             :  * same one).
    5217             :  *
    5218             :  * This method honours the dimensionality of eTargetType.
    5219             :  *
    5220             :  * @param poGeom the input geometry - ownership is passed to the method.
    5221             :  * @param eTargetType target output geometry type.
    5222             :  * @param papszOptions options as a null-terminated list of strings or NULL.
    5223             :  * @return new geometry, or nullptr in case of error.
    5224             :  *
    5225             :  * @since 3.13
    5226             :  */
    5227             : 
    5228             : std::unique_ptr<OGRGeometry>
    5229        5154 : OGRGeometryFactory::forceTo(std::unique_ptr<OGRGeometry> poGeom,
    5230             :                             OGRwkbGeometryType eTargetType,
    5231             :                             const char *const *papszOptions)
    5232             : {
    5233        5154 :     if (poGeom == nullptr)
    5234           0 :         return poGeom;
    5235             : 
    5236        5154 :     const OGRwkbGeometryType eTargetTypeFlat = wkbFlatten(eTargetType);
    5237        5154 :     if (eTargetTypeFlat == wkbUnknown)
    5238         274 :         return poGeom;
    5239             : 
    5240        4880 :     if (poGeom->IsEmpty())
    5241             :     {
    5242         558 :         auto poRet = std::unique_ptr<OGRGeometry>(createGeometry(eTargetType));
    5243         279 :         if (poRet)
    5244             :         {
    5245         279 :             poRet->assignSpatialReference(poGeom->getSpatialReference());
    5246         279 :             poRet->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5247         279 :             poRet->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5248             :         }
    5249         279 :         return poRet;
    5250             :     }
    5251             : 
    5252        4601 :     OGRwkbGeometryType eType = poGeom->getGeometryType();
    5253        4601 :     OGRwkbGeometryType eTypeFlat = wkbFlatten(eType);
    5254             : 
    5255        4601 :     if (eTargetTypeFlat != eTargetType && (eType == eTypeFlat))
    5256             :     {
    5257             :         auto poGeomNew =
    5258         132 :             forceTo(std::move(poGeom), eTargetTypeFlat, papszOptions);
    5259          66 :         if (poGeomNew)
    5260             :         {
    5261          66 :             poGeomNew->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5262          66 :             poGeomNew->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5263             :         }
    5264          66 :         return poGeomNew;
    5265             :     }
    5266             : 
    5267        4535 :     if (eTypeFlat == eTargetTypeFlat)
    5268             :     {
    5269         555 :         poGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5270         555 :         poGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5271         555 :         return poGeom;
    5272             :     }
    5273             : 
    5274        3980 :     eType = eTypeFlat;
    5275             : 
    5276        5689 :     if (OGR_GT_IsSubClassOf(eType, wkbPolyhedralSurface) &&
    5277        1709 :         (eTargetTypeFlat == wkbMultiSurface ||
    5278             :          eTargetTypeFlat == wkbGeometryCollection))
    5279             :     {
    5280         847 :         OGRwkbGeometryType eTempGeomType = wkbMultiPolygon;
    5281         847 :         if (OGR_GT_HasZ(eTargetType))
    5282         843 :             eTempGeomType = OGR_GT_SetZ(eTempGeomType);
    5283         847 :         if (OGR_GT_HasM(eTargetType))
    5284           0 :             eTempGeomType = OGR_GT_SetM(eTempGeomType);
    5285        1694 :         return forceTo(forceTo(std::move(poGeom), eTempGeomType, papszOptions),
    5286         847 :                        eTargetType, papszOptions);
    5287             :     }
    5288             : 
    5289        3133 :     if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
    5290             :         eTargetTypeFlat == wkbGeometryCollection)
    5291             :     {
    5292         921 :         OGRGeometryCollection *poGC = poGeom.release()->toGeometryCollection();
    5293             :         auto poRet = std::unique_ptr<OGRGeometry>(
    5294        1842 :             OGRGeometryCollection::CastToGeometryCollection(poGC));
    5295         921 :         poRet->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5296         921 :         poRet->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5297         921 :         return poRet;
    5298             :     }
    5299             : 
    5300        2212 :     if (eType == wkbTriangle && eTargetTypeFlat == wkbPolyhedralSurface)
    5301             :     {
    5302           2 :         auto poPS = std::make_unique<OGRPolyhedralSurface>();
    5303           1 :         poPS->assignSpatialReference(poGeom->getSpatialReference());
    5304           1 :         poPS->addGeometryDirectly(OGRTriangle::CastToPolygon(poGeom.release()));
    5305           1 :         poPS->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5306           1 :         poPS->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5307           1 :         return poPS;
    5308             :     }
    5309        2211 :     else if (eType == wkbPolygon && eTargetTypeFlat == wkbPolyhedralSurface)
    5310             :     {
    5311           6 :         auto poPS = std::make_unique<OGRPolyhedralSurface>();
    5312           3 :         poPS->assignSpatialReference(poGeom->getSpatialReference());
    5313           3 :         poPS->addGeometry(std::move(poGeom));
    5314           3 :         poPS->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5315           3 :         poPS->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5316           3 :         return poPS;
    5317             :     }
    5318        2208 :     else if (eType == wkbMultiPolygon &&
    5319             :              eTargetTypeFlat == wkbPolyhedralSurface)
    5320             :     {
    5321           2 :         const OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
    5322           4 :         auto poPS = std::make_unique<OGRPolyhedralSurface>();
    5323           4 :         for (const auto *poPoly : *poMP)
    5324             :         {
    5325           2 :             poPS->addGeometry(poPoly);
    5326             :         }
    5327           2 :         poPS->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5328           2 :         poPS->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5329           2 :         return poPS;
    5330             :     }
    5331        2206 :     else if (eType == wkbTIN && eTargetTypeFlat == wkbPolyhedralSurface)
    5332             :     {
    5333           1 :         poGeom.reset(OGRTriangulatedSurface::CastToPolyhedralSurface(
    5334             :             poGeom.release()->toTriangulatedSurface()));
    5335             :     }
    5336        2205 :     else if (eType == wkbCurvePolygon &&
    5337             :              eTargetTypeFlat == wkbPolyhedralSurface)
    5338             :     {
    5339           1 :         OGRwkbGeometryType eTempGeomType = wkbPolygon;
    5340           1 :         if (OGR_GT_HasZ(eTargetType))
    5341           0 :             eTempGeomType = OGR_GT_SetZ(eTempGeomType);
    5342           1 :         if (OGR_GT_HasM(eTargetType))
    5343           0 :             eTempGeomType = OGR_GT_SetM(eTempGeomType);
    5344           2 :         return forceTo(forceTo(std::move(poGeom), eTempGeomType, papszOptions),
    5345           1 :                        eTargetType, papszOptions);
    5346             :     }
    5347        2204 :     else if (eType == wkbMultiSurface &&
    5348             :              eTargetTypeFlat == wkbPolyhedralSurface)
    5349             :     {
    5350           1 :         OGRwkbGeometryType eTempGeomType = wkbMultiPolygon;
    5351           1 :         if (OGR_GT_HasZ(eTargetType))
    5352           0 :             eTempGeomType = OGR_GT_SetZ(eTempGeomType);
    5353           1 :         if (OGR_GT_HasM(eTargetType))
    5354           0 :             eTempGeomType = OGR_GT_SetM(eTempGeomType);
    5355           2 :         return forceTo(forceTo(std::move(poGeom), eTempGeomType, papszOptions),
    5356           1 :                        eTargetType, papszOptions);
    5357             :     }
    5358             : 
    5359        2203 :     else if (eType == wkbTriangle && eTargetTypeFlat == wkbTIN)
    5360             :     {
    5361           2 :         auto poTS = std::make_unique<OGRTriangulatedSurface>();
    5362           1 :         poTS->assignSpatialReference(poGeom->getSpatialReference());
    5363           1 :         poTS->addGeometry(std::move(poGeom));
    5364           1 :         poTS->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5365           1 :         poTS->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5366           1 :         return poTS;
    5367             :     }
    5368        2202 :     else if (eType == wkbPolygon && eTargetTypeFlat == wkbTIN)
    5369             :     {
    5370           4 :         const OGRPolygon *poPoly = poGeom->toPolygon();
    5371           4 :         const OGRLinearRing *poLR = poPoly->getExteriorRing();
    5372           7 :         if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
    5373           3 :               poPoly->getNumInteriorRings() == 0))
    5374             :         {
    5375           1 :             return poGeom;
    5376             :         }
    5377           3 :         OGRErr eErr = OGRERR_NONE;
    5378           6 :         auto poTriangle = std::make_unique<OGRTriangle>(*poPoly, eErr);
    5379           6 :         auto poTS = std::make_unique<OGRTriangulatedSurface>();
    5380           3 :         poTS->assignSpatialReference(poGeom->getSpatialReference());
    5381           3 :         poTS->addGeometry(std::move(poTriangle));
    5382           3 :         poTS->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5383           3 :         poTS->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5384           3 :         return poTS;
    5385             :     }
    5386        2198 :     else if (eType == wkbMultiPolygon && eTargetTypeFlat == wkbTIN)
    5387             :     {
    5388           1 :         const OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
    5389           2 :         for (const auto poPoly : *poMP)
    5390             :         {
    5391           1 :             const OGRLinearRing *poLR = poPoly->getExteriorRing();
    5392           2 :             if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
    5393           1 :                   poPoly->getNumInteriorRings() == 0))
    5394             :             {
    5395           0 :                 return poGeom;
    5396             :             }
    5397             :         }
    5398           2 :         auto poTS = std::make_unique<OGRTriangulatedSurface>();
    5399           1 :         poTS->assignSpatialReference(poGeom->getSpatialReference());
    5400           2 :         for (const auto poPoly : *poMP)
    5401             :         {
    5402           1 :             OGRErr eErr = OGRERR_NONE;
    5403           1 :             poTS->addGeometry(std::make_unique<OGRTriangle>(*poPoly, eErr));
    5404             :         }
    5405           1 :         poTS->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5406           1 :         poTS->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5407           1 :         return poTS;
    5408             :     }
    5409        2197 :     else if (eType == wkbPolyhedralSurface && eTargetTypeFlat == wkbTIN)
    5410             :     {
    5411           2 :         const OGRPolyhedralSurface *poPS = poGeom->toPolyhedralSurface();
    5412           3 :         for (const auto poPoly : *poPS)
    5413             :         {
    5414           2 :             const OGRLinearRing *poLR = poPoly->getExteriorRing();
    5415           3 :             if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
    5416           1 :                   poPoly->getNumInteriorRings() == 0))
    5417             :             {
    5418           1 :                 poGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5419           1 :                 poGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5420           1 :                 return poGeom;
    5421             :             }
    5422             :         }
    5423           2 :         auto poTS = std::make_unique<OGRTriangulatedSurface>();
    5424           1 :         poTS->assignSpatialReference(poGeom->getSpatialReference());
    5425           2 :         for (const auto poPoly : *poPS)
    5426             :         {
    5427           1 :             OGRErr eErr = OGRERR_NONE;
    5428           1 :             poTS->addGeometry(std::make_unique<OGRTriangle>(*poPoly, eErr));
    5429             :         }
    5430           1 :         poTS->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5431           1 :         poTS->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5432           1 :         return poTS;
    5433             :     }
    5434             : 
    5435        2195 :     else if (eType == wkbPolygon && eTargetTypeFlat == wkbTriangle)
    5436             :     {
    5437           7 :         const OGRPolygon *poPoly = poGeom->toPolygon();
    5438           7 :         const OGRLinearRing *poLR = poPoly->getExteriorRing();
    5439          13 :         if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
    5440           6 :               poPoly->getNumInteriorRings() == 0))
    5441             :         {
    5442           1 :             poGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5443           1 :             poGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5444           1 :             return poGeom;
    5445             :         }
    5446           6 :         OGRErr eErr = OGRERR_NONE;
    5447          12 :         auto poTriangle = std::make_unique<OGRTriangle>(*poPoly, eErr);
    5448           6 :         poTriangle->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5449           6 :         poTriangle->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5450           6 :         return poTriangle;
    5451             :     }
    5452             : 
    5453        2189 :     if (eTargetTypeFlat == wkbTriangle || eTargetTypeFlat == wkbTIN ||
    5454             :         eTargetTypeFlat == wkbPolyhedralSurface)
    5455             :     {
    5456           9 :         OGRwkbGeometryType eTempGeomType = wkbPolygon;
    5457           9 :         if (OGR_GT_HasZ(eTargetType))
    5458           0 :             eTempGeomType = OGR_GT_SetZ(eTempGeomType);
    5459           9 :         if (OGR_GT_HasM(eTargetType))
    5460           1 :             eTempGeomType = OGR_GT_SetM(eTempGeomType);
    5461           9 :         auto poGeomPtr = poGeom.get();
    5462          18 :         auto poPoly = forceTo(std::move(poGeom), eTempGeomType, papszOptions);
    5463           9 :         if (poPoly.get() == poGeomPtr)
    5464           0 :             return poPoly;
    5465           9 :         return forceTo(std::move(poPoly), eTargetType, papszOptions);
    5466             :     }
    5467             : 
    5468        2180 :     if (eType == wkbTriangle && eTargetTypeFlat == wkbGeometryCollection)
    5469             :     {
    5470           2 :         auto poGC = std::make_unique<OGRGeometryCollection>();
    5471           1 :         poGC->assignSpatialReference(poGeom->getSpatialReference());
    5472           1 :         poGC->addGeometry(std::move(poGeom));
    5473           1 :         poGC->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5474           1 :         poGC->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5475           1 :         return poGC;
    5476             :     }
    5477             : 
    5478             :     // Promote single to multi.
    5479        4059 :     if (!OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
    5480        1880 :         OGR_GT_IsSubClassOf(OGR_GT_GetCollection(eType), eTargetType))
    5481             :     {
    5482        1108 :         auto poRet = std::unique_ptr<OGRGeometry>(createGeometry(eTargetType));
    5483         554 :         if (poRet == nullptr)
    5484             :         {
    5485           0 :             return nullptr;
    5486             :         }
    5487         554 :         poRet->assignSpatialReference(poGeom->getSpatialReference());
    5488         554 :         if (eType == wkbLineString)
    5489          65 :             poGeom.reset(
    5490          65 :                 OGRCurve::CastToLineString(poGeom.release()->toCurve()));
    5491         554 :         poRet->toGeometryCollection()->addGeometry(std::move(poGeom));
    5492         554 :         poRet->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5493         554 :         poRet->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5494         554 :         return poRet;
    5495             :     }
    5496             : 
    5497        1625 :     const bool bIsCurve = CPL_TO_BOOL(OGR_GT_IsCurve(eType));
    5498        1625 :     if (bIsCurve && eTargetTypeFlat == wkbCompoundCurve)
    5499             :     {
    5500             :         auto poRet = std::unique_ptr<OGRGeometry>(
    5501          64 :             OGRCurve::CastToCompoundCurve(poGeom.release()->toCurve()));
    5502          32 :         if (poRet)
    5503             :         {
    5504          30 :             poRet->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5505          30 :             poRet->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5506             :         }
    5507          32 :         return poRet;
    5508             :     }
    5509        1593 :     else if (bIsCurve && eTargetTypeFlat == wkbCurvePolygon)
    5510             :     {
    5511          26 :         const OGRCurve *poCurve = poGeom->toCurve();
    5512          26 :         if (poCurve->getNumPoints() >= 3 && poCurve->get_IsClosed())
    5513             :         {
    5514          18 :             auto poCP = std::make_unique<OGRCurvePolygon>();
    5515          18 :             if (poCP->addRing(std::move(poCurve)) == OGRERR_NONE)
    5516             :             {
    5517          18 :                 poCP->assignSpatialReference(poGeom->getSpatialReference());
    5518          18 :                 poCP->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5519          18 :                 poCP->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5520          18 :                 return poCP;
    5521             :             }
    5522           8 :         }
    5523             :     }
    5524        1644 :     else if (eType == wkbLineString &&
    5525          77 :              OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface))
    5526             :     {
    5527          23 :         auto poTmp = forceTo(std::move(poGeom), wkbPolygon, papszOptions);
    5528          23 :         if (wkbFlatten(poTmp->getGeometryType()) != eType)
    5529          15 :             return forceTo(std::move(poTmp), eTargetType, papszOptions);
    5530           8 :         poGeom = std::move(poTmp);
    5531             :     }
    5532        1544 :     else if (bIsCurve && eTargetTypeFlat == wkbMultiSurface)
    5533             :     {
    5534          10 :         auto poTmp = forceTo(std::move(poGeom), wkbCurvePolygon, papszOptions);
    5535          10 :         if (wkbFlatten(poTmp->getGeometryType()) != eType)
    5536           8 :             return forceTo(std::move(poTmp), eTargetType, papszOptions);
    5537           4 :         poGeom = std::move(poTmp);
    5538             :     }
    5539        1534 :     else if (bIsCurve && eTargetTypeFlat == wkbMultiPolygon)
    5540             :     {
    5541          13 :         auto poTmp = forceTo(std::move(poGeom), wkbPolygon, papszOptions);
    5542          13 :         if (wkbFlatten(poTmp->getGeometryType()) != eType)
    5543          11 :             return forceTo(std::move(poTmp), eTargetType, papszOptions);
    5544           4 :         poGeom = std::move(poTmp);
    5545             :     }
    5546        1521 :     else if (eType == wkbTriangle && eTargetTypeFlat == wkbCurvePolygon)
    5547             :     {
    5548             :         auto poRet =
    5549           1 :             std::unique_ptr<OGRGeometry>(OGRSurface::CastToCurvePolygon(
    5550           2 :                 OGRTriangle::CastToPolygon(poGeom.release())->toSurface()));
    5551           1 :         poRet->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5552           1 :         poRet->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5553           1 :         return poRet;
    5554             :     }
    5555        1520 :     else if (eType == wkbPolygon && eTargetTypeFlat == wkbCurvePolygon)
    5556             :     {
    5557             :         auto poRet = std::unique_ptr<OGRGeometry>(
    5558          48 :             OGRSurface::CastToCurvePolygon(poGeom.release()->toPolygon()));
    5559          24 :         poRet->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5560          24 :         poRet->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5561          24 :         return poRet;
    5562             :     }
    5563        1496 :     else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
    5564             :              eTargetTypeFlat == wkbCompoundCurve)
    5565             :     {
    5566          15 :         OGRCurvePolygon *poPoly = poGeom->toCurvePolygon();
    5567          15 :         if (poPoly->getNumInteriorRings() == 0)
    5568             :         {
    5569             :             auto poRet =
    5570          14 :                 std::unique_ptr<OGRGeometry>(poPoly->stealExteriorRingCurve());
    5571          14 :             if (poRet)
    5572          14 :                 poRet->assignSpatialReference(poGeom->getSpatialReference());
    5573          14 :             return forceTo(std::move(poRet), eTargetType, papszOptions);
    5574             :         }
    5575             :     }
    5576        1481 :     else if (eType == wkbMultiPolygon && eTargetTypeFlat == wkbMultiSurface)
    5577             :     {
    5578             :         auto poRet =
    5579          14 :             std::unique_ptr<OGRGeometry>(OGRMultiPolygon::CastToMultiSurface(
    5580          28 :                 poGeom.release()->toMultiPolygon()));
    5581          14 :         poRet->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5582          14 :         poRet->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5583          14 :         return poRet;
    5584             :     }
    5585        1467 :     else if (eType == wkbMultiLineString && eTargetTypeFlat == wkbMultiCurve)
    5586             :     {
    5587             :         auto poRet =
    5588           9 :             std::unique_ptr<OGRGeometry>(OGRMultiLineString::CastToMultiCurve(
    5589          18 :                 poGeom.release()->toMultiLineString()));
    5590           9 :         poRet->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5591           9 :         poRet->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5592           9 :         return poRet;
    5593             :     }
    5594        1458 :     else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
    5595             :     {
    5596         276 :         const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    5597         276 :         if (poGC->getNumGeometries() == 1)
    5598             :         {
    5599         170 :             const OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
    5600         170 :             if (poSubGeom)
    5601             :             {
    5602             :                 auto poSubGeomClone =
    5603         170 :                     std::unique_ptr<OGRGeometry>(poSubGeom->clone());
    5604         340 :                 poSubGeomClone->assignSpatialReference(
    5605         170 :                     poGeom->getSpatialReference());
    5606         170 :                 auto poRet = forceTo(std::move(poSubGeomClone), eTargetType,
    5607         170 :                                      papszOptions);
    5608         340 :                 if (poRet &&
    5609         170 :                     OGR_GT_IsSubClassOf(wkbFlatten(poRet->getGeometryType()),
    5610         170 :                                         eTargetType))
    5611             :                 {
    5612         135 :                     return poRet;
    5613             :                 }
    5614             :             }
    5615             :         }
    5616             :     }
    5617        1301 :     else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
    5618         119 :              (OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface) ||
    5619         107 :               OGR_GT_IsSubClassOf(eTargetType, wkbMultiCurve)))
    5620             :     {
    5621          43 :         const OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
    5622          43 :         if (poCP->getNumInteriorRings() == 0)
    5623             :         {
    5624          41 :             const OGRCurve *poRing = poCP->getExteriorRingCurve();
    5625          41 :             auto poRingClone = std::unique_ptr<OGRGeometry>(poRing->clone());
    5626          41 :             poRingClone->assignSpatialReference(poGeom->getSpatialReference());
    5627          41 :             const OGRwkbGeometryType eRingType = poRing->getGeometryType();
    5628             :             auto poRet =
    5629          41 :                 forceTo(std::move(poRingClone), eTargetType, papszOptions);
    5630          57 :             if (poRet->getGeometryType() != eRingType &&
    5631          16 :                 !(eTypeFlat == wkbPolygon &&
    5632             :                   eTargetTypeFlat == wkbMultiLineString))
    5633             :             {
    5634          29 :                 return poRet;
    5635             :             }
    5636             :         }
    5637             :     }
    5638             : 
    5639        1315 :     if (eTargetTypeFlat == wkbLineString)
    5640             :     {
    5641             :         auto poNewGeom =
    5642         224 :             std::unique_ptr<OGRGeometry>(forceToLineString(poGeom.release()));
    5643         112 :         poNewGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5644         112 :         poNewGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5645         112 :         poGeom = std::move(poNewGeom);
    5646             :     }
    5647        1203 :     else if (eTargetTypeFlat == wkbPolygon)
    5648             :     {
    5649             :         auto poNewGeom =
    5650         208 :             std::unique_ptr<OGRGeometry>(forceToPolygon(poGeom.release()));
    5651         104 :         if (poNewGeom)
    5652             :         {
    5653         104 :             poNewGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5654         104 :             poNewGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5655             :         }
    5656         104 :         poGeom = std::move(poNewGeom);
    5657             :     }
    5658        1099 :     else if (eTargetTypeFlat == wkbMultiPolygon)
    5659             :     {
    5660             :         auto poNewGeom =
    5661        1830 :             std::unique_ptr<OGRGeometry>(forceToMultiPolygon(poGeom.release()));
    5662         915 :         if (poNewGeom)
    5663             :         {
    5664         915 :             poNewGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5665         915 :             poNewGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5666             :         }
    5667         915 :         poGeom = std::move(poNewGeom);
    5668             :     }
    5669         184 :     else if (eTargetTypeFlat == wkbMultiLineString)
    5670             :     {
    5671             :         auto poNewGeom = std::unique_ptr<OGRGeometry>(
    5672          82 :             forceToMultiLineString(poGeom.release()));
    5673          41 :         poNewGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5674          41 :         poNewGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5675          41 :         poGeom = std::move(poNewGeom);
    5676             :     }
    5677         143 :     else if (eTargetTypeFlat == wkbMultiPoint)
    5678             :     {
    5679             :         auto poNewGeom =
    5680          44 :             std::unique_ptr<OGRGeometry>(forceToMultiPoint(poGeom.release()));
    5681          22 :         poNewGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
    5682          22 :         poNewGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
    5683          22 :         poGeom = std::move(poNewGeom);
    5684             :     }
    5685             : 
    5686        1315 :     return poGeom;
    5687             : }
    5688             : 
    5689             : /************************************************************************/
    5690             : /*                           OGR_G_ForceTo()                            */
    5691             : /************************************************************************/
    5692             : 
    5693             : /**
    5694             :  * \brief Convert to another geometry type
    5695             :  *
    5696             :  * This function is the same as the C++ method OGRGeometryFactory::forceTo().
    5697             :  *
    5698             :  * @param hGeom the input geometry - ownership is passed to the method.
    5699             :  * @param eTargetType target output geometry type.
    5700             :  * @param papszOptions options as a null-terminated list of strings or NULL.
    5701             :  * @return new geometry.
    5702             :  *
    5703             :  */
    5704             : 
    5705         853 : OGRGeometryH OGR_G_ForceTo(OGRGeometryH hGeom, OGRwkbGeometryType eTargetType,
    5706             :                            CSLConstList papszOptions)
    5707             : 
    5708             : {
    5709         853 :     return OGRGeometry::ToHandle(
    5710        1706 :         OGRGeometryFactory::forceTo(
    5711        1706 :             std::unique_ptr<OGRGeometry>(OGRGeometry::FromHandle(hGeom)),
    5712             :             eTargetType, papszOptions)
    5713        1706 :             .release());
    5714             : }
    5715             : 
    5716             : /************************************************************************/
    5717             : /*                         makeCompatibleWith()                         */
    5718             : /************************************************************************/
    5719             : 
    5720             : /**
    5721             :  * \brief Adjust a geometry to be compatible with a specified geometry type.
    5722             :  *
    5723             :  * This is a soft version of forceTo() that:
    5724             :  * - converts single geometry type to a multi-geometry type if eTargetType is
    5725             :  *   a multi-geometry type (e.g. wkbMultiPolygon) and the single geometry type
    5726             :  *   is compatible with it (e.g. wkbPolygon)
    5727             :  * - insert components of multi-geometries that are not wkbGeometryCollection
    5728             :  *   into a GeometryCollection, when eTargetType == wkbGeometryCollection
    5729             :  * - insert single geometries into a GeometryCollection, when
    5730             :  *   eTargetType == wkbGeometryCollection.
    5731             :  * - convert a single-part multi-geometry to the specified target single
    5732             :  *   geometry type. e.g a MultiPolygon to a Polygon
    5733             :  * - in other cases, the geometry is returned unmodified.
    5734             :  *
    5735             :  * @param poGeom the input geometry - ownership is passed to the method.
    5736             :  * @param eTargetType target output geometry type.
    5737             :  *                    Typically a layer geometry type.
    5738             :  * @return a geometry (potentially poGeom itself)
    5739             :  *
    5740             :  * @since GDAL 3.12
    5741             :  */
    5742             : 
    5743             : std::unique_ptr<OGRGeometry>
    5744          39 : OGRGeometryFactory::makeCompatibleWith(std::unique_ptr<OGRGeometry> poGeom,
    5745             :                                        OGRwkbGeometryType eTargetType)
    5746             : {
    5747          39 :     const auto eGeomType = poGeom->getGeometryType();
    5748          39 :     const auto eFlattenTargetType = wkbFlatten(eTargetType);
    5749          78 :     if (eFlattenTargetType != wkbUnknown &&
    5750          39 :         eFlattenTargetType != wkbFlatten(eGeomType))
    5751             :     {
    5752          12 :         if (OGR_GT_GetCollection(eGeomType) == eFlattenTargetType)
    5753             :         {
    5754             :             poGeom =
    5755           4 :                 OGRGeometryFactory::forceTo(std::move(poGeom), eTargetType);
    5756             :         }
    5757           8 :         else if (eGeomType == OGR_GT_GetCollection(eTargetType) &&
    5758           0 :                  poGeom->toGeometryCollection()->getNumGeometries() == 1)
    5759             :         {
    5760           0 :             poGeom = poGeom->toGeometryCollection()->stealGeometry(0);
    5761             :         }
    5762           8 :         else if (eFlattenTargetType == wkbGeometryCollection)
    5763             :         {
    5764           4 :             auto poGeomColl = std::make_unique<OGRGeometryCollection>();
    5765           2 :             if (OGR_GT_IsSubClassOf(eGeomType, wkbGeometryCollection))
    5766             :             {
    5767           3 :                 for (const auto *poSubGeom : *(poGeom->toGeometryCollection()))
    5768             :                 {
    5769           2 :                     poGeomColl->addGeometry(poSubGeom);
    5770             :                 }
    5771             :             }
    5772             :             else
    5773             :             {
    5774           1 :                 poGeomColl->addGeometry(std::move(poGeom));
    5775             :             }
    5776           2 :             poGeom = std::move(poGeomColl);
    5777             :         }
    5778             :     }
    5779          39 :     return poGeom;
    5780             : }
    5781             : 
    5782             : /************************************************************************/
    5783             : /*                         GetCurveParameters()                         */
    5784             : /************************************************************************/
    5785             : 
    5786             : /**
    5787             :  * \brief Returns the parameter of an arc circle.
    5788             :  *
    5789             :  * Angles are return in radians, with trigonometic convention (counter clock
    5790             :  * wise)
    5791             :  *
    5792             :  * @param x0 x of first point
    5793             :  * @param y0 y of first point
    5794             :  * @param x1 x of intermediate point
    5795             :  * @param y1 y of intermediate point
    5796             :  * @param x2 x of final point
    5797             :  * @param y2 y of final point
    5798             :  * @param R radius (output)
    5799             :  * @param cx x of arc center (output)
    5800             :  * @param cy y of arc center (output)
    5801             :  * @param alpha0 angle between center and first point, in radians (output)
    5802             :  * @param alpha1 angle between center and intermediate point, in radians
    5803             :  * (output)
    5804             :  * @param alpha2 angle between center and final point, in radians (output)
    5805             :  * @return TRUE if the points are not aligned and define an arc circle.
    5806             :  *
    5807             :  */
    5808             : 
    5809      188299 : int OGRGeometryFactory::GetCurveParameters(double x0, double y0, double x1,
    5810             :                                            double y1, double x2, double y2,
    5811             :                                            double &R, double &cx, double &cy,
    5812             :                                            double &alpha0, double &alpha1,
    5813             :                                            double &alpha2)
    5814             : {
    5815      564897 :     if (std::isnan(x0) || std::isnan(y0) || std::isnan(x1) || std::isnan(y1) ||
    5816      564897 :         std::isnan(x2) || std::isnan(y2))
    5817             :     {
    5818           0 :         return FALSE;
    5819             :     }
    5820             : 
    5821             :     // Circle.
    5822      188299 :     if (x0 == x2 && y0 == y2)
    5823             :     {
    5824         149 :         if (x0 != x1 || y0 != y1)
    5825             :         {
    5826         148 :             cx = (x0 + x1) / 2;
    5827         148 :             cy = (y0 + y1) / 2;
    5828         148 :             R = DISTANCE(cx, cy, x0, y0);
    5829             :             // Arbitrarily pick counter-clock-wise order (like PostGIS does).
    5830         148 :             alpha0 = atan2(y0 - cy, x0 - cx);
    5831         148 :             alpha1 = alpha0 + M_PI;
    5832         148 :             alpha2 = alpha0 + 2 * M_PI;
    5833         148 :             return TRUE;
    5834             :         }
    5835             :         else
    5836             :         {
    5837           1 :             return FALSE;
    5838             :         }
    5839             :     }
    5840             : 
    5841      188150 :     double dx01 = x1 - x0;
    5842      188150 :     double dy01 = y1 - y0;
    5843      188150 :     double dx12 = x2 - x1;
    5844      188150 :     double dy12 = y2 - y1;
    5845             : 
    5846             :     // Normalize above values so as to make sure we don't end up with
    5847             :     // computing a difference of too big values.
    5848      188150 :     double dfScale = fabs(dx01);
    5849      188150 :     if (fabs(dy01) > dfScale)
    5850       93638 :         dfScale = fabs(dy01);
    5851      188150 :     if (fabs(dx12) > dfScale)
    5852       46827 :         dfScale = fabs(dx12);
    5853      188150 :     if (fabs(dy12) > dfScale)
    5854       46973 :         dfScale = fabs(dy12);
    5855      188150 :     const double dfInvScale = 1.0 / dfScale;
    5856      188150 :     dx01 *= dfInvScale;
    5857      188150 :     dy01 *= dfInvScale;
    5858      188150 :     dx12 *= dfInvScale;
    5859      188150 :     dy12 *= dfInvScale;
    5860             : 
    5861      188150 :     const double det = dx01 * dy12 - dx12 * dy01;
    5862      188150 :     if (fabs(det) < 1.0e-8 || std::isnan(det))
    5863             :     {
    5864         131 :         return FALSE;
    5865             :     }
    5866      188019 :     const double x01_mid = (x0 + x1) * dfInvScale;
    5867      188019 :     const double x12_mid = (x1 + x2) * dfInvScale;
    5868      188019 :     const double y01_mid = (y0 + y1) * dfInvScale;
    5869      188019 :     const double y12_mid = (y1 + y2) * dfInvScale;
    5870      188019 :     const double c01 = dx01 * x01_mid + dy01 * y01_mid;
    5871      188019 :     const double c12 = dx12 * x12_mid + dy12 * y12_mid;
    5872      188019 :     cx = 0.5 * dfScale * (c01 * dy12 - c12 * dy01) / det;
    5873      188019 :     cy = 0.5 * dfScale * (-c01 * dx12 + c12 * dx01) / det;
    5874             : 
    5875      188019 :     alpha0 = atan2((y0 - cy) * dfInvScale, (x0 - cx) * dfInvScale);
    5876      188019 :     alpha1 = atan2((y1 - cy) * dfInvScale, (x1 - cx) * dfInvScale);
    5877      188019 :     alpha2 = atan2((y2 - cy) * dfInvScale, (x2 - cx) * dfInvScale);
    5878      188019 :     R = DISTANCE(cx, cy, x0, y0);
    5879             : 
    5880             :     // If det is negative, the orientation if clockwise.
    5881      188019 :     if (det < 0)
    5882             :     {
    5883       92391 :         if (alpha1 > alpha0)
    5884        1253 :             alpha1 -= 2 * M_PI;
    5885       92391 :         if (alpha2 > alpha1)
    5886        3296 :             alpha2 -= 2 * M_PI;
    5887             :     }
    5888             :     else
    5889             :     {
    5890       95628 :         if (alpha1 < alpha0)
    5891        1304 :             alpha1 += 2 * M_PI;
    5892       95628 :         if (alpha2 < alpha1)
    5893        3269 :             alpha2 += 2 * M_PI;
    5894             :     }
    5895             : 
    5896      188019 :     CPLAssert((alpha0 <= alpha1 && alpha1 <= alpha2) ||
    5897             :               (alpha0 >= alpha1 && alpha1 >= alpha2));
    5898             : 
    5899      188019 :     return TRUE;
    5900             : }
    5901             : 
    5902             : /************************************************************************/
    5903             : /*                    OGRGeometryFactoryStrokeArc()                     */
    5904             : /************************************************************************/
    5905             : 
    5906        4396 : static void OGRGeometryFactoryStrokeArc(OGRLineString *poLine, double cx,
    5907             :                                         double cy, double R, double z0,
    5908             :                                         double z1, int bHasZ, double alpha0,
    5909             :                                         double alpha1, double dfStep,
    5910             :                                         int bStealthConstraints)
    5911             : {
    5912        4396 :     const int nSign = dfStep > 0 ? 1 : -1;
    5913             : 
    5914             :     // Constant angle between all points, so as to not depend on winding order.
    5915        4396 :     const double dfNumSteps = fabs((alpha1 - alpha0) / dfStep) + 0.5;
    5916        4396 :     if (dfNumSteps >= std::numeric_limits<int>::max() ||
    5917        4396 :         dfNumSteps <= std::numeric_limits<int>::min() || std::isnan(dfNumSteps))
    5918             :     {
    5919           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    5920             :                  "OGRGeometryFactoryStrokeArc: bogus steps: "
    5921             :                  "%lf %lf %lf %lf",
    5922             :                  alpha0, alpha1, dfStep, dfNumSteps);
    5923           0 :         return;
    5924             :     }
    5925             : 
    5926        4396 :     int nSteps = static_cast<int>(dfNumSteps);
    5927        4396 :     if (bStealthConstraints)
    5928             :     {
    5929             :         // We need at least 6 intermediate vertex, and if more additional
    5930             :         // multiples of 2.
    5931        4198 :         if (nSteps < 1 + 6)
    5932         112 :             nSteps = 1 + 6;
    5933             :         else
    5934        4086 :             nSteps = 1 + 6 + 2 * ((nSteps - (1 + 6) + (2 - 1)) / 2);
    5935             :     }
    5936         198 :     else if (nSteps < 4)
    5937             :     {
    5938         194 :         nSteps = 4;
    5939             :     }
    5940        4396 :     dfStep = nSign * fabs((alpha1 - alpha0) / nSteps);
    5941        4396 :     double alpha = alpha0 + dfStep;
    5942             : 
    5943      234892 :     for (; (alpha - alpha1) * nSign < -1e-8; alpha += dfStep)
    5944             :     {
    5945      230496 :         const double dfX = cx + R * cos(alpha);
    5946      230496 :         const double dfY = cy + R * sin(alpha);
    5947      230496 :         if (bHasZ)
    5948             :         {
    5949        9896 :             const double z =
    5950        9896 :                 z0 + (z1 - z0) * (alpha - alpha0) / (alpha1 - alpha0);
    5951        9896 :             poLine->addPoint(dfX, dfY, z);
    5952             :         }
    5953             :         else
    5954             :         {
    5955      220600 :             poLine->addPoint(dfX, dfY);
    5956             :         }
    5957             :     }
    5958             : }
    5959             : 
    5960             : /************************************************************************/
    5961             : /*                        OGRGF_SetHiddenValue()                        */
    5962             : /************************************************************************/
    5963             : 
    5964             : // TODO(schwehr): Cleanup these static constants.
    5965             : constexpr int HIDDEN_ALPHA_WIDTH = 32;
    5966             : constexpr GUInt32 HIDDEN_ALPHA_SCALE =
    5967             :     static_cast<GUInt32>((static_cast<GUIntBig>(1) << HIDDEN_ALPHA_WIDTH) - 2);
    5968             : constexpr int HIDDEN_ALPHA_HALF_WIDTH = (HIDDEN_ALPHA_WIDTH / 2);
    5969             : constexpr int HIDDEN_ALPHA_HALF_MASK = (1 << HIDDEN_ALPHA_HALF_WIDTH) - 1;
    5970             : 
    5971             : // Encode 16-bit nValue in the 8-lsb of dfX and dfY.
    5972             : 
    5973             : #ifdef CPL_LSB
    5974             : constexpr int DOUBLE_LSB_OFFSET = 0;
    5975             : #else
    5976             : constexpr int DOUBLE_LSB_OFFSET = 7;
    5977             : #endif
    5978             : 
    5979      230362 : static void OGRGF_SetHiddenValue(GUInt16 nValue, double &dfX, double &dfY)
    5980             : {
    5981      230362 :     GByte abyData[8] = {};
    5982             : 
    5983      230362 :     memcpy(abyData, &dfX, sizeof(double));
    5984      230362 :     abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue & 0xFF);
    5985      230362 :     memcpy(&dfX, abyData, sizeof(double));
    5986             : 
    5987      230362 :     memcpy(abyData, &dfY, sizeof(double));
    5988      230362 :     abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue >> 8);
    5989      230362 :     memcpy(&dfY, abyData, sizeof(double));
    5990      230362 : }
    5991             : 
    5992             : /************************************************************************/
    5993             : /*                        OGRGF_GetHiddenValue()                        */
    5994             : /************************************************************************/
    5995             : 
    5996             : // Decode 16-bit nValue from the 8-lsb of dfX and dfY.
    5997      182904 : static GUInt16 OGRGF_GetHiddenValue(double dfX, double dfY)
    5998             : {
    5999      182904 :     GByte abyData[8] = {};
    6000      182904 :     memcpy(abyData, &dfX, sizeof(double));
    6001      182904 :     GUInt16 nValue = abyData[DOUBLE_LSB_OFFSET];
    6002      182904 :     memcpy(abyData, &dfY, sizeof(double));
    6003      182904 :     nValue |= (abyData[DOUBLE_LSB_OFFSET] << 8);
    6004             : 
    6005      182904 :     return nValue;
    6006             : }
    6007             : 
    6008             : /************************************************************************/
    6009             : /*                      OGRGF_NeedSwithArcOrder()                       */
    6010             : /************************************************************************/
    6011             : 
    6012             : // We need to define a full ordering between starting point and ending point
    6013             : // whatever it is.
    6014        9602 : static bool OGRGF_NeedSwithArcOrder(double x0, double y0, double x2, double y2)
    6015             : {
    6016        9602 :     return x0 < x2 || (x0 == x2 && y0 < y2);
    6017             : }
    6018             : 
    6019             : /************************************************************************/
    6020             : /*                         curveToLineString()                          */
    6021             : /************************************************************************/
    6022             : 
    6023             : /* clang-format off */
    6024             : /**
    6025             :  * \brief Converts an arc circle into an approximate line string
    6026             :  *
    6027             :  * The arc circle is defined by a first point, an intermediate point and a
    6028             :  * final point.
    6029             :  *
    6030             :  * The provided dfMaxAngleStepSizeDegrees is a hint. The discretization
    6031             :  * algorithm may pick a slightly different value.
    6032             :  *
    6033             :  * So as to avoid gaps when rendering curve polygons that share common arcs,
    6034             :  * this method is guaranteed to return a line with reversed vertex if called
    6035             :  * with inverted first and final point, and identical intermediate point.
    6036             :  *
    6037             :  * @param x0 x of first point
    6038             :  * @param y0 y of first point
    6039             :  * @param z0 z of first point
    6040             :  * @param x1 x of intermediate point
    6041             :  * @param y1 y of intermediate point
    6042             :  * @param z1 z of intermediate point
    6043             :  * @param x2 x of final point
    6044             :  * @param y2 y of final point
    6045             :  * @param z2 z of final point
    6046             :  * @param bHasZ TRUE if z must be taken into account
    6047             :  * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
    6048             :  * arc, zero to use the default setting.
    6049             :  * @param papszOptions options as a null-terminated list of strings or NULL.
    6050             :  * Recognized options:
    6051             :  * <ul>
    6052             :  * <li>ADD_INTERMEDIATE_POINT=STEALTH/YES/NO (Default to STEALTH).
    6053             :  *         Determine if and how the intermediate point must be output in the
    6054             :  *         linestring.  If set to STEALTH, no explicit intermediate point is
    6055             :  *         added but its properties are encoded in low significant bits of
    6056             :  *         intermediate points and OGRGeometryFactory::curveFromLineString() can
    6057             :  *         decode them.  This is the best compromise for round-tripping in OGR
    6058             :  *         and better results with PostGIS
    6059             :  *         <a href="http://postgis.org/docs/ST_LineToCurve.html">ST_LineToCurve()</a>.
    6060             :  *         If set to YES, the intermediate point is explicitly added to the
    6061             :  *         linestring. If set to NO, the intermediate point is not explicitly
    6062             :  *         added.
    6063             :  * </li>
    6064             :  * </ul>
    6065             :  *
    6066             :  * @return the converted geometry (ownership to caller).
    6067             :  *
    6068             :  */
    6069             : /* clang-format on */
    6070             : 
    6071        6532 : OGRLineString *OGRGeometryFactory::curveToLineString(
    6072             :     double x0, double y0, double z0, double x1, double y1, double z1, double x2,
    6073             :     double y2, double z2, int bHasZ, double dfMaxAngleStepSizeDegrees,
    6074             :     const char *const *papszOptions)
    6075             : {
    6076             :     // So as to make sure the same curve followed in both direction results
    6077             :     // in perfectly(=binary identical) symmetrical points.
    6078        6532 :     if (OGRGF_NeedSwithArcOrder(x0, y0, x2, y2))
    6079             :     {
    6080             :         OGRLineString *poLS =
    6081        2234 :             curveToLineString(x2, y2, z2, x1, y1, z1, x0, y0, z0, bHasZ,
    6082             :                               dfMaxAngleStepSizeDegrees, papszOptions);
    6083        2234 :         poLS->reversePoints();
    6084        2234 :         return poLS;
    6085             :     }
    6086             : 
    6087        4298 :     double R = 0.0;
    6088        4298 :     double cx = 0.0;
    6089        4298 :     double cy = 0.0;
    6090        4298 :     double alpha0 = 0.0;
    6091        4298 :     double alpha1 = 0.0;
    6092        4298 :     double alpha2 = 0.0;
    6093             : 
    6094        4298 :     OGRLineString *poLine = new OGRLineString();
    6095        4298 :     bool bIsArc = true;
    6096        4298 :     if (!GetCurveParameters(x0, y0, x1, y1, x2, y2, R, cx, cy, alpha0, alpha1,
    6097             :                             alpha2))
    6098             :     {
    6099          97 :         bIsArc = false;
    6100          97 :         cx = 0.0;
    6101          97 :         cy = 0.0;
    6102          97 :         R = 0.0;
    6103          97 :         alpha0 = 0.0;
    6104          97 :         alpha1 = 0.0;
    6105          97 :         alpha2 = 0.0;
    6106             :     }
    6107             : 
    6108        4298 :     const int nSign = alpha1 >= alpha0 ? 1 : -1;
    6109             : 
    6110             :     // support default arc step setting.
    6111        4298 :     if (dfMaxAngleStepSizeDegrees < 1e-6)
    6112             :     {
    6113        4279 :         dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
    6114             :     }
    6115             : 
    6116        4298 :     double dfStep = dfMaxAngleStepSizeDegrees / 180 * M_PI;
    6117        4298 :     if (dfStep <= 0.01 / 180 * M_PI)
    6118             :     {
    6119           0 :         CPLDebug("OGR", "Too small arc step size: limiting to 0.01 degree.");
    6120           0 :         dfStep = 0.01 / 180 * M_PI;
    6121             :     }
    6122             : 
    6123        4298 :     dfStep *= nSign;
    6124             : 
    6125        4298 :     if (bHasZ)
    6126         272 :         poLine->addPoint(x0, y0, z0);
    6127             :     else
    6128        4026 :         poLine->addPoint(x0, y0);
    6129             : 
    6130        4298 :     bool bAddIntermediatePoint = false;
    6131        4298 :     bool bStealth = true;
    6132        4304 :     for (const char *const *papszIter = papszOptions; papszIter && *papszIter;
    6133             :          papszIter++)
    6134             :     {
    6135           6 :         char *pszKey = nullptr;
    6136           6 :         const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
    6137           6 :         if (pszKey != nullptr && EQUAL(pszKey, "ADD_INTERMEDIATE_POINT"))
    6138             :         {
    6139           4 :             if (EQUAL(pszValue, "YES") || EQUAL(pszValue, "TRUE") ||
    6140           3 :                 EQUAL(pszValue, "ON"))
    6141             :             {
    6142           1 :                 bAddIntermediatePoint = true;
    6143           1 :                 bStealth = false;
    6144             :             }
    6145           3 :             else if (EQUAL(pszValue, "NO") || EQUAL(pszValue, "FALSE") ||
    6146           1 :                      EQUAL(pszValue, "OFF"))
    6147             :             {
    6148           2 :                 bAddIntermediatePoint = false;
    6149           2 :                 bStealth = false;
    6150             :             }
    6151             :             else if (EQUAL(pszValue, "STEALTH"))
    6152             :             {
    6153             :                 // default.
    6154             :             }
    6155             :         }
    6156             :         else
    6157             :         {
    6158           2 :             CPLError(CE_Warning, CPLE_NotSupported, "Unsupported option: %s",
    6159             :                      *papszIter);
    6160             :         }
    6161           6 :         CPLFree(pszKey);
    6162             :     }
    6163             : 
    6164        4298 :     if (!bIsArc || bAddIntermediatePoint)
    6165             :     {
    6166          98 :         OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z1, bHasZ, alpha0,
    6167             :                                     alpha1, dfStep, FALSE);
    6168             : 
    6169          98 :         if (bHasZ)
    6170          25 :             poLine->addPoint(x1, y1, z1);
    6171             :         else
    6172          73 :             poLine->addPoint(x1, y1);
    6173             : 
    6174          98 :         OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z1, z2, bHasZ, alpha1,
    6175             :                                     alpha2, dfStep, FALSE);
    6176             :     }
    6177             :     else
    6178             :     {
    6179        4200 :         OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z2, bHasZ, alpha0,
    6180             :                                     alpha2, dfStep, bStealth);
    6181             : 
    6182        4200 :         if (bStealth && poLine->getNumPoints() > 6)
    6183             :         {
    6184             :             // 'Hide' the angle of the intermediate point in the 8
    6185             :             // low-significant bits of the x, y of the first 2 computed points
    6186             :             // (so 32 bits), then put 0xFF, and on the last couple points put
    6187             :             // again the angle but in reverse order, so that overall the
    6188             :             // low-significant bits of all the points are symmetrical w.r.t the
    6189             :             // mid-point.
    6190        4198 :             const double dfRatio = (alpha1 - alpha0) / (alpha2 - alpha0);
    6191        4198 :             double dfAlphaRatio = 0.5 + HIDDEN_ALPHA_SCALE * dfRatio;
    6192        4198 :             if (dfAlphaRatio < 0.0)
    6193             :             {
    6194           0 :                 CPLError(CE_Warning, CPLE_AppDefined, "AlphaRation < 0: %lf",
    6195             :                          dfAlphaRatio);
    6196           0 :                 dfAlphaRatio *= -1;
    6197             :             }
    6198        8396 :             else if (dfAlphaRatio >= std::numeric_limits<GUInt32>::max() ||
    6199        4198 :                      std::isnan(dfAlphaRatio))
    6200             :             {
    6201           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    6202             :                          "AlphaRatio too large: %lf", dfAlphaRatio);
    6203           0 :                 dfAlphaRatio = std::numeric_limits<GUInt32>::max();
    6204             :             }
    6205        4198 :             const GUInt32 nAlphaRatio = static_cast<GUInt32>(dfAlphaRatio);
    6206        4198 :             const GUInt16 nAlphaRatioLow = nAlphaRatio & HIDDEN_ALPHA_HALF_MASK;
    6207        4198 :             const GUInt16 nAlphaRatioHigh =
    6208        4198 :                 nAlphaRatio >> HIDDEN_ALPHA_HALF_WIDTH;
    6209             :             // printf("alpha0=%f, alpha1=%f, alpha2=%f, dfRatio=%f, "/*ok*/
    6210             :             //        "nAlphaRatio = %u\n",
    6211             :             //        alpha0, alpha1, alpha2, dfRatio, nAlphaRatio);
    6212             : 
    6213        4198 :             CPLAssert(((poLine->getNumPoints() - 1 - 6) % 2) == 0);
    6214             : 
    6215      119379 :             for (int i = 1; i + 1 < poLine->getNumPoints(); i += 2)
    6216             :             {
    6217      115181 :                 GUInt16 nVal = 0xFFFF;
    6218             : 
    6219      115181 :                 double dfX = poLine->getX(i);
    6220      115181 :                 double dfY = poLine->getY(i);
    6221      115181 :                 if (i == 1)
    6222        4198 :                     nVal = nAlphaRatioLow;
    6223      110983 :                 else if (i == poLine->getNumPoints() - 2)
    6224        4198 :                     nVal = nAlphaRatioHigh;
    6225      115181 :                 OGRGF_SetHiddenValue(nVal, dfX, dfY);
    6226      115181 :                 poLine->setPoint(i, dfX, dfY);
    6227             : 
    6228      115181 :                 dfX = poLine->getX(i + 1);
    6229      115181 :                 dfY = poLine->getY(i + 1);
    6230      115181 :                 if (i == 1)
    6231        4198 :                     nVal = nAlphaRatioHigh;
    6232      110983 :                 else if (i == poLine->getNumPoints() - 2)
    6233        4198 :                     nVal = nAlphaRatioLow;
    6234      115181 :                 OGRGF_SetHiddenValue(nVal, dfX, dfY);
    6235      115181 :                 poLine->setPoint(i + 1, dfX, dfY);
    6236             :             }
    6237             :         }
    6238             :     }
    6239             : 
    6240        4298 :     if (bHasZ)
    6241         272 :         poLine->addPoint(x2, y2, z2);
    6242             :     else
    6243        4026 :         poLine->addPoint(x2, y2);
    6244             : 
    6245        4298 :     return poLine;
    6246             : }
    6247             : 
    6248             : /************************************************************************/
    6249             : /*                           OGRGF_FixAngle()                           */
    6250             : /************************************************************************/
    6251             : 
    6252             : // Fix dfAngle by offsets of 2 PI so that it lies between dfAngleStart and
    6253             : // dfAngleStop, whatever their respective order.
    6254      181910 : static double OGRGF_FixAngle(double dfAngleStart, double dfAngleStop,
    6255             :                              double dfAngle)
    6256             : {
    6257      181910 :     if (dfAngleStart < dfAngleStop)
    6258             :     {
    6259      127411 :         while (dfAngle <= dfAngleStart + 1e-8)
    6260       34834 :             dfAngle += 2 * M_PI;
    6261             :     }
    6262             :     else
    6263             :     {
    6264      123295 :         while (dfAngle >= dfAngleStart - 1e-8)
    6265       33962 :             dfAngle -= 2 * M_PI;
    6266             :     }
    6267      181910 :     return dfAngle;
    6268             : }
    6269             : 
    6270             : /************************************************************************/
    6271             : /*                          OGRGF_DetectArc()                           */
    6272             : /************************************************************************/
    6273             : 
    6274             : // #define VERBOSE_DEBUG_CURVEFROMLINESTRING
    6275             : 
    6276       12223 : static inline bool IS_ALMOST_INTEGER(double x)
    6277             : {
    6278       12223 :     const double val = fabs(x - floor(x + 0.5));
    6279       12223 :     return val < 1.0e-8;
    6280             : }
    6281             : 
    6282        3473 : static int OGRGF_DetectArc(const OGRLineString *poLS, int i,
    6283             :                            OGRCompoundCurve *&poCC, OGRCircularString *&poCS,
    6284             :                            OGRLineString *&poLSNew)
    6285             : {
    6286        3473 :     if (i + 3 >= poLS->getNumPoints())
    6287         304 :         return -1;
    6288             : 
    6289        6338 :     OGRPoint p0;
    6290        6338 :     OGRPoint p1;
    6291        6338 :     OGRPoint p2;
    6292        3169 :     poLS->getPoint(i, &p0);
    6293        3169 :     poLS->getPoint(i + 1, &p1);
    6294        3169 :     poLS->getPoint(i + 2, &p2);
    6295        3169 :     double R_1 = 0.0;
    6296        3169 :     double cx_1 = 0.0;
    6297        3169 :     double cy_1 = 0.0;
    6298        3169 :     double alpha0_1 = 0.0;
    6299        3169 :     double alpha1_1 = 0.0;
    6300        3169 :     double alpha2_1 = 0.0;
    6301        6331 :     if (!(OGRGeometryFactory::GetCurveParameters(
    6302             :               p0.getX(), p0.getY(), p1.getX(), p1.getY(), p2.getX(), p2.getY(),
    6303             :               R_1, cx_1, cy_1, alpha0_1, alpha1_1, alpha2_1) &&
    6304        3162 :           fabs(alpha2_1 - alpha0_1) < 2.0 * 20.0 / 180.0 * M_PI))
    6305             :     {
    6306          23 :         return -1;
    6307             :     }
    6308             : 
    6309        3146 :     const double dfDeltaAlpha10 = alpha1_1 - alpha0_1;
    6310        3146 :     const double dfDeltaAlpha21 = alpha2_1 - alpha1_1;
    6311             :     const double dfMaxDeltaAlpha =
    6312        3146 :         std::max(fabs(dfDeltaAlpha10), fabs(dfDeltaAlpha21));
    6313             :     GUInt32 nAlphaRatioRef =
    6314        3146 :         OGRGF_GetHiddenValue(p1.getX(), p1.getY()) |
    6315        3146 :         (OGRGF_GetHiddenValue(p2.getX(), p2.getY()) << HIDDEN_ALPHA_HALF_WIDTH);
    6316        3146 :     bool bFoundFFFFFFFFPattern = false;
    6317        3146 :     bool bFoundReversedAlphaRatioRef = false;
    6318        3146 :     bool bValidAlphaRatio = nAlphaRatioRef > 0 && nAlphaRatioRef < 0xFFFFFFFF;
    6319        3146 :     int nCountValidAlphaRatio = 1;
    6320             : 
    6321        3146 :     double dfScale = std::max(1.0, R_1);
    6322        3146 :     dfScale = std::max(dfScale, fabs(cx_1));
    6323        3146 :     dfScale = std::max(dfScale, fabs(cy_1));
    6324        3146 :     dfScale = pow(10.0, ceil(log10(dfScale)));
    6325        3146 :     const double dfInvScale = 1.0 / dfScale;
    6326             : 
    6327        3146 :     const int bInitialConstantStep =
    6328        3146 :         (fabs(dfDeltaAlpha10 - dfDeltaAlpha21) / dfMaxDeltaAlpha) < 1.0e-4;
    6329        3146 :     const double dfDeltaEpsilon =
    6330        3146 :         bInitialConstantStep ? dfMaxDeltaAlpha * 1e-4 : dfMaxDeltaAlpha / 10;
    6331             : 
    6332             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6333             :     printf("----------------------------\n");             /*ok*/
    6334             :     printf("Curve beginning at offset i = %d\n", i);      /*ok*/
    6335             :     printf("Initial alpha ratio = %u\n", nAlphaRatioRef); /*ok*/
    6336             :     /*ok*/ printf("Initial R = %.16g, cx = %.16g, cy = %.16g\n", R_1, cx_1,
    6337             :                   cy_1);
    6338             :     printf("dfScale = %f\n", dfScale);   /*ok*/
    6339             :     printf("bInitialConstantStep = %d, " /*ok*/
    6340             :            "fabs(dfDeltaAlpha10 - dfDeltaAlpha21)=%.8g, "
    6341             :            "dfMaxDeltaAlpha = %.8f, "
    6342             :            "dfDeltaEpsilon = %.8f (%.8f)\n",
    6343             :            bInitialConstantStep, fabs(dfDeltaAlpha10 - dfDeltaAlpha21),
    6344             :            dfMaxDeltaAlpha, dfDeltaEpsilon, 1.0 / 180.0 * M_PI);
    6345             : #endif
    6346        3146 :     int iMidPoint = -1;
    6347        3146 :     double dfLastValidAlpha = alpha2_1;
    6348             : 
    6349        3146 :     double dfLastLogRelDiff = 0;
    6350             : 
    6351        6292 :     OGRPoint p3;
    6352        3146 :     int j = i + 1;  // Used after for.
    6353      183447 :     for (; j + 2 < poLS->getNumPoints(); j++)
    6354             :     {
    6355      180402 :         poLS->getPoint(j, &p1);
    6356      180402 :         poLS->getPoint(j + 1, &p2);
    6357      180402 :         poLS->getPoint(j + 2, &p3);
    6358      180402 :         double R_2 = 0.0;
    6359      180402 :         double cx_2 = 0.0;
    6360      180402 :         double cy_2 = 0.0;
    6361      180402 :         double alpha0_2 = 0.0;
    6362      180402 :         double alpha1_2 = 0.0;
    6363      180402 :         double alpha2_2 = 0.0;
    6364             :         // Check that the new candidate arc shares the same
    6365             :         // radius, center and winding order.
    6366      180402 :         if (!(OGRGeometryFactory::GetCurveParameters(
    6367             :                 p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(),
    6368             :                 p3.getY(), R_2, cx_2, cy_2, alpha0_2, alpha1_2, alpha2_2)))
    6369             :         {
    6370             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6371             :             printf("End of curve at j=%d\n : straight line", j); /*ok*/
    6372             : #endif
    6373         101 :             break;
    6374             :         }
    6375             : 
    6376      180394 :         const double dfRelDiffR = fabs(R_1 - R_2) * dfInvScale;
    6377      180394 :         const double dfRelDiffCx = fabs(cx_1 - cx_2) * dfInvScale;
    6378      180394 :         const double dfRelDiffCy = fabs(cy_1 - cy_2) * dfInvScale;
    6379             : 
    6380             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6381             :         printf("j=%d: R = %.16g, cx = %.16g, cy = %.16g, " /*ok*/
    6382             :                "rel_diff_R=%.8g rel_diff_cx=%.8g rel_diff_cy=%.8g\n",
    6383             :                j, R_2, cx_2, cy_2, dfRelDiffR, dfRelDiffCx, dfRelDiffCy);
    6384             : #endif
    6385             : 
    6386      180394 :         if (dfRelDiffR > 1.0e-7 || dfRelDiffCx > 1.0e-7 ||
    6387      180323 :             dfRelDiffCy > 1.0e-7 ||
    6388      180323 :             dfDeltaAlpha10 * (alpha1_2 - alpha0_2) < 0.0)
    6389             :         {
    6390             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6391             :             printf("End of curve at j=%d\n", j); /*ok*/
    6392             : #endif
    6393             :             break;
    6394             :         }
    6395             : 
    6396      180323 :         if (dfRelDiffR > 0.0 && dfRelDiffCx > 0.0 && dfRelDiffCy > 0.0)
    6397             :         {
    6398             :             const double dfLogRelDiff = std::min(
    6399      360616 :                 std::min(fabs(log10(dfRelDiffR)), fabs(log10(dfRelDiffCx))),
    6400      180308 :                 fabs(log10(dfRelDiffCy)));
    6401             :             // printf("dfLogRelDiff = %f, dfLastLogRelDiff=%f, "/*ok*/
    6402             :             //        "dfLogRelDiff - dfLastLogRelDiff=%f\n",
    6403             :             //         dfLogRelDiff, dfLastLogRelDiff,
    6404             :             //         dfLogRelDiff - dfLastLogRelDiff);
    6405      180308 :             if (dfLogRelDiff > 0.0 && dfLastLogRelDiff >= 8.0 &&
    6406           2 :                 dfLogRelDiff <= 8.0 && dfLogRelDiff < dfLastLogRelDiff - 2.0)
    6407             :             {
    6408             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6409             :                 printf("End of curve at j=%d. Significant different in " /*ok*/
    6410             :                        "relative error w.r.t previous points\n",
    6411             :                        j);
    6412             : #endif
    6413           2 :                 break;
    6414             :             }
    6415      180306 :             dfLastLogRelDiff = dfLogRelDiff;
    6416             :         }
    6417             : 
    6418      180321 :         const double dfStep10 = fabs(alpha1_2 - alpha0_2);
    6419      180321 :         const double dfStep21 = fabs(alpha2_2 - alpha1_2);
    6420             :         // Check that the angle step is consistent with the original step.
    6421      180321 :         if (!(dfStep10 < 2.0 * dfMaxDeltaAlpha &&
    6422      180321 :               dfStep21 < 2.0 * dfMaxDeltaAlpha))
    6423             :         {
    6424             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6425             :             printf("End of curve at j=%d: dfStep10=%f, dfStep21=%f, " /*ok*/
    6426             :                    "2*dfMaxDeltaAlpha=%f\n",
    6427             :                    j, dfStep10, dfStep21, 2 * dfMaxDeltaAlpha);
    6428             : #endif
    6429             :             break;
    6430             :         }
    6431             : 
    6432      180320 :         if (bValidAlphaRatio && j > i + 1 && (i % 2) != (j % 2))
    6433             :         {
    6434             :             const GUInt32 nAlphaRatioReversed =
    6435       88306 :                 (OGRGF_GetHiddenValue(p1.getX(), p1.getY())
    6436      176612 :                  << HIDDEN_ALPHA_HALF_WIDTH) |
    6437       88306 :                 (OGRGF_GetHiddenValue(p2.getX(), p2.getY()));
    6438             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6439             :             printf("j=%d, nAlphaRatioReversed = %u\n", /*ok*/
    6440             :                    j, nAlphaRatioReversed);
    6441             : #endif
    6442       88306 :             if (!bFoundFFFFFFFFPattern && nAlphaRatioReversed == 0xFFFFFFFF)
    6443             :             {
    6444        3070 :                 bFoundFFFFFFFFPattern = true;
    6445        3070 :                 nCountValidAlphaRatio++;
    6446             :             }
    6447       85236 :             else if (bFoundFFFFFFFFPattern && !bFoundReversedAlphaRatioRef &&
    6448             :                      nAlphaRatioReversed == 0xFFFFFFFF)
    6449             :             {
    6450       82137 :                 nCountValidAlphaRatio++;
    6451             :             }
    6452        3099 :             else if (bFoundFFFFFFFFPattern && !bFoundReversedAlphaRatioRef &&
    6453             :                      nAlphaRatioReversed == nAlphaRatioRef)
    6454             :             {
    6455        3070 :                 bFoundReversedAlphaRatioRef = true;
    6456        3070 :                 nCountValidAlphaRatio++;
    6457             :             }
    6458             :             else
    6459             :             {
    6460          29 :                 if (bInitialConstantStep &&
    6461          28 :                     fabs(dfLastValidAlpha - alpha0_1) >= M_PI &&
    6462             :                     nCountValidAlphaRatio > 10)
    6463             :                 {
    6464             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6465             :                     printf("End of curve at j=%d: " /*ok*/
    6466             :                            "fabs(dfLastValidAlpha - alpha0_1)=%f, "
    6467             :                            "nCountValidAlphaRatio=%d\n",
    6468             :                            j, fabs(dfLastValidAlpha - alpha0_1),
    6469             :                            nCountValidAlphaRatio);
    6470             : #endif
    6471          19 :                     if (dfLastValidAlpha - alpha0_1 > 0)
    6472             :                     {
    6473          21 :                         while (dfLastValidAlpha - alpha0_1 - dfMaxDeltaAlpha -
    6474          14 :                                    M_PI >
    6475          14 :                                -dfMaxDeltaAlpha / 10)
    6476             :                         {
    6477           7 :                             dfLastValidAlpha -= dfMaxDeltaAlpha;
    6478           7 :                             j--;
    6479             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6480             :                             printf(/*ok*/
    6481             :                                    "--> corrected as fabs(dfLastValidAlpha - "
    6482             :                                    "alpha0_1)=%f, j=%d\n",
    6483             :                                    fabs(dfLastValidAlpha - alpha0_1), j);
    6484             : #endif
    6485             :                         }
    6486             :                     }
    6487             :                     else
    6488             :                     {
    6489          36 :                         while (dfLastValidAlpha - alpha0_1 + dfMaxDeltaAlpha +
    6490          24 :                                    M_PI <
    6491          24 :                                dfMaxDeltaAlpha / 10)
    6492             :                         {
    6493          12 :                             dfLastValidAlpha += dfMaxDeltaAlpha;
    6494          12 :                             j--;
    6495             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6496             :                             printf(/*ok*/
    6497             :                                    "--> corrected as fabs(dfLastValidAlpha - "
    6498             :                                    "alpha0_1)=%f, j=%d\n",
    6499             :                                    fabs(dfLastValidAlpha - alpha0_1), j);
    6500             : #endif
    6501             :                         }
    6502             :                     }
    6503          19 :                     poLS->getPoint(j + 1, &p2);
    6504          19 :                     break;
    6505             :                 }
    6506             : 
    6507             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6508             :                 printf("j=%d, nAlphaRatioReversed = %u --> inconsistent " /*ok*/
    6509             :                        "values across arc. Don't use it\n",
    6510             :                        j, nAlphaRatioReversed);
    6511             : #endif
    6512          10 :                 bValidAlphaRatio = false;
    6513             :             }
    6514             :         }
    6515             : 
    6516             :         // Correct current end angle, consistently with start angle.
    6517      180301 :         dfLastValidAlpha = OGRGF_FixAngle(alpha0_1, alpha1_1, alpha2_2);
    6518             : 
    6519             :         // Try to detect the precise intermediate point of the
    6520             :         // arc circle by detecting irregular angle step
    6521             :         // This is OK if we don't detect the right point or fail
    6522             :         // to detect it.
    6523             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6524             :         printf("j=%d A(0,1)-maxDelta=%.8f A(1,2)-maxDelta=%.8f " /*ok*/
    6525             :                "x1=%.8f y1=%.8f x2=%.8f y2=%.8f x3=%.8f y3=%.8f\n",
    6526             :                j, fabs(dfStep10 - dfMaxDeltaAlpha),
    6527             :                fabs(dfStep21 - dfMaxDeltaAlpha), p1.getX(), p1.getY(),
    6528             :                p2.getX(), p2.getY(), p3.getX(), p3.getY());
    6529             : #endif
    6530      180301 :         if (j > i + 1 && iMidPoint < 0 && dfDeltaEpsilon < 1.0 / 180.0 * M_PI)
    6531             :         {
    6532      176825 :             if (fabs(dfStep10 - dfMaxDeltaAlpha) > dfDeltaEpsilon)
    6533           8 :                 iMidPoint = j + ((bInitialConstantStep) ? 0 : 1);
    6534      176817 :             else if (fabs(dfStep21 - dfMaxDeltaAlpha) > dfDeltaEpsilon)
    6535           4 :                 iMidPoint = j + ((bInitialConstantStep) ? 1 : 2);
    6536             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6537             :             if (iMidPoint >= 0)
    6538             :             {
    6539             :                 OGRPoint pMid;
    6540             :                 poLS->getPoint(iMidPoint, &pMid);
    6541             :                 printf("Midpoint detected at j = %d, iMidPoint = %d, " /*ok*/
    6542             :                        "x=%.8f y=%.8f\n",
    6543             :                        j, iMidPoint, pMid.getX(), pMid.getY());
    6544             :             }
    6545             : #endif
    6546             :         }
    6547             :     }
    6548             : 
    6549             :     // Take a minimum threshold of consecutive points
    6550             :     // on the arc to avoid false positives.
    6551        3146 :     if (j < i + 3)
    6552          63 :         return -1;
    6553             : 
    6554        3083 :     bValidAlphaRatio &= bFoundFFFFFFFFPattern && bFoundReversedAlphaRatioRef;
    6555             : 
    6556             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6557             :     printf("bValidAlphaRatio=%d bFoundFFFFFFFFPattern=%d, " /*ok*/
    6558             :            "bFoundReversedAlphaRatioRef=%d\n",
    6559             :            static_cast<int>(bValidAlphaRatio),
    6560             :            static_cast<int>(bFoundFFFFFFFFPattern),
    6561             :            static_cast<int>(bFoundReversedAlphaRatioRef));
    6562             :     printf("alpha0_1=%f dfLastValidAlpha=%f\n", /*ok*/
    6563             :            alpha0_1, dfLastValidAlpha);
    6564             : #endif
    6565             : 
    6566        3083 :     if (poLSNew != nullptr)
    6567             :     {
    6568          13 :         double dfScale2 = std::max(1.0, fabs(p0.getX()));
    6569          13 :         dfScale2 = std::max(dfScale2, fabs(p0.getY()));
    6570             :         // Not strictly necessary, but helps having 'clean' lines without
    6571             :         // duplicated points.
    6572          13 :         constexpr double dfToleranceEps =
    6573             :             OGRCompoundCurve::DEFAULT_TOLERANCE_EPSILON;
    6574          13 :         if (fabs(poLSNew->getX(poLSNew->getNumPoints() - 1) - p0.getX()) >
    6575          14 :                 dfToleranceEps * dfScale2 ||
    6576           1 :             fabs(poLSNew->getY(poLSNew->getNumPoints() - 1) - p0.getY()) >
    6577           1 :                 dfToleranceEps * dfScale2)
    6578          12 :             poLSNew->addPoint(&p0);
    6579          13 :         if (poLSNew->getNumPoints() >= 2)
    6580             :         {
    6581          12 :             if (poCC == nullptr)
    6582           5 :                 poCC = new OGRCompoundCurve();
    6583          12 :             poCC->addCurveDirectly(poLSNew);
    6584             :         }
    6585             :         else
    6586           1 :             delete poLSNew;
    6587          13 :         poLSNew = nullptr;
    6588             :     }
    6589             : 
    6590        3083 :     if (poCS == nullptr)
    6591             :     {
    6592        3059 :         poCS = new OGRCircularString();
    6593        3059 :         poCS->addPoint(&p0);
    6594             :     }
    6595             : 
    6596        3083 :     OGRPoint *poFinalPoint = (j + 2 >= poLS->getNumPoints()) ? &p3 : &p2;
    6597             : 
    6598        3083 :     double dfXMid = 0.0;
    6599        3083 :     double dfYMid = 0.0;
    6600        3083 :     double dfZMid = 0.0;
    6601        3083 :     if (bValidAlphaRatio)
    6602             :     {
    6603             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6604             :         printf("Using alpha ratio...\n"); /*ok*/
    6605             : #endif
    6606        3070 :         double dfAlphaMid = 0.0;
    6607        3070 :         if (OGRGF_NeedSwithArcOrder(p0.getX(), p0.getY(), poFinalPoint->getX(),
    6608             :                                     poFinalPoint->getY()))
    6609             :         {
    6610             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6611             :             printf("Switching angles\n"); /*ok*/
    6612             : #endif
    6613        1561 :             dfAlphaMid = dfLastValidAlpha + nAlphaRatioRef *
    6614        1561 :                                                 (alpha0_1 - dfLastValidAlpha) /
    6615             :                                                 HIDDEN_ALPHA_SCALE;
    6616        1561 :             dfAlphaMid = OGRGF_FixAngle(alpha0_1, dfLastValidAlpha, dfAlphaMid);
    6617             :         }
    6618             :         else
    6619             :         {
    6620        1509 :             dfAlphaMid = alpha0_1 + nAlphaRatioRef *
    6621        1509 :                                         (dfLastValidAlpha - alpha0_1) /
    6622             :                                         HIDDEN_ALPHA_SCALE;
    6623             :         }
    6624             : 
    6625        3070 :         dfXMid = cx_1 + R_1 * cos(dfAlphaMid);
    6626        3070 :         dfYMid = cy_1 + R_1 * sin(dfAlphaMid);
    6627             : 
    6628        3070 :         if (poLS->getCoordinateDimension() == 3)
    6629             :         {
    6630           2 :             double dfLastAlpha = 0.0;
    6631           2 :             double dfLastZ = 0.0;
    6632           2 :             int k = i;  // Used after for.
    6633          48 :             for (; k < j + 2; k++)
    6634             :             {
    6635          48 :                 OGRPoint p;
    6636          48 :                 poLS->getPoint(k, &p);
    6637          48 :                 double dfAlpha = atan2(p.getY() - cy_1, p.getX() - cx_1);
    6638          48 :                 dfAlpha = OGRGF_FixAngle(alpha0_1, dfLastValidAlpha, dfAlpha);
    6639          48 :                 if (k > i &&
    6640          46 :                     ((dfAlpha < dfLastValidAlpha && dfAlphaMid < dfAlpha) ||
    6641          23 :                      (dfAlpha > dfLastValidAlpha && dfAlphaMid > dfAlpha)))
    6642             :                 {
    6643           2 :                     const double dfRatio =
    6644           2 :                         (dfAlphaMid - dfLastAlpha) / (dfAlpha - dfLastAlpha);
    6645           2 :                     dfZMid = (1 - dfRatio) * dfLastZ + dfRatio * p.getZ();
    6646           2 :                     break;
    6647             :                 }
    6648          46 :                 dfLastAlpha = dfAlpha;
    6649          46 :                 dfLastZ = p.getZ();
    6650             :             }
    6651           2 :             if (k == j + 2)
    6652           0 :                 dfZMid = dfLastZ;
    6653           2 :             if (IS_ALMOST_INTEGER(dfZMid))
    6654           2 :                 dfZMid = static_cast<int>(floor(dfZMid + 0.5));
    6655             :         }
    6656             : 
    6657             :         // A few rounding strategies in case the mid point was at "exact"
    6658             :         // coordinates.
    6659        3070 :         if (R_1 > 1e-5)
    6660             :         {
    6661             :             const bool bStartEndInteger =
    6662        9170 :                 IS_ALMOST_INTEGER(p0.getX()) && IS_ALMOST_INTEGER(p0.getY()) &&
    6663        9170 :                 IS_ALMOST_INTEGER(poFinalPoint->getX()) &&
    6664        3051 :                 IS_ALMOST_INTEGER(poFinalPoint->getY());
    6665        3064 :             if (bStartEndInteger &&
    6666        3051 :                 fabs(dfXMid - floor(dfXMid + 0.5)) / dfScale < 1e-4 &&
    6667        3032 :                 fabs(dfYMid - floor(dfYMid + 0.5)) / dfScale < 1e-4)
    6668             :             {
    6669        3032 :                 dfXMid = static_cast<int>(floor(dfXMid + 0.5));
    6670        3032 :                 dfYMid = static_cast<int>(floor(dfYMid + 0.5));
    6671             :                 // Sometimes rounding to closest is not best approach
    6672             :                 // Try neighbouring integers to look for the one that
    6673             :                 // minimize the error w.r.t to the arc center
    6674             :                 // But only do that if the radius is greater than
    6675             :                 // the magnitude of the delta that we will try!
    6676             :                 double dfBestRError =
    6677        3032 :                     fabs(R_1 - DISTANCE(dfXMid, dfYMid, cx_1, cy_1));
    6678             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6679             :                 printf("initial_error=%f\n", dfBestRError); /*ok*/
    6680             : #endif
    6681        3032 :                 int iBestX = 0;
    6682        3032 :                 int iBestY = 0;
    6683        3032 :                 if (dfBestRError > 0.001 && R_1 > 2)
    6684             :                 {
    6685           3 :                     int nSearchRadius = 1;
    6686             :                     // Extend the search radius if the arc circle radius
    6687             :                     // is much higher than the coordinate values.
    6688             :                     double dfMaxCoords =
    6689           3 :                         std::max(fabs(p0.getX()), fabs(p0.getY()));
    6690           3 :                     dfMaxCoords = std::max(dfMaxCoords, poFinalPoint->getX());
    6691           3 :                     dfMaxCoords = std::max(dfMaxCoords, poFinalPoint->getY());
    6692           3 :                     dfMaxCoords = std::max(dfMaxCoords, dfXMid);
    6693           3 :                     dfMaxCoords = std::max(dfMaxCoords, dfYMid);
    6694           3 :                     if (R_1 > dfMaxCoords * 1000)
    6695           3 :                         nSearchRadius = 100;
    6696           0 :                     else if (R_1 > dfMaxCoords * 10)
    6697           0 :                         nSearchRadius = 10;
    6698         606 :                     for (int iY = -nSearchRadius; iY <= nSearchRadius; iY++)
    6699             :                     {
    6700      121806 :                         for (int iX = -nSearchRadius; iX <= nSearchRadius; iX++)
    6701             :                         {
    6702      121203 :                             const double dfCandidateX = dfXMid + iX;
    6703      121203 :                             const double dfCandidateY = dfYMid + iY;
    6704      121203 :                             if (fabs(dfCandidateX - p0.getX()) < 1e-8 &&
    6705           0 :                                 fabs(dfCandidateY - p0.getY()) < 1e-8)
    6706           0 :                                 continue;
    6707      121203 :                             if (fabs(dfCandidateX - poFinalPoint->getX()) <
    6708      121203 :                                     1e-8 &&
    6709           0 :                                 fabs(dfCandidateY - poFinalPoint->getY()) <
    6710             :                                     1e-8)
    6711           0 :                                 continue;
    6712             :                             const double dfRError =
    6713      121203 :                                 fabs(R_1 - DISTANCE(dfCandidateX, dfCandidateY,
    6714      121203 :                                                     cx_1, cy_1));
    6715             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6716             :                             printf("x=%d y=%d error=%f besterror=%f\n", /*ok*/
    6717             :                                    static_cast<int>(dfXMid + iX),
    6718             :                                    static_cast<int>(dfYMid + iY), dfRError,
    6719             :                                    dfBestRError);
    6720             : #endif
    6721      121203 :                             if (dfRError < dfBestRError)
    6722             :                             {
    6723          20 :                                 iBestX = iX;
    6724          20 :                                 iBestY = iY;
    6725          20 :                                 dfBestRError = dfRError;
    6726             :                             }
    6727             :                         }
    6728             :                     }
    6729             :                 }
    6730        3032 :                 dfXMid += iBestX;
    6731        3032 :                 dfYMid += iBestY;
    6732             :             }
    6733             :             else
    6734             :             {
    6735             :                 // Limit the number of significant figures in decimal
    6736             :                 // representation.
    6737          32 :                 if (fabs(dfXMid) < 100000000.0)
    6738             :                 {
    6739          32 :                     dfXMid =
    6740          32 :                         static_cast<GIntBig>(floor(dfXMid * 100000000 + 0.5)) /
    6741             :                         100000000.0;
    6742             :                 }
    6743          32 :                 if (fabs(dfYMid) < 100000000.0)
    6744             :                 {
    6745          32 :                     dfYMid =
    6746          32 :                         static_cast<GIntBig>(floor(dfYMid * 100000000 + 0.5)) /
    6747             :                         100000000.0;
    6748             :                 }
    6749             :             }
    6750             :         }
    6751             : 
    6752             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6753             :         printf("dfAlphaMid=%f, x_mid = %f, y_mid = %f\n", /*ok*/
    6754             :                dfLastValidAlpha, dfXMid, dfYMid);
    6755             : #endif
    6756             :     }
    6757             : 
    6758             :     // If this is a full circle of a non-polygonal zone, we must
    6759             :     // use a 5-point representation to keep the winding order.
    6760        3094 :     if (p0.Equals(poFinalPoint) &&
    6761          11 :         !EQUAL(poLS->getGeometryName(), "LINEARRING"))
    6762             :     {
    6763             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6764             :         printf("Full circle of a non-polygonal zone\n"); /*ok*/
    6765             : #endif
    6766           1 :         poLS->getPoint((i + j + 2) / 4, &p1);
    6767           1 :         poCS->addPoint(&p1);
    6768           1 :         if (bValidAlphaRatio)
    6769             :         {
    6770           1 :             p1.setX(dfXMid);
    6771           1 :             p1.setY(dfYMid);
    6772           1 :             if (poLS->getCoordinateDimension() == 3)
    6773           0 :                 p1.setZ(dfZMid);
    6774             :         }
    6775             :         else
    6776             :         {
    6777           0 :             poLS->getPoint((i + j + 1) / 2, &p1);
    6778             :         }
    6779           1 :         poCS->addPoint(&p1);
    6780           1 :         poLS->getPoint(3 * (i + j + 2) / 4, &p1);
    6781           1 :         poCS->addPoint(&p1);
    6782             :     }
    6783             : 
    6784        3082 :     else if (bValidAlphaRatio)
    6785             :     {
    6786        3069 :         p1.setX(dfXMid);
    6787        3069 :         p1.setY(dfYMid);
    6788        3069 :         if (poLS->getCoordinateDimension() == 3)
    6789           2 :             p1.setZ(dfZMid);
    6790        3069 :         poCS->addPoint(&p1);
    6791             :     }
    6792             : 
    6793             :     // If we have found a candidate for a precise intermediate
    6794             :     // point, use it.
    6795          13 :     else if (iMidPoint >= 1 && iMidPoint < j)
    6796             :     {
    6797           3 :         poLS->getPoint(iMidPoint, &p1);
    6798           3 :         poCS->addPoint(&p1);
    6799             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6800             :         printf("Using detected midpoint...\n");                   /*ok*/
    6801             :         printf("x_mid = %f, y_mid = %f\n", p1.getX(), p1.getY()); /*ok*/
    6802             : #endif
    6803             :     }
    6804             :     // Otherwise pick up the mid point between both extremities.
    6805             :     else
    6806             :     {
    6807          10 :         poLS->getPoint((i + j + 1) / 2, &p1);
    6808          10 :         poCS->addPoint(&p1);
    6809             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6810             :         printf("Pickup 'random' midpoint at index=%d...\n", /*ok*/
    6811             :                (i + j + 1) / 2);
    6812             :         printf("x_mid = %f, y_mid = %f\n", p1.getX(), p1.getY()); /*ok*/
    6813             : #endif
    6814             :     }
    6815        3083 :     poCS->addPoint(poFinalPoint);
    6816             : 
    6817             : #ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
    6818             :     printf("----------------------------\n"); /*ok*/
    6819             : #endif
    6820             : 
    6821        3083 :     if (j + 2 >= poLS->getNumPoints())
    6822        3045 :         return -2;
    6823          38 :     return j + 1;
    6824             : }
    6825             : 
    6826             : /************************************************************************/
    6827             : /*                        curveFromLineString()                         */
    6828             : /************************************************************************/
    6829             : 
    6830             : /**
    6831             :  * \brief Try to convert a linestring approximating curves into a curve.
    6832             :  *
    6833             :  * This method can return a COMPOUNDCURVE, a CIRCULARSTRING or a LINESTRING.
    6834             :  *
    6835             :  * This method is the reverse of curveFromLineString().
    6836             :  *
    6837             :  * @param poLS handle to the geometry to convert.
    6838             :  * @param papszOptions options as a null-terminated list of strings.
    6839             :  *                     Unused for now. Must be set to NULL.
    6840             :  *
    6841             :  * @return the converted geometry (ownership to caller).
    6842             :  *
    6843             :  */
    6844             : 
    6845        3195 : OGRCurve *OGRGeometryFactory::curveFromLineString(
    6846             :     const OGRLineString *poLS, CPL_UNUSED const char *const *papszOptions)
    6847             : {
    6848        3195 :     OGRCompoundCurve *poCC = nullptr;
    6849        3195 :     OGRCircularString *poCS = nullptr;
    6850        3195 :     OGRLineString *poLSNew = nullptr;
    6851        3195 :     const int nLSNumPoints = poLS->getNumPoints();
    6852        3195 :     const bool bIsClosed = nLSNumPoints >= 4 && poLS->get_IsClosed();
    6853        3623 :     for (int i = 0; i < nLSNumPoints; /* nothing */)
    6854             :     {
    6855        3473 :         const int iNewI = OGRGF_DetectArc(poLS, i, poCC, poCS, poLSNew);
    6856        3473 :         if (iNewI == -2)
    6857        3045 :             break;
    6858         428 :         if (iNewI >= 0)
    6859             :         {
    6860          38 :             i = iNewI;
    6861          38 :             continue;
    6862             :         }
    6863             : 
    6864         390 :         if (poCS != nullptr)
    6865             :         {
    6866          14 :             if (poCC == nullptr)
    6867           3 :                 poCC = new OGRCompoundCurve();
    6868          14 :             poCC->addCurveDirectly(poCS);
    6869          14 :             poCS = nullptr;
    6870             :         }
    6871             : 
    6872         390 :         OGRPoint p;
    6873         390 :         poLS->getPoint(i, &p);
    6874         390 :         if (poLSNew == nullptr)
    6875             :         {
    6876         162 :             poLSNew = new OGRLineString();
    6877         162 :             poLSNew->addPoint(&p);
    6878             :         }
    6879             :         // Not strictly necessary, but helps having 'clean' lines without
    6880             :         // duplicated points.
    6881             :         else
    6882             :         {
    6883         228 :             double dfScale = std::max(1.0, fabs(p.getX()));
    6884         228 :             dfScale = std::max(dfScale, fabs(p.getY()));
    6885         228 :             if (bIsClosed && i == nLSNumPoints - 1)
    6886           7 :                 dfScale = 0;
    6887         228 :             constexpr double dfToleranceEps =
    6888             :                 OGRCompoundCurve::DEFAULT_TOLERANCE_EPSILON;
    6889         228 :             if (fabs(poLSNew->getX(poLSNew->getNumPoints() - 1) - p.getX()) >
    6890         237 :                     dfToleranceEps * dfScale ||
    6891           9 :                 fabs(poLSNew->getY(poLSNew->getNumPoints() - 1) - p.getY()) >
    6892           9 :                     dfToleranceEps * dfScale)
    6893             :             {
    6894         227 :                 poLSNew->addPoint(&p);
    6895             :             }
    6896             :         }
    6897             : 
    6898         390 :         i++;
    6899             :     }
    6900             : 
    6901        3195 :     OGRCurve *poRet = nullptr;
    6902             : 
    6903        3195 :     if (poLSNew != nullptr && poLSNew->getNumPoints() < 2)
    6904             :     {
    6905           1 :         delete poLSNew;
    6906           1 :         poLSNew = nullptr;
    6907           1 :         if (poCC != nullptr)
    6908             :         {
    6909           1 :             if (poCC->getNumCurves() == 1)
    6910             :             {
    6911           1 :                 poRet = poCC->stealCurve(0);
    6912           1 :                 delete poCC;
    6913           1 :                 poCC = nullptr;
    6914             :             }
    6915             :             else
    6916           0 :                 poRet = poCC;
    6917             :         }
    6918             :         else
    6919           0 :             poRet = poLS->clone();
    6920             :     }
    6921        3194 :     else if (poCC != nullptr)
    6922             :     {
    6923           7 :         if (poLSNew)
    6924           6 :             poCC->addCurveDirectly(poLSNew);
    6925             :         else
    6926           1 :             poCC->addCurveDirectly(poCS);
    6927           7 :         poRet = poCC;
    6928             :     }
    6929        3187 :     else if (poLSNew != nullptr)
    6930         142 :         poRet = poLSNew;
    6931        3045 :     else if (poCS != nullptr)
    6932        3044 :         poRet = poCS;
    6933             :     else
    6934           1 :         poRet = poLS->clone();
    6935             : 
    6936        3195 :     poRet->assignSpatialReference(poLS->getSpatialReference());
    6937             : 
    6938        3195 :     return poRet;
    6939             : }
    6940             : 
    6941             : /************************************************************************/
    6942             : /*                   createFromGeoJson( const char* )                   */
    6943             : /************************************************************************/
    6944             : 
    6945             : /**
    6946             :  * @brief Create geometry from GeoJson fragment.
    6947             :  * @param pszJsonString The GeoJSON fragment for the geometry.
    6948             :  * @param nSize (new in GDAL 3.4) Optional length of the string
    6949             :  *              if it is not null-terminated
    6950             :  * @return a geometry on success, or NULL on error.
    6951             :  */
    6952           5 : OGRGeometry *OGRGeometryFactory::createFromGeoJson(const char *pszJsonString,
    6953             :                                                    int nSize)
    6954             : {
    6955          10 :     CPLJSONDocument oDocument;
    6956           5 :     if (!oDocument.LoadMemory(reinterpret_cast<const GByte *>(pszJsonString),
    6957             :                               nSize))
    6958             :     {
    6959           3 :         return nullptr;
    6960             :     }
    6961             : 
    6962           2 :     return createFromGeoJson(oDocument.GetRoot());
    6963             : }
    6964             : 
    6965             : /************************************************************************/
    6966             : /*              createFromGeoJson( const CPLJSONObject& )               */
    6967             : /************************************************************************/
    6968             : 
    6969             : /**
    6970             :  * @brief Create geometry from GeoJson fragment.
    6971             :  * @param oJsonObject The JSONObject class describes the GeoJSON geometry.
    6972             :  * @return a geometry on success, or NULL on error.
    6973             :  */
    6974             : OGRGeometry *
    6975           2 : OGRGeometryFactory::createFromGeoJson(const CPLJSONObject &oJsonObject)
    6976             : {
    6977           2 :     if (!oJsonObject.IsValid())
    6978             :     {
    6979           0 :         return nullptr;
    6980             :     }
    6981             : 
    6982             :     // TODO: Move from GeoJSON driver functions create geometry here, and
    6983             :     // replace json-c specific json_object to CPLJSONObject
    6984           4 :     return OGRGeoJSONReadGeometry(
    6985           2 :                static_cast<json_object *>(oJsonObject.GetInternalHandle()),
    6986             :                /* bHasM = */ false, /* OGRSpatialReference* = */ nullptr)
    6987           2 :         .release();
    6988             : }

Generated by: LCOV version 1.14