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

Generated by: LCOV version 1.14