LCOV - code coverage report
Current view: top level - ogr - ogrgeometryfactory.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2327 2554 91.1 %
Date: 2025-01-18 12:42:00 Functions: 77 81 95.1 %

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

Generated by: LCOV version 1.14