LCOV - code coverage report
Current view: top level - ogr - ogrgeometryfactory.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2440 2662 91.7 %
Date: 2026-02-01 11:59:10 Functions: 88 91 96.7 %

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

Generated by: LCOV version 1.14