LCOV - code coverage report
Current view: top level - ogr - ogrgeometryfactory.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2386 2607 91.5 %
Date: 2025-10-21 22:35:35 Functions: 82 84 97.6 %

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

Generated by: LCOV version 1.14