Coverage Report

Created: 2026-06-30 08:33

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