Coverage Report

Created: 2026-07-25 06:50

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
0
{
97
0
    size_t nBytesConsumedOutIgnored = 0;
98
0
    return createFromWkb(pabyData, poSR, ppoReturn, nBytes, eWkbVariant,
99
0
                         nBytesConsumedOutIgnored);
100
0
}
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
0
{
142
0
    const GByte *l_pabyData = static_cast<const GByte *>(pabyData);
143
0
    nBytesConsumedOut = 0;
144
0
    *ppoReturn = nullptr;
145
146
0
    if (nBytes < 9 && nBytes != static_cast<size_t>(-1))
147
0
        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
0
    const int nByteOrder = DB2_V72_FIX_BYTE_ORDER(*l_pabyData);
154
0
    if (nByteOrder != wkbXDR && nByteOrder != wkbNDR)
155
0
    {
156
0
        CPLDebug("OGR",
157
0
                 "OGRGeometryFactory::createFromWkb() - got corrupt data.\n"
158
0
                 "%02X%02X%02X%02X%02X%02X%02X%02X%02X",
159
0
                 l_pabyData[0], l_pabyData[1], l_pabyData[2], l_pabyData[3],
160
0
                 l_pabyData[4], l_pabyData[5], l_pabyData[6], l_pabyData[7],
161
0
                 l_pabyData[8]);
162
0
        return OGRERR_CORRUPT_DATA;
163
0
    }
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
0
    OGRwkbGeometryType eGeometryType = wkbUnknown;
172
0
    const OGRErr err =
173
0
        OGRReadWKBGeometryType(l_pabyData, eWkbVariant, &eGeometryType);
174
175
0
    if (err != OGRERR_NONE)
176
0
        return err;
177
178
    /* -------------------------------------------------------------------- */
179
    /*      Instantiate a geometry of the appropriate type, and             */
180
    /*      initialize from the input stream.                               */
181
    /* -------------------------------------------------------------------- */
182
0
    OGRGeometry *poGeom = createGeometry(eGeometryType);
183
184
0
    if (poGeom == nullptr)
185
0
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
186
187
    /* -------------------------------------------------------------------- */
188
    /*      Import from binary.                                             */
189
    /* -------------------------------------------------------------------- */
190
0
    const OGRErr eErr = poGeom->importFromWkb(l_pabyData, nBytes, eWkbVariant,
191
0
                                              nBytesConsumedOut);
192
0
    if (eErr != OGRERR_NONE)
193
0
    {
194
0
        delete poGeom;
195
0
        return eErr;
196
0
    }
197
198
    /* -------------------------------------------------------------------- */
199
    /*      Assign spatial reference system.                                */
200
    /* -------------------------------------------------------------------- */
201
202
0
    if (poGeom->hasCurveGeometry() &&
203
0
        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
0
    poGeom->assignSpatialReference(poSR);
210
0
    *ppoReturn = poGeom;
211
212
0
    return OGRERR_NONE;
213
0
}
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
0
{
252
0
    return OGRGeometryFactory::createFromWkb(
253
0
        pabyData, OGRSpatialReference::FromHandle(hSRS),
254
0
        reinterpret_cast<OGRGeometry **>(phGeometry), nBytes);
255
0
}
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
0
{
349
0
    const char *pszInput = *ppszData;
350
0
    *ppoReturn = nullptr;
351
352
0
    OGRSpatialReferenceRefCountedPtr poEwktSR;
353
354
    /* -------------------------------------------------------------------- */
355
    /*      Check for a SRID (PostGIS EWKT)                                 */
356
    /* -------------------------------------------------------------------- */
357
0
    if (STARTS_WITH_CI(pszInput, "SRID="))
358
0
    {
359
0
        const char *pszSRID = pszInput + 5;
360
0
        char *pszEnd;
361
362
0
        auto nSRID = std::strtol(pszSRID, &pszEnd, 10);
363
364
0
        if (static_cast<int>(nSRID) != nSRID || !isdigit(*pszSRID))
365
0
        {
366
0
            return OGRERR_CORRUPT_DATA;
367
0
        }
368
369
0
        while (pszSRID != pszEnd)
370
0
        {
371
0
            if (!isdigit(*pszSRID))
372
0
            {
373
0
                return OGRERR_CORRUPT_DATA;
374
0
            }
375
0
            pszSRID++;
376
0
        }
377
378
0
        if (*pszEnd != ';')
379
0
        {
380
0
            return OGRERR_CORRUPT_DATA;
381
0
        }
382
383
0
        pszInput = pszEnd + 1;
384
385
0
        if (poSR == nullptr)
386
0
        {
387
0
            poEwktSR = OGRSpatialReferenceRefCountedPtr::makeInstance();
388
0
            if (poEwktSR->importFromEPSG(static_cast<int>(nSRID)) !=
389
0
                OGRERR_NONE)
390
0
            {
391
0
                return OGRERR_CORRUPT_DATA;
392
0
            }
393
0
            poEwktSR->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
394
0
            poSR = poEwktSR.get();
395
0
        }
396
0
    }
397
398
    /* -------------------------------------------------------------------- */
399
    /*      Get the first token, which should be the geometry type.         */
400
    /* -------------------------------------------------------------------- */
401
0
    char szToken[OGR_WKT_TOKEN_MAX] = {};
402
0
    if (OGRWktReadToken(pszInput, szToken) == nullptr)
403
0
        return OGRERR_CORRUPT_DATA;
404
405
    /* -------------------------------------------------------------------- */
406
    /*      Instantiate a geometry of the appropriate type.                 */
407
    /* -------------------------------------------------------------------- */
408
0
    OGRGeometry *poGeom = nullptr;
409
0
    if (STARTS_WITH_CI(szToken, "POINT"))
410
0
    {
411
0
        poGeom = new OGRPoint();
412
0
    }
413
0
    else if (STARTS_WITH_CI(szToken, "LINESTRING"))
414
0
    {
415
0
        poGeom = new OGRLineString();
416
0
    }
417
0
    else if (STARTS_WITH_CI(szToken, "POLYGON"))
418
0
    {
419
0
        poGeom = new OGRPolygon();
420
0
    }
421
0
    else if (STARTS_WITH_CI(szToken, "TRIANGLE"))
422
0
    {
423
0
        poGeom = new OGRTriangle();
424
0
    }
425
0
    else if (STARTS_WITH_CI(szToken, "GEOMETRYCOLLECTION"))
426
0
    {
427
0
        poGeom = new OGRGeometryCollection();
428
0
    }
429
0
    else if (STARTS_WITH_CI(szToken, "MULTIPOLYGON"))
430
0
    {
431
0
        poGeom = new OGRMultiPolygon();
432
0
    }
433
0
    else if (STARTS_WITH_CI(szToken, "MULTIPOINT"))
434
0
    {
435
0
        poGeom = new OGRMultiPoint();
436
0
    }
437
0
    else if (STARTS_WITH_CI(szToken, "MULTILINESTRING"))
438
0
    {
439
0
        poGeom = new OGRMultiLineString();
440
0
    }
441
0
    else if (STARTS_WITH_CI(szToken, "CIRCULARSTRING"))
442
0
    {
443
0
        poGeom = new OGRCircularString();
444
0
    }
445
0
    else if (STARTS_WITH_CI(szToken, "COMPOUNDCURVE"))
446
0
    {
447
0
        poGeom = new OGRCompoundCurve();
448
0
    }
449
0
    else if (STARTS_WITH_CI(szToken, "CURVEPOLYGON"))
450
0
    {
451
0
        poGeom = new OGRCurvePolygon();
452
0
    }
453
0
    else if (STARTS_WITH_CI(szToken, "MULTICURVE"))
454
0
    {
455
0
        poGeom = new OGRMultiCurve();
456
0
    }
457
0
    else if (STARTS_WITH_CI(szToken, "MULTISURFACE"))
458
0
    {
459
0
        poGeom = new OGRMultiSurface();
460
0
    }
461
462
0
    else if (STARTS_WITH_CI(szToken, "POLYHEDRALSURFACE"))
463
0
    {
464
0
        poGeom = new OGRPolyhedralSurface();
465
0
    }
466
467
0
    else if (STARTS_WITH_CI(szToken, "TIN"))
468
0
    {
469
0
        poGeom = new OGRTriangulatedSurface();
470
0
    }
471
472
0
    else
473
0
    {
474
0
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
475
0
    }
476
477
    /* -------------------------------------------------------------------- */
478
    /*      Do the import.                                                  */
479
    /* -------------------------------------------------------------------- */
480
0
    const OGRErr eErr = poGeom->importFromWkt(&pszInput);
481
482
    /* -------------------------------------------------------------------- */
483
    /*      Assign spatial reference system.                                */
484
    /* -------------------------------------------------------------------- */
485
0
    if (eErr == OGRERR_NONE)
486
0
    {
487
0
        if (poGeom->hasCurveGeometry() &&
488
0
            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
0
        poGeom->assignSpatialReference(poSR);
495
0
        *ppoReturn = poGeom;
496
0
        *ppszData = pszInput;
497
0
    }
498
0
    else
499
0
    {
500
0
        delete poGeom;
501
0
    }
502
503
0
    return eErr;
504
0
}
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
0
{
531
0
    return createFromWkt(&pszData, poSR, ppoReturn);
532
0
}
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
0
{
557
0
    std::unique_ptr<OGRGeometry> poGeom;
558
0
    OGRGeometry *poTmpGeom;
559
0
    auto err = createFromWkt(&pszData, poSR, &poTmpGeom);
560
0
    poGeom.reset(poTmpGeom);
561
562
0
    return {std::move(poGeom), err};
563
0
}
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
0
{
594
0
    return OGRGeometryFactory::createFromWkt(
595
0
        const_cast<const char **>(ppszData),
596
0
        OGRSpatialReference::FromHandle(hSRS),
597
0
        reinterpret_cast<OGRGeometry **>(phGeometry));
598
0
}
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
0
{
659
0
    OGRGeometry *poGeom = nullptr;
660
0
    switch (wkbFlatten(eGeometryType))
661
0
    {
662
0
        case wkbPoint:
663
0
            poGeom = new (std::nothrow) OGRPoint();
664
0
            break;
665
666
0
        case wkbLineString:
667
0
            poGeom = new (std::nothrow) OGRLineString();
668
0
            break;
669
670
0
        case wkbPolygon:
671
0
            poGeom = new (std::nothrow) OGRPolygon();
672
0
            break;
673
674
0
        case wkbGeometryCollection:
675
0
            poGeom = new (std::nothrow) OGRGeometryCollection();
676
0
            break;
677
678
0
        case wkbMultiPolygon:
679
0
            poGeom = new (std::nothrow) OGRMultiPolygon();
680
0
            break;
681
682
0
        case wkbMultiPoint:
683
0
            poGeom = new (std::nothrow) OGRMultiPoint();
684
0
            break;
685
686
0
        case wkbMultiLineString:
687
0
            poGeom = new (std::nothrow) OGRMultiLineString();
688
0
            break;
689
690
0
        case wkbLinearRing:
691
0
            poGeom = new (std::nothrow) OGRLinearRing();
692
0
            break;
693
694
0
        case wkbCircularString:
695
0
            poGeom = new (std::nothrow) OGRCircularString();
696
0
            break;
697
698
0
        case wkbCompoundCurve:
699
0
            poGeom = new (std::nothrow) OGRCompoundCurve();
700
0
            break;
701
702
0
        case wkbCurvePolygon:
703
0
            poGeom = new (std::nothrow) OGRCurvePolygon();
704
0
            break;
705
706
0
        case wkbMultiCurve:
707
0
            poGeom = new (std::nothrow) OGRMultiCurve();
708
0
            break;
709
710
0
        case wkbMultiSurface:
711
0
            poGeom = new (std::nothrow) OGRMultiSurface();
712
0
            break;
713
714
0
        case wkbTriangle:
715
0
            poGeom = new (std::nothrow) OGRTriangle();
716
0
            break;
717
718
0
        case wkbPolyhedralSurface:
719
0
            poGeom = new (std::nothrow) OGRPolyhedralSurface();
720
0
            break;
721
722
0
        case wkbTIN:
723
0
            poGeom = new (std::nothrow) OGRTriangulatedSurface();
724
0
            break;
725
726
0
        case wkbUnknown:
727
0
            break;
728
729
0
        default:
730
0
            CPLAssert(false);
731
0
            break;
732
0
    }
733
0
    if (poGeom)
734
0
    {
735
0
        if (OGR_GT_HasZ(eGeometryType))
736
0
            poGeom->set3D(true);
737
0
        if (OGR_GT_HasM(eGeometryType))
738
0
            poGeom->setMeasured(true);
739
0
    }
740
0
    return poGeom;
741
0
}
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
0
{
808
0
    delete OGRGeometry::FromHandle(hGeom);
809
0
}
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
0
{
834
0
    if (poGeom == nullptr)
835
0
        return nullptr;
836
837
0
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
838
839
0
    if (eGeomType == wkbCurvePolygon)
840
0
    {
841
0
        OGRCurvePolygon *poCurve = poGeom->toCurvePolygon();
842
843
0
        if (!poGeom->hasCurveGeometry(TRUE))
844
0
            return OGRSurface::CastToPolygon(poCurve);
845
846
0
        OGRPolygon *poPoly = poCurve->CurvePolyToPoly();
847
0
        delete poGeom;
848
0
        return poPoly;
849
0
    }
850
851
    // base polygon or triangle
852
0
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolygon))
853
0
    {
854
0
        return OGRSurface::CastToPolygon(poGeom->toSurface());
855
0
    }
856
857
0
    if (OGR_GT_IsCurve(eGeomType))
858
0
    {
859
0
        OGRCurve *poCurve = poGeom->toCurve();
860
0
        if (poCurve->getNumPoints() >= 3 && poCurve->get_IsClosed())
861
0
        {
862
0
            OGRPolygon *poPolygon = new OGRPolygon();
863
0
            poPolygon->assignSpatialReference(poGeom->getSpatialReference());
864
865
0
            if (!poGeom->hasCurveGeometry(TRUE))
866
0
            {
867
0
                poPolygon->addRingDirectly(OGRCurve::CastToLinearRing(poCurve));
868
0
            }
869
0
            else
870
0
            {
871
0
                OGRLineString *poLS = poCurve->CurveToLine();
872
0
                poPolygon->addRingDirectly(OGRCurve::CastToLinearRing(poLS));
873
0
                delete poGeom;
874
0
            }
875
0
            return poPolygon;
876
0
        }
877
0
    }
878
879
0
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
880
0
    {
881
0
        OGRPolyhedralSurface *poPS = poGeom->toPolyhedralSurface();
882
0
        if (poPS->getNumGeometries() == 1)
883
0
        {
884
0
            poGeom = OGRSurface::CastToPolygon(
885
0
                poPS->getGeometryRef(0)->clone()->toSurface());
886
0
            delete poPS;
887
0
            return poGeom;
888
0
        }
889
0
    }
890
891
0
    if (eGeomType != wkbGeometryCollection && eGeomType != wkbMultiPolygon &&
892
0
        eGeomType != wkbMultiSurface)
893
0
        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
0
}
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
0
{
971
0
    if (poGeom == nullptr)
972
0
        return nullptr;
973
974
0
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
975
976
    /* -------------------------------------------------------------------- */
977
    /*      If this is already a MultiPolygon, nothing to do                */
978
    /* -------------------------------------------------------------------- */
979
0
    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
0
    if (eGeomType == wkbMultiSurface)
989
0
    {
990
0
        OGRMultiSurface *poMS = poGeom->toMultiSurface();
991
0
        if (!poMS->hasCurveGeometry(TRUE))
992
0
        {
993
0
            return OGRMultiSurface::CastToMultiPolygon(poMS);
994
0
        }
995
0
    }
996
997
    /* -------------------------------------------------------------------- */
998
    /*      Check for the case of a geometrycollection that can be          */
999
    /*      promoted to MultiPolygon.                                       */
1000
    /* -------------------------------------------------------------------- */
1001
0
    if (eGeomType == wkbGeometryCollection || eGeomType == wkbMultiSurface)
1002
0
    {
1003
0
        bool bAllPoly = true;
1004
0
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
1005
0
        if (poGeom->hasCurveGeometry())
1006
0
        {
1007
0
            OGRGeometryCollection *poNewGC =
1008
0
                poGC->getLinearGeometry()->toGeometryCollection();
1009
0
            delete poGC;
1010
0
            poGeom = poNewGC;
1011
0
            poGC = poNewGC;
1012
0
        }
1013
1014
0
        bool bCanConvertToMultiPoly = true;
1015
0
        for (int iGeom = 0; iGeom < poGC->getNumGeometries(); iGeom++)
1016
0
        {
1017
0
            OGRwkbGeometryType eSubGeomType =
1018
0
                wkbFlatten(poGC->getGeometryRef(iGeom)->getGeometryType());
1019
0
            if (eSubGeomType != wkbPolygon)
1020
0
                bAllPoly = false;
1021
0
            if (eSubGeomType != wkbMultiPolygon && eSubGeomType != wkbPolygon &&
1022
0
                eSubGeomType != wkbPolyhedralSurface && eSubGeomType != wkbTIN)
1023
0
            {
1024
0
                bCanConvertToMultiPoly = false;
1025
0
            }
1026
0
        }
1027
1028
0
        if (!bCanConvertToMultiPoly)
1029
0
            return poGeom;
1030
1031
0
        OGRMultiPolygon *poMP = new OGRMultiPolygon();
1032
0
        poMP->assignSpatialReference(poGeom->getSpatialReference());
1033
1034
0
        while (poGC->getNumGeometries() > 0)
1035
0
        {
1036
0
            OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
1037
0
            poGC->removeGeometry(0, FALSE);
1038
0
            if (bAllPoly)
1039
0
            {
1040
0
                poMP->addGeometryDirectly(poSubGeom);
1041
0
            }
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
0
        }
1054
1055
0
        delete poGC;
1056
1057
0
        return poMP;
1058
0
    }
1059
1060
0
    if (eGeomType == wkbCurvePolygon)
1061
0
    {
1062
0
        OGRPolygon *poPoly = poGeom->toCurvePolygon()->CurvePolyToPoly();
1063
0
        OGRMultiPolygon *poMP = new OGRMultiPolygon();
1064
0
        poMP->assignSpatialReference(poGeom->getSpatialReference());
1065
0
        poMP->addGeometryDirectly(poPoly);
1066
0
        delete poGeom;
1067
0
        return poMP;
1068
0
    }
1069
1070
    /* -------------------------------------------------------------------- */
1071
    /*      If it is PolyhedralSurface or TIN, then pretend it is a         */
1072
    /*      multipolygon.                                                   */
1073
    /* -------------------------------------------------------------------- */
1074
0
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
1075
0
    {
1076
0
        return OGRPolyhedralSurface::CastToMultiPolygon(
1077
0
            poGeom->toPolyhedralSurface());
1078
0
    }
1079
1080
0
    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
0
    if (eGeomType != wkbPolygon)
1090
0
        return poGeom;
1091
1092
0
    OGRMultiPolygon *poMP = new OGRMultiPolygon();
1093
0
    poMP->assignSpatialReference(poGeom->getSpatialReference());
1094
0
    poMP->addGeometryDirectly(poGeom);
1095
1096
0
    return poMP;
1097
0
}
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
0
{
1240
0
    if (poGeom == nullptr)
1241
0
        return nullptr;
1242
1243
0
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
1244
1245
    /* -------------------------------------------------------------------- */
1246
    /*      If this is already a MultiLineString, nothing to do             */
1247
    /* -------------------------------------------------------------------- */
1248
0
    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
0
    if (eGeomType == wkbGeometryCollection)
1258
0
    {
1259
0
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
1260
0
        if (poGeom->hasCurveGeometry())
1261
0
        {
1262
0
            OGRGeometryCollection *poNewGC =
1263
0
                poGC->getLinearGeometry()->toGeometryCollection();
1264
0
            delete poGC;
1265
0
            poGeom = poNewGC;
1266
0
            poGC = poNewGC;
1267
0
        }
1268
1269
0
        for (auto &&poMember : poGC)
1270
0
        {
1271
0
            if (wkbFlatten(poMember->getGeometryType()) != wkbLineString)
1272
0
            {
1273
0
                return poGeom;
1274
0
            }
1275
0
        }
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
0
    }
1290
1291
    /* -------------------------------------------------------------------- */
1292
    /*      Turn a linestring into a multilinestring.                       */
1293
    /* -------------------------------------------------------------------- */
1294
0
    if (eGeomType == wkbLineString)
1295
0
    {
1296
0
        OGRMultiLineString *poMP = new OGRMultiLineString();
1297
0
        poMP->assignSpatialReference(poGeom->getSpatialReference());
1298
0
        poMP->addGeometryDirectly(poGeom);
1299
0
        return poMP;
1300
0
    }
1301
1302
    /* -------------------------------------------------------------------- */
1303
    /*      Convert polygons into a multilinestring.                        */
1304
    /* -------------------------------------------------------------------- */
1305
0
    if (OGR_GT_IsSubClassOf(eGeomType, wkbCurvePolygon))
1306
0
    {
1307
0
        OGRMultiLineString *poMLS = new OGRMultiLineString();
1308
0
        poMLS->assignSpatialReference(poGeom->getSpatialReference());
1309
1310
0
        const auto AddRingFromSrcPoly = [poMLS](const OGRPolygon *poPoly)
1311
0
        {
1312
0
            for (int iRing = 0; iRing < poPoly->getNumInteriorRings() + 1;
1313
0
                 iRing++)
1314
0
            {
1315
0
                const OGRLineString *poLR;
1316
1317
0
                if (iRing == 0)
1318
0
                {
1319
0
                    poLR = poPoly->getExteriorRing();
1320
0
                    if (poLR == nullptr)
1321
0
                        break;
1322
0
                }
1323
0
                else
1324
0
                    poLR = poPoly->getInteriorRing(iRing - 1);
1325
1326
0
                if (poLR == nullptr || poLR->getNumPoints() == 0)
1327
0
                    continue;
1328
1329
0
                auto poNewLS = new OGRLineString();
1330
0
                poNewLS->addSubLineString(poLR);
1331
0
                poMLS->addGeometryDirectly(poNewLS);
1332
0
            }
1333
0
        };
1334
1335
0
        if (OGR_GT_IsSubClassOf(eGeomType, wkbPolygon))
1336
0
        {
1337
0
            AddRingFromSrcPoly(poGeom->toPolygon());
1338
0
        }
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
0
        delete poGeom;
1347
1348
0
        return poMLS;
1349
0
    }
1350
1351
    /* -------------------------------------------------------------------- */
1352
    /*      If it is PolyhedralSurface or TIN, then pretend it is a         */
1353
    /*      multipolygon.                                                   */
1354
    /* -------------------------------------------------------------------- */
1355
0
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
1356
0
    {
1357
0
        poGeom = CPLAssertNotNull(forceToMultiPolygon(poGeom));
1358
0
        eGeomType = wkbMultiPolygon;
1359
0
    }
1360
1361
    /* -------------------------------------------------------------------- */
1362
    /*      Convert multi-polygons into a multilinestring.                  */
1363
    /* -------------------------------------------------------------------- */
1364
0
    if (eGeomType == wkbMultiPolygon || eGeomType == wkbMultiSurface)
1365
0
    {
1366
0
        OGRMultiLineString *poMLS = new OGRMultiLineString();
1367
0
        poMLS->assignSpatialReference(poGeom->getSpatialReference());
1368
1369
0
        const auto AddRingFromSrcMP = [poMLS](const OGRMultiPolygon *poSrcMP)
1370
0
        {
1371
0
            for (auto &&poPoly : poSrcMP)
1372
0
            {
1373
0
                for (auto &&poLR : poPoly)
1374
0
                {
1375
0
                    if (poLR->IsEmpty())
1376
0
                        continue;
1377
1378
0
                    OGRLineString *poNewLS = new OGRLineString();
1379
0
                    poNewLS->addSubLineString(poLR);
1380
0
                    poMLS->addGeometryDirectly(poNewLS);
1381
0
                }
1382
0
            }
1383
0
        };
1384
1385
0
        if (eGeomType == wkbMultiPolygon)
1386
0
        {
1387
0
            AddRingFromSrcMP(poGeom->toMultiPolygon());
1388
0
        }
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
0
        delete poGeom;
1397
0
        return poMLS;
1398
0
    }
1399
1400
    /* -------------------------------------------------------------------- */
1401
    /*      If it is a curve line, approximate it and wrap in a multilinestring
1402
     */
1403
    /* -------------------------------------------------------------------- */
1404
0
    if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
1405
0
    {
1406
0
        OGRMultiLineString *poMP = new OGRMultiLineString();
1407
0
        poMP->assignSpatialReference(poGeom->getSpatialReference());
1408
0
        poMP->addGeometryDirectly(poGeom->toCurve()->CurveToLine());
1409
0
        delete poGeom;
1410
0
        return poMP;
1411
0
    }
1412
1413
    /* -------------------------------------------------------------------- */
1414
    /*      If this is already a MultiCurve with compatible content,        */
1415
    /*      just cast                                                       */
1416
    /* -------------------------------------------------------------------- */
1417
0
    if (eGeomType == wkbMultiCurve &&
1418
0
        !poGeom->toMultiCurve()->hasCurveGeometry(TRUE))
1419
0
    {
1420
0
        return OGRMultiCurve::CastToMultiLineString(poGeom->toMultiCurve());
1421
0
    }
1422
1423
    /* -------------------------------------------------------------------- */
1424
    /*      If it is a multicurve, call getLinearGeometry()                */
1425
    /* -------------------------------------------------------------------- */
1426
0
    if (eGeomType == wkbMultiCurve)
1427
0
    {
1428
0
        OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
1429
0
        CPLAssert(wkbFlatten(poNewGeom->getGeometryType()) ==
1430
0
                  wkbMultiLineString);
1431
0
        delete poGeom;
1432
0
        return poNewGeom->toMultiLineString();
1433
0
    }
1434
1435
0
    return poGeom;
1436
0
}
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
0
    sPolyExtended() = default;
1584
0
    sPolyExtended(sPolyExtended &&) = default;
1585
0
    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
0
    {
1599
0
        if (poPolygonForTest)
1600
0
        {
1601
0
            return poPolygonForTest->getExteriorRingCurve()->toLinearRing();
1602
0
        }
1603
0
        else
1604
0
        {
1605
0
            const auto *poPoly =
1606
0
                dynamic_cast<const OGRPolygon *>(poCurvePolygon.get());
1607
0
            CPLAssert(poPoly);
1608
0
            return poPoly->getExteriorRingCurve()->toLinearRing();
1609
0
        }
1610
0
    }
1611
1612
    static void GetBoundsFromPolyEx(const void *hFeature, CPLRectObj *pBounds)
1613
0
    {
1614
0
        const auto *poPolyEx = static_cast<const sPolyExtended *>(hFeature);
1615
0
        pBounds->minx = poPolyEx->sEnvelope.MinX;
1616
0
        pBounds->miny = poPolyEx->sEnvelope.MinY;
1617
0
        pBounds->maxx = poPolyEx->sEnvelope.MaxX;
1618
0
        pBounds->maxy = poPolyEx->sEnvelope.MaxY;
1619
0
    }
1620
};
1621
1622
static bool OGRGeometryFactoryCompareAreaDescending(const sPolyExtended &sPoly1,
1623
                                                    const sPolyExtended &sPoly2)
1624
0
{
1625
0
    return sPoly1.dfArea > sPoly2.dfArea;
1626
0
}
1627
1628
static bool OGRGeometryFactoryCompareByIndex(const sPolyExtended &sPoly1,
1629
                                             const sPolyExtended &sPoly2)
1630
0
{
1631
0
    return sPoly1.nInitialIndex < sPoly2.nInitialIndex;
1632
0
}
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
0
{
1775
0
    if (apoPolygons.empty())
1776
0
    {
1777
0
        if (pbIsValidGeometry)
1778
0
            *pbIsValidGeometry = true;
1779
1780
0
        return std::make_unique<OGRPolygon>();
1781
0
    }
1782
1783
0
    std::unique_ptr<OGRGeometry> geom;
1784
0
    OrganizePolygonMethod method = METHOD_NORMAL;
1785
0
    bool bHasCurves = false;
1786
1787
    /* -------------------------------------------------------------------- */
1788
    /*      Trivial case of a single polygon.                               */
1789
    /* -------------------------------------------------------------------- */
1790
0
    if (apoPolygons.size() == 1)
1791
0
    {
1792
0
        OGRwkbGeometryType eType =
1793
0
            wkbFlatten(apoPolygons[0]->getGeometryType());
1794
1795
0
        bool bIsValid = true;
1796
1797
0
        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
0
        else
1806
0
        {
1807
0
            geom = std::move(apoPolygons[0]);
1808
0
        }
1809
1810
0
        if (pbIsValidGeometry)
1811
0
            *pbIsValidGeometry = bIsValid;
1812
1813
0
        return geom;
1814
0
    }
1815
1816
0
    bool bUseFastVersion = true;
1817
0
    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
0
    std::vector<sPolyExtended> asPolyEx;
1839
0
    asPolyEx.reserve(apoPolygons.size());
1840
1841
0
    bool bValidTopology = true;
1842
0
    bool bMixedUpGeometries = false;
1843
0
    bool bFoundCCW = false;
1844
1845
0
    const char *pszMethodValue = CSLFetchNameValue(papszOptions, "METHOD");
1846
0
    const char *pszMethodValueOption =
1847
0
        CPLGetConfigOption("OGR_ORGANIZE_POLYGONS", nullptr);
1848
0
    if (pszMethodValueOption != nullptr && pszMethodValueOption[0] != '\0')
1849
0
        pszMethodValue = pszMethodValueOption;
1850
1851
0
    if (pszMethodValue != nullptr)
1852
0
    {
1853
0
        if (EQUAL(pszMethodValue, "SKIP"))
1854
0
        {
1855
0
            method = METHOD_SKIP;
1856
0
            bMixedUpGeometries = true;
1857
0
        }
1858
0
        else if (EQUAL(pszMethodValue, "ONLY_CCW"))
1859
0
        {
1860
0
            method = METHOD_ONLY_CCW;
1861
0
        }
1862
0
        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
0
        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
0
    }
1873
1874
0
    size_t nCountCWPolygon = 0;
1875
0
    constexpr size_t INVALID_INDEX = static_cast<size_t>(-1);
1876
0
    size_t indexOfCWPolygon = INVALID_INDEX;
1877
0
    OGREnvelope sGlobalEnvelope;
1878
1879
0
    const auto AddRingToPolyOrCurvePoly =
1880
0
        [](OGRCurvePolygon *poDst, std::unique_ptr<OGRCurve> poRing)
1881
0
    {
1882
0
        const bool bIsCurvePoly =
1883
0
            wkbFlatten(poDst->getGeometryType()) == wkbCurvePolygon;
1884
0
        const OGRLinearRing *poLinearRing =
1885
0
            dynamic_cast<const OGRLinearRing *>(poRing.get());
1886
0
        if (bIsCurvePoly)
1887
0
        {
1888
0
            if (poLinearRing)
1889
0
                poDst->addRing(std::make_unique<OGRLineString>(*poLinearRing));
1890
0
            else
1891
0
                poDst->addRing(std::move(poRing));
1892
0
        }
1893
0
        else
1894
0
        {
1895
0
            if (poLinearRing)
1896
0
            {
1897
0
                poDst->addRing(std::move(poRing));
1898
0
            }
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
0
        }
1911
0
    };
1912
1913
0
    for (size_t i = 0; !bHasCurves && i < apoPolygons.size(); ++i)
1914
0
    {
1915
0
        const OGRwkbGeometryType eType =
1916
0
            wkbFlatten(apoPolygons[i]->getGeometryType());
1917
0
        if (eType == wkbCurvePolygon)
1918
0
            bHasCurves = true;
1919
0
    }
1920
1921
0
    bool bIncrementINextIter = true;
1922
    // Size of apoPolygons might increase during the loop
1923
0
    for (size_t i = 0; i < apoPolygons.size(); bIncrementINextIter ? ++i : 0)
1924
0
    {
1925
0
        bIncrementINextIter = true;
1926
1927
0
        const OGRwkbGeometryType eType =
1928
0
            wkbFlatten(apoPolygons[i]->getGeometryType());
1929
1930
0
        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
0
        sPolyExtended sPolyEx;
1940
1941
0
        sPolyEx.nInitialIndex = i;
1942
0
        sPolyEx.poCurvePolygon.reset(
1943
0
            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
0
        sPolyEx.poCurvePolygon->getEnvelope(&sPolyEx.sEnvelope);
2086
0
        sGlobalEnvelope.Merge(sPolyEx.sEnvelope);
2087
2088
0
        if (bUseFastVersion)
2089
0
        {
2090
0
            if (eType == wkbCurvePolygon)
2091
0
            {
2092
0
                sPolyEx.poPolygonForTest.reset(
2093
0
                    cpl::down_cast<const OGRCurvePolygon *>(
2094
0
                        sPolyEx.poCurvePolygon.get())
2095
0
                        ->CurvePolyToPoly());
2096
2097
                // Above CurvePolyToPoly() can fail on non-closed rings
2098
0
                if (sPolyEx.poPolygonForTest == nullptr ||
2099
0
                    sPolyEx.poPolygonForTest->IsEmpty())
2100
0
                {
2101
0
                    apoPolygons[i].reset();
2102
0
                    continue;
2103
0
                }
2104
0
            }
2105
0
            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
0
        }
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
0
        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
0
        if (!sPolyEx.poCurvePolygon->IsEmpty() &&
2127
0
            sPolyEx.poCurvePolygon->getNumInteriorRings() == 0 &&
2128
0
            sPolyEx.poCurvePolygon->getExteriorRingCurve()->getNumPoints() >= 4)
2129
0
        {
2130
0
            if (method != METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
2131
0
                sPolyEx.dfArea = sPolyEx.poCurvePolygon->get_Area();
2132
0
            const auto *poExteriorRing =
2133
0
                sPolyEx.poCurvePolygon->getExteriorRingCurve();
2134
0
            sPolyEx.bIsCW = poExteriorRing->isClockwise();
2135
0
            poExteriorRing->StartPoint(&sPolyEx.sPoint);
2136
0
            if (sPolyEx.bIsCW)
2137
0
            {
2138
0
                indexOfCWPolygon = i;
2139
0
                nCountCWPolygon++;
2140
0
            }
2141
0
            if (!bFoundCCW)
2142
0
                bFoundCCW = !(sPolyEx.bIsCW);
2143
0
        }
2144
0
        else
2145
0
        {
2146
0
            if (!bMixedUpGeometries)
2147
0
            {
2148
0
                CPLError(CE_Warning, CPLE_AppDefined,
2149
0
                         "organizePolygons() received an unexpected geometry.  "
2150
0
                         "Either a polygon with interior rings, or a polygon "
2151
0
                         "with less than 4 points, or a non-Polygon geometry.  "
2152
0
                         "Return arguments as a collection.");
2153
0
                bMixedUpGeometries = true;
2154
0
            }
2155
0
        }
2156
2157
0
        asPolyEx.push_back(std::move(sPolyEx));
2158
0
    }
2159
0
    if (asPolyEx.empty())
2160
0
        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
0
    if ((method == METHOD_ONLY_CCW ||
2166
0
         method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER) &&
2167
0
        nCountCWPolygon == 1 && bUseFastVersion)
2168
0
    {
2169
0
        assert(indexOfCWPolygon != INVALID_INDEX);
2170
0
        auto poCP = std::move(asPolyEx[indexOfCWPolygon].poCurvePolygon);
2171
0
        for (size_t i = 0; i < asPolyEx.size(); i++)
2172
0
        {
2173
0
            if (i != indexOfCWPolygon)
2174
0
            {
2175
0
                std::unique_ptr<OGRCurve> poRing(
2176
0
                    asPolyEx[i].poCurvePolygon->stealExteriorRingCurve());
2177
0
                AddRingToPolyOrCurvePoly(poCP.get(), std::move(poRing));
2178
0
            }
2179
0
        }
2180
2181
0
        if (pbIsValidGeometry)
2182
0
            *pbIsValidGeometry = TRUE;
2183
0
        return poCP;
2184
0
    }
2185
2186
0
    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
0
    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
0
    if (apoPolygons.size() > N_CRITICAL_PART_NUMBER &&
2257
0
        method == METHOD_NORMAL && pszMethodValue == nullptr)
2258
0
    {
2259
0
        if (bFoundCCW)
2260
0
        {
2261
0
            CPLErrorOnce(
2262
0
                CE_Warning, CPLE_AppDefined,
2263
0
                "organizePolygons() received a polygon with more than %d "
2264
0
                "parts. The processing may be really slow.  "
2265
0
                "You can skip the processing by setting METHOD=SKIP, "
2266
0
                "or only make it analyze counter-clock wise parts by "
2267
0
                "setting METHOD=ONLY_CCW if you can assume that the "
2268
0
                "outline of holes is counter-clock wise defined",
2269
0
                N_CRITICAL_PART_NUMBER);
2270
0
        }
2271
0
        else
2272
0
        {
2273
0
            CPLErrorOnce(
2274
0
                CE_Warning, CPLE_AppDefined,
2275
0
                "organizePolygons() received a polygon with more than %d "
2276
0
                "parts.  The processing may be really slow.  "
2277
0
                "You can skip the processing by setting METHOD=SKIP.",
2278
0
                N_CRITICAL_PART_NUMBER);
2279
0
        }
2280
0
    }
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
0
    if (!bMixedUpGeometries)
2314
0
    {
2315
        // STEP 1: Sort polygons by descending area.
2316
0
        std::sort(asPolyEx.begin(), asPolyEx.end(),
2317
0
                  OGRGeometryFactoryCompareAreaDescending);
2318
0
    }
2319
2320
    /* -------------------------------------------------------------------- */
2321
    /*      Build a quadtree of polygons that can be exterior rings.        */
2322
    /* -------------------------------------------------------------------- */
2323
2324
0
    CPLRectObj sRect;
2325
0
    sRect.minx = sGlobalEnvelope.MinX;
2326
0
    sRect.miny = sGlobalEnvelope.MinY;
2327
0
    sRect.maxx = sGlobalEnvelope.MaxX;
2328
0
    sRect.maxy = sGlobalEnvelope.MaxY;
2329
0
    std::unique_ptr<CPLQuadTree, decltype(&CPLQuadTreeDestroy)> poQuadTree(
2330
0
        CPLQuadTreeCreate(&sRect, sPolyExtended::GetBoundsFromPolyEx),
2331
0
        CPLQuadTreeDestroy);
2332
0
    for (auto &sPolyEx : asPolyEx)
2333
0
    {
2334
0
        if (method == METHOD_ONLY_CCW && sPolyEx.bIsCW == false)
2335
0
        {
2336
            // In that mode, we are interested only in indexing clock-wise
2337
            // polygons, which are the exterior rings
2338
0
            continue;
2339
0
        }
2340
2341
0
        CPLQuadTreeInsert(poQuadTree.get(), &sPolyEx);
2342
0
    }
2343
2344
    /* -------------------------------------------------------------------- */
2345
    /*      Compute relationships, if things seem well structured.          */
2346
    /* -------------------------------------------------------------------- */
2347
2348
    // The first (largest) polygon is necessarily top-level.
2349
0
    asPolyEx[0].bIsTopLevel = true;
2350
0
    asPolyEx[0].poEnclosingPolygon = nullptr;
2351
2352
0
    size_t nCountTopLevel = 1;
2353
2354
    // STEP 2.
2355
0
    for (size_t i = 1;
2356
0
         !bMixedUpGeometries && bValidTopology && i < asPolyEx.size(); i++)
2357
0
    {
2358
0
        auto &thisPoly = asPolyEx[i];
2359
2360
0
        if (method == METHOD_ONLY_CCW && thisPoly.bIsCW)
2361
0
        {
2362
0
            nCountTopLevel++;
2363
0
            thisPoly.bIsTopLevel = true;
2364
0
            thisPoly.poEnclosingPolygon = nullptr;
2365
0
            continue;
2366
0
        }
2367
2368
        // Look for candidate rings that intersect the current ring
2369
0
        CPLRectObj aoi;
2370
0
        aoi.minx = thisPoly.sEnvelope.MinX;
2371
0
        aoi.miny = thisPoly.sEnvelope.MinY;
2372
0
        aoi.maxx = thisPoly.sEnvelope.MaxX;
2373
0
        aoi.maxy = thisPoly.sEnvelope.MaxY;
2374
0
        int nCandidates = 0;
2375
0
        std::unique_ptr<const sPolyExtended *, decltype(&CPLFree)>
2376
0
            aphCandidateShells(
2377
0
                const_cast<const sPolyExtended **>(
2378
0
                    reinterpret_cast<sPolyExtended **>(CPLQuadTreeSearch(
2379
0
                        poQuadTree.get(), &aoi, &nCandidates))),
2380
0
                CPLFree);
2381
2382
        // Sort candidate outer rings by increasing area
2383
0
        if (nCandidates)
2384
0
        {
2385
0
            std::sort(
2386
0
                aphCandidateShells.get(),
2387
0
                aphCandidateShells.get() + nCandidates,
2388
0
                [](const sPolyExtended *psPoly1, const sPolyExtended *psPoly2)
2389
0
                { return psPoly1->dfArea < psPoly2->dfArea; });
2390
0
        }
2391
2392
0
        int j = 0;
2393
0
        for (; bValidTopology && j < nCandidates; j++)
2394
0
        {
2395
0
            const auto &otherPoly = *(aphCandidateShells.get()[j]);
2396
2397
0
            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
0
            if (otherPoly.dfArea < thisPoly.dfArea || &otherPoly == &thisPoly)
2404
0
            {
2405
0
                continue;
2406
0
            }
2407
2408
0
            bool thisInsideOther = false;
2409
0
            if (otherPoly.sEnvelope.Contains(thisPoly.sEnvelope))
2410
0
            {
2411
0
                if (bUseFastVersion)
2412
0
                {
2413
0
                    if (method == METHOD_ONLY_CCW &&
2414
0
                        (&otherPoly) == (&asPolyEx[0]))
2415
0
                    {
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
0
                        thisInsideOther = true;
2421
0
                    }
2422
0
                    else if (otherPoly.getExteriorLinearRing()
2423
0
                                 ->isPointOnRingBoundary(&thisPoly.sPoint,
2424
0
                                                         FALSE))
2425
0
                    {
2426
0
                        const OGRLinearRing *poLR_this =
2427
0
                            thisPoly.getExteriorLinearRing();
2428
0
                        const OGRLinearRing *poLR_other =
2429
0
                            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
0
                        const int nPoints = poLR_this->getNumPoints();
2434
0
                        int k = 1;  // Used after for.
2435
0
                        OGRPoint previousPoint = thisPoly.sPoint;
2436
0
                        for (; k < nPoints; k++)
2437
0
                        {
2438
0
                            OGRPoint point;
2439
0
                            poLR_this->getPoint(k, &point);
2440
0
                            if (point.getX() == previousPoint.getX() &&
2441
0
                                point.getY() == previousPoint.getY())
2442
0
                            {
2443
0
                                continue;
2444
0
                            }
2445
0
                            if (poLR_other->isPointOnRingBoundary(&point,
2446
0
                                                                  FALSE))
2447
0
                            {
2448
                                // If it is on the boundary of other, iterate again.
2449
0
                            }
2450
0
                            else if (poLR_other->isPointInRing(&point, FALSE))
2451
0
                            {
2452
                                // If then point is strictly included in other, then
2453
                                // this is considered inside other.
2454
0
                                thisInsideOther = true;
2455
0
                                break;
2456
0
                            }
2457
0
                            else
2458
0
                            {
2459
                                // If it is outside, then this cannot be inside other.
2460
0
                                break;
2461
0
                            }
2462
0
                            previousPoint = std::move(point);
2463
0
                        }
2464
0
                        if (!thisInsideOther && k == nPoints && nPoints > 2)
2465
0
                        {
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
0
                            poLR_this->getPoint(0, &previousPoint);
2470
0
                            for (k = 1; k < nPoints; k++)
2471
0
                            {
2472
0
                                OGRPoint point;
2473
0
                                poLR_this->getPoint(k, &point);
2474
0
                                if (point.getX() == previousPoint.getX() &&
2475
0
                                    point.getY() == previousPoint.getY())
2476
0
                                {
2477
0
                                    continue;
2478
0
                                }
2479
0
                                OGRPoint pointMiddle;
2480
0
                                pointMiddle.setX(
2481
0
                                    (point.getX() + previousPoint.getX()) / 2);
2482
0
                                pointMiddle.setY(
2483
0
                                    (point.getY() + previousPoint.getY()) / 2);
2484
0
                                if (poLR_other->isPointOnRingBoundary(
2485
0
                                        &pointMiddle, FALSE))
2486
0
                                {
2487
                                    // If it is on the boundary of other, iterate
2488
                                    // again.
2489
0
                                }
2490
0
                                else if (poLR_other->isPointInRing(&pointMiddle,
2491
0
                                                                   FALSE))
2492
0
                                {
2493
                                    // If then point is strictly included in other,
2494
                                    // then this is considered inside other.
2495
0
                                    thisInsideOther = true;
2496
0
                                    break;
2497
0
                                }
2498
0
                                else
2499
0
                                {
2500
                                    // If it is outside, then this cannot be inside
2501
                                    // other.
2502
0
                                    break;
2503
0
                                }
2504
0
                                previousPoint = std::move(point);
2505
0
                            }
2506
0
                        }
2507
0
                    }
2508
                    // Note that isPointInRing only test strict inclusion in the
2509
                    // ring.
2510
0
                    else if (otherPoly.getExteriorLinearRing()->isPointInRing(
2511
0
                                 &thisPoly.sPoint, FALSE))
2512
0
                    {
2513
0
                        thisInsideOther = true;
2514
0
                    }
2515
0
                }
2516
0
                else if (otherPoly.poCurvePolygon->Contains(
2517
0
                             thisPoly.poCurvePolygon.get()))
2518
0
                {
2519
0
                    thisInsideOther = true;
2520
0
                }
2521
0
            }
2522
2523
0
            if (thisInsideOther)
2524
0
            {
2525
0
                if (otherPoly.bIsTopLevel)
2526
0
                {
2527
                    // We are a lake.
2528
0
                    thisPoly.bIsTopLevel = false;
2529
0
                    thisPoly.poEnclosingPolygon =
2530
0
                        otherPoly.poCurvePolygon.get();
2531
0
                }
2532
0
                else
2533
0
                {
2534
                    // We are included in a something not toplevel (a lake),
2535
                    // so in OGCSF we are considered as toplevel too.
2536
0
                    nCountTopLevel++;
2537
0
                    thisPoly.bIsTopLevel = true;
2538
0
                    thisPoly.poEnclosingPolygon = nullptr;
2539
0
                }
2540
0
                break;
2541
0
            }
2542
            // Use Overlaps instead of Intersects to be more
2543
            // tolerant about touching polygons.
2544
0
            else if (bUseFastVersion || !thisPoly.poCurvePolygon->Overlaps(
2545
0
                                            otherPoly.poCurvePolygon.get()))
2546
0
            {
2547
0
            }
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
0
#ifdef DEBUG
2556
0
                char *wkt1 = nullptr;
2557
0
                char *wkt2 = nullptr;
2558
0
                thisPoly.poCurvePolygon->exportToWkt(&wkt1);
2559
0
                otherPoly.poCurvePolygon->exportToWkt(&wkt2);
2560
0
                const int realJ = static_cast<int>(&otherPoly - &asPolyEx[0]);
2561
0
                CPLDebug("OGR",
2562
0
                         "Bad intersection for polygons %d and %d\n"
2563
0
                         "geom %d: %s\n"
2564
0
                         "geom %d: %s",
2565
0
                         static_cast<int>(i), realJ, static_cast<int>(i), wkt1,
2566
0
                         realJ, wkt2);
2567
0
                CPLFree(wkt1);
2568
0
                CPLFree(wkt2);
2569
0
#endif
2570
0
            }
2571
0
        }
2572
2573
0
        if (j == nCandidates)
2574
0
        {
2575
            // We come here because we are not included in anything.
2576
            // We are toplevel.
2577
0
            nCountTopLevel++;
2578
0
            thisPoly.bIsTopLevel = true;
2579
0
            thisPoly.poEnclosingPolygon = nullptr;
2580
0
        }
2581
0
    }
2582
2583
0
    if (pbIsValidGeometry)
2584
0
        *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
0
    if (!bValidTopology || bMixedUpGeometries)
2591
0
    {
2592
0
        for (auto &sPolyEx : asPolyEx)
2593
0
        {
2594
0
            sPolyEx.bIsTopLevel = true;
2595
0
        }
2596
0
        nCountTopLevel = asPolyEx.size();
2597
0
    }
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
0
    std::sort(asPolyEx.begin(), asPolyEx.end(),
2605
0
              OGRGeometryFactoryCompareByIndex);
2606
2607
    // STEP 4: Add holes as rings of their enclosing polygon.
2608
0
    for (auto &sPolyEx : asPolyEx)
2609
0
    {
2610
0
        if (!sPolyEx.bIsTopLevel)
2611
0
        {
2612
0
            AddRingToPolyOrCurvePoly(
2613
0
                sPolyEx.poEnclosingPolygon,
2614
0
                std::unique_ptr<OGRCurve>(
2615
0
                    sPolyEx.poCurvePolygon->stealExteriorRingCurve()));
2616
0
            sPolyEx.poCurvePolygon.reset();
2617
0
        }
2618
0
        else if (nCountTopLevel == 1)
2619
0
        {
2620
0
            geom = std::move(sPolyEx.poCurvePolygon);
2621
0
        }
2622
0
    }
2623
2624
    // STEP 5: Add toplevel polygons.
2625
0
    if (nCountTopLevel > 1)
2626
0
    {
2627
0
        std::unique_ptr<OGRMultiSurface> poMS;
2628
0
        if (bHasCurves)
2629
0
            poMS = std::make_unique<OGRMultiSurface>();
2630
0
        else
2631
0
            poMS = std::make_unique<OGRMultiPolygon>();
2632
0
        for (auto &sPolyEx : asPolyEx)
2633
0
        {
2634
0
            if (sPolyEx.bIsTopLevel)
2635
0
            {
2636
0
                poMS->addGeometry(std::move(sPolyEx.poCurvePolygon));
2637
0
            }
2638
0
        }
2639
0
        geom = std::move(poMS);
2640
0
    }
2641
2642
0
    return geom;
2643
0
}
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
0
{
2764
0
#ifndef HAVE_GEOS
2765
0
    return false;
2766
#else
2767
    return true;
2768
#endif
2769
0
}
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
0
{
2806
0
    return createFromFgfInternal(static_cast<const GByte *>(pabyData), poSR,
2807
0
                                 ppoReturn, nBytes, pnBytesConsumed, 0);
2808
0
}
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
0
{
2818
    // Arbitrary value, but certainly large enough for reasonable usages.
2819
0
    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
0
    *ppoReturn = nullptr;
2828
2829
0
    if (nBytes < 4)
2830
0
        return OGRERR_NOT_ENOUGH_DATA;
2831
2832
    /* -------------------------------------------------------------------- */
2833
    /*      Decode the geometry type.                                       */
2834
    /* -------------------------------------------------------------------- */
2835
0
    GInt32 nGType = 0;
2836
0
    memcpy(&nGType, pabyData + 0, 4);
2837
0
    CPL_LSBPTR32(&nGType);
2838
2839
0
    if (nGType < 0 || nGType > 13)
2840
0
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
2841
2842
    /* -------------------------------------------------------------------- */
2843
    /*      Decode the dimensionality if appropriate.                       */
2844
    /* -------------------------------------------------------------------- */
2845
0
    int nTupleSize = 0;
2846
0
    GInt32 nGDim = 0;
2847
2848
    // TODO: Why is this a switch?
2849
0
    switch (nGType)
2850
0
    {
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
0
        default:
2872
0
            break;
2873
0
    }
2874
2875
0
    OGRGeometry *poGeom = nullptr;
2876
2877
    /* -------------------------------------------------------------------- */
2878
    /*      None                                                            */
2879
    /* -------------------------------------------------------------------- */
2880
0
    if (nGType == 0)
2881
0
    {
2882
0
        if (pnBytesConsumed)
2883
0
            *pnBytesConsumed = 4;
2884
0
    }
2885
2886
    /* -------------------------------------------------------------------- */
2887
    /*      Point                                                           */
2888
    /* -------------------------------------------------------------------- */
2889
0
    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
0
    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
0
    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
0
    else if (nGType == 4      // MultiPoint
3031
0
             || nGType == 5   // MultiLineString
3032
0
             || nGType == 6   // MultiPolygon
3033
0
             || nGType == 7)  // MultiGeometry
3034
0
    {
3035
0
        if (nBytes < 8)
3036
0
            return OGRERR_NOT_ENOUGH_DATA;
3037
3038
0
        GInt32 nGeomCount = 0;
3039
0
        memcpy(&nGeomCount, pabyData + 4, 4);
3040
0
        CPL_LSBPTR32(&nGeomCount);
3041
3042
0
        if (nGeomCount < 0 || nGeomCount > INT_MAX / 4)
3043
0
            return OGRERR_CORRUPT_DATA;
3044
3045
        // Each geometry takes at least 4 bytes.
3046
0
        if (nBytes - 8 < 4 * nGeomCount)
3047
0
            return OGRERR_NOT_ENOUGH_DATA;
3048
3049
0
        OGRGeometryCollection *poGC = nullptr;
3050
0
        if (nGType == 4)
3051
0
            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
0
        int nBytesUsed = 8;
3060
3061
0
        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
0
        poGeom = poGC;
3089
0
        if (pnBytesConsumed)
3090
0
            *pnBytesConsumed = nBytesUsed;
3091
0
    }
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
0
    if (poGeom != nullptr && poSR)
3107
0
        poGeom->assignSpatialReference(poSR);
3108
0
    *ppoReturn = poGeom;
3109
3110
0
    return OGRERR_NONE;
3111
0
}
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
0
    : d(new Private())
4377
0
{
4378
0
}
4379
4380
/************************************************************************/
4381
/*                     ~TransformWithOptionsCache()                     */
4382
/************************************************************************/
4383
4384
OGRGeometryFactory::TransformWithOptionsCache::~TransformWithOptionsCache()
4385
0
{
4386
0
}
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
0
{
4486
0
    auto poDstGeom = std::unique_ptr<OGRGeometry>(poSrcGeom->clone());
4487
0
    if (poCT)
4488
0
    {
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
0
        OGRErr eErr = poDstGeom->transform(poCT);
4538
0
        if (eErr != OGRERR_NONE)
4539
0
        {
4540
0
            return nullptr;
4541
0
        }
4542
#ifdef HAVE_GEOS
4543
        if (bNeedPostCorrection)
4544
        {
4545
            SnapCoordsCloseToLatLongBounds(poDstGeom.get());
4546
        }
4547
#endif
4548
0
    }
4549
4550
0
    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
0
    return poDstGeom.release();
4623
0
}
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
0
{
4735
0
    const double dfVal = CPLAtofM(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4"));
4736
0
    constexpr double MIN_VAL = 1e-2;
4737
0
    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
0
    constexpr double MAX_VAL = 180;
4745
0
    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
0
    return dfVal;
4753
0
}
4754
4755
/************************************************************************/
4756
/*                              DISTANCE()                              */
4757
/************************************************************************/
4758
4759
static inline double DISTANCE(double x1, double y1, double x2, double y2)
4760
0
{
4761
0
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
4762
0
}
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
0
{
4808
0
    OGRLineString *poLine = new OGRLineString();
4809
0
    const double dfRotationRadians = dfRotation * M_PI / 180.0;
4810
4811
    // Support default arc step setting.
4812
0
    if (dfMaxAngleStepSizeDegrees < 1e-6)
4813
0
    {
4814
0
        dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
4815
0
    }
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
0
    const double dfMaxInterpolationGap =
4822
0
        bUseMaxGap ? CPLAtofM(CPLGetConfigOption("OGR_ARC_MAX_GAP", "0")) : 0.0;
4823
4824
    // Is this a full circle?
4825
0
    const bool bIsFullCircle = fabs(dfEndAngle - dfStartAngle) == 360.0;
4826
4827
    // Switch direction.
4828
0
    dfStartAngle *= -1;
4829
0
    dfEndAngle *= -1;
4830
4831
    // Figure out the number of slices to make this into.
4832
0
    int nVertexCount =
4833
0
        std::max(2, static_cast<int>(ceil(fabs(dfEndAngle - dfStartAngle) /
4834
0
                                          dfMaxAngleStepSizeDegrees) +
4835
0
                                     1));
4836
0
    const double dfSlice = (dfEndAngle - dfStartAngle) / (nVertexCount - 1);
4837
4838
    // If it is a full circle we will work out the last point separately.
4839
0
    if (bIsFullCircle)
4840
0
    {
4841
0
        nVertexCount--;
4842
0
    }
4843
4844
    /* -------------------------------------------------------------------- */
4845
    /*      Compute the interpolated points.                                */
4846
    /* -------------------------------------------------------------------- */
4847
0
    double dfLastX = 0.0;
4848
0
    double dfLastY = 0.0;
4849
0
    int nTotalAddPoints = 0;
4850
0
    for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
4851
0
    {
4852
0
        const double dfAngleOnEllipse =
4853
0
            (dfStartAngle + iPoint * dfSlice) * M_PI / 180.0;
4854
4855
        // Compute position on the unrotated ellipse.
4856
0
        const double dfEllipseX = cos(dfAngleOnEllipse) * dfPrimaryRadius;
4857
0
        const double dfEllipseY = sin(dfAngleOnEllipse) * dfSecondaryRadius;
4858
4859
        // Is this point too far from the previous point?
4860
0
        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
0
        poLine->setPoint(iPoint + nTotalAddPoints, dfEllipseX, dfEllipseY, dfZ);
4890
0
        dfLastX = dfEllipseX;
4891
0
        dfLastY = dfEllipseY;
4892
0
    }
4893
4894
    /* -------------------------------------------------------------------- */
4895
    /*      Rotate and translate the ellipse.                               */
4896
    /* -------------------------------------------------------------------- */
4897
0
    nVertexCount = poLine->getNumPoints();
4898
0
    for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
4899
0
    {
4900
0
        const double dfEllipseX = poLine->getX(iPoint);
4901
0
        const double dfEllipseY = poLine->getY(iPoint);
4902
4903
        // Rotate this position around the center of the ellipse.
4904
0
        const double dfArcX = dfCenterX + dfEllipseX * cos(dfRotationRadians) +
4905
0
                              dfEllipseY * sin(dfRotationRadians);
4906
0
        const double dfArcY = dfCenterY - dfEllipseX * sin(dfRotationRadians) +
4907
0
                              dfEllipseY * cos(dfRotationRadians);
4908
4909
0
        poLine->setPoint(iPoint, dfArcX, dfArcY, dfZ);
4910
0
    }
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
0
    if (bIsFullCircle)
4917
0
    {
4918
0
        OGRPoint oPoint;
4919
0
        poLine->getPoint(0, &oPoint);
4920
0
        poLine->setPoint(nVertexCount, &oPoint);
4921
0
    }
4922
4923
0
    return poLine;
4924
0
}
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
0
{
4997
0
    if (poGeom == nullptr)
4998
0
        return nullptr;
4999
5000
0
    const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
5001
5002
    /* -------------------------------------------------------------------- */
5003
    /*      If this is already a LineString, nothing to do                  */
5004
    /* -------------------------------------------------------------------- */
5005
0
    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
0
    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
0
    if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
5032
0
    {
5033
0
        OGRGeometry *poNewGeom = poGeom->toCurve()->CurveToLine();
5034
0
        delete poGeom;
5035
0
        return poNewGeom;
5036
0
    }
5037
5038
0
    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
0
    OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
5044
0
    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
0
    if (poGC->getNumGeometries() == 0)
5053
0
    {
5054
0
        poGeom = new OGRLineString();
5055
0
        poGeom->assignSpatialReference(poGC->getSpatialReference());
5056
0
        delete poGC;
5057
0
        return poGeom;
5058
0
    }
5059
5060
0
    int iGeom0 = 0;
5061
0
    while (iGeom0 < poGC->getNumGeometries())
5062
0
    {
5063
0
        if (wkbFlatten(poGC->getGeometryRef(iGeom0)->getGeometryType()) !=
5064
0
            wkbLineString)
5065
0
        {
5066
0
            iGeom0++;
5067
0
            continue;
5068
0
        }
5069
5070
0
        OGRLineString *poLineString0 =
5071
0
            poGC->getGeometryRef(iGeom0)->toLineString();
5072
0
        if (poLineString0->getNumPoints() < 2)
5073
0
        {
5074
0
            iGeom0++;
5075
0
            continue;
5076
0
        }
5077
5078
0
        OGRPoint pointStart0;
5079
0
        poLineString0->StartPoint(&pointStart0);
5080
0
        OGRPoint pointEnd0;
5081
0
        poLineString0->EndPoint(&pointEnd0);
5082
5083
0
        int iGeom1 = iGeom0 + 1;  // Used after for.
5084
0
        for (; iGeom1 < poGC->getNumGeometries(); iGeom1++)
5085
0
        {
5086
0
            if (wkbFlatten(poGC->getGeometryRef(iGeom1)->getGeometryType()) !=
5087
0
                wkbLineString)
5088
0
                continue;
5089
5090
0
            OGRLineString *poLineString1 =
5091
0
                poGC->getGeometryRef(iGeom1)->toLineString();
5092
0
            if (poLineString1->getNumPoints() < 2)
5093
0
                continue;
5094
5095
0
            OGRPoint pointStart1;
5096
0
            poLineString1->StartPoint(&pointStart1);
5097
0
            OGRPoint pointEnd1;
5098
0
            poLineString1->EndPoint(&pointEnd1);
5099
5100
0
            if (!bOnlyInOrder && (pointEnd0.Equals(&pointEnd1) ||
5101
0
                                  pointStart0.Equals(&pointStart1)))
5102
0
            {
5103
0
                poLineString1->reversePoints();
5104
0
                poLineString1->StartPoint(&pointStart1);
5105
0
                poLineString1->EndPoint(&pointEnd1);
5106
0
            }
5107
5108
0
            if (pointEnd0.Equals(&pointStart1))
5109
0
            {
5110
0
                poLineString0->addSubLineString(poLineString1, 1);
5111
0
                poGC->removeGeometry(iGeom1);
5112
0
                break;
5113
0
            }
5114
5115
0
            if (pointEnd1.Equals(&pointStart0))
5116
0
            {
5117
0
                poLineString1->addSubLineString(poLineString0, 1);
5118
0
                poGC->removeGeometry(iGeom0);
5119
0
                break;
5120
0
            }
5121
0
        }
5122
5123
0
        if (iGeom1 == poGC->getNumGeometries())
5124
0
        {
5125
0
            iGeom0++;
5126
0
        }
5127
0
    }
5128
5129
0
    if (poGC->getNumGeometries() == 1)
5130
0
    {
5131
0
        OGRGeometry *poSingleGeom = poGC->getGeometryRef(0);
5132
0
        poGC->removeGeometry(0, FALSE);
5133
0
        delete poGC;
5134
5135
0
        return poSingleGeom;
5136
0
    }
5137
5138
0
    return poGC;
5139
0
}
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
0
{
5233
0
    if (poGeom == nullptr)
5234
0
        return poGeom;
5235
5236
0
    const OGRwkbGeometryType eTargetTypeFlat = wkbFlatten(eTargetType);
5237
0
    if (eTargetTypeFlat == wkbUnknown)
5238
0
        return poGeom;
5239
5240
0
    if (poGeom->IsEmpty())
5241
0
    {
5242
0
        auto poRet = std::unique_ptr<OGRGeometry>(createGeometry(eTargetType));
5243
0
        if (poRet)
5244
0
        {
5245
0
            poRet->assignSpatialReference(poGeom->getSpatialReference());
5246
0
            poRet->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
5247
0
            poRet->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
5248
0
        }
5249
0
        return poRet;
5250
0
    }
5251
5252
0
    OGRwkbGeometryType eType = poGeom->getGeometryType();
5253
0
    OGRwkbGeometryType eTypeFlat = wkbFlatten(eType);
5254
5255
0
    if (eTargetTypeFlat != eTargetType && (eType == eTypeFlat))
5256
0
    {
5257
0
        auto poGeomNew =
5258
0
            forceTo(std::move(poGeom), eTargetTypeFlat, papszOptions);
5259
0
        if (poGeomNew)
5260
0
        {
5261
0
            poGeomNew->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
5262
0
            poGeomNew->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
5263
0
        }
5264
0
        return poGeomNew;
5265
0
    }
5266
5267
0
    if (eTypeFlat == eTargetTypeFlat)
5268
0
    {
5269
0
        poGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
5270
0
        poGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
5271
0
        return poGeom;
5272
0
    }
5273
5274
0
    eType = eTypeFlat;
5275
5276
0
    if (OGR_GT_IsSubClassOf(eType, wkbPolyhedralSurface) &&
5277
0
        (eTargetTypeFlat == wkbMultiSurface ||
5278
0
         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
0
    if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
5290
0
        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
0
    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
0
    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
0
    else if (eType == wkbMultiPolygon &&
5319
0
             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
0
    else if (eType == wkbTIN && eTargetTypeFlat == wkbPolyhedralSurface)
5332
0
    {
5333
0
        poGeom.reset(OGRTriangulatedSurface::CastToPolyhedralSurface(
5334
0
            poGeom.release()->toTriangulatedSurface()));
5335
0
    }
5336
0
    else if (eType == wkbCurvePolygon &&
5337
0
             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
0
    else if (eType == wkbMultiSurface &&
5348
0
             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
0
    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
0
    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
0
    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
0
    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
0
    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
0
    if (eTargetTypeFlat == wkbTriangle || eTargetTypeFlat == wkbTIN ||
5454
0
        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
0
    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
0
    if (!OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
5480
0
        OGR_GT_IsSubClassOf(OGR_GT_GetCollection(eType), eTargetType))
5481
0
    {
5482
0
        auto poRet = std::unique_ptr<OGRGeometry>(createGeometry(eTargetType));
5483
0
        if (poRet == nullptr)
5484
0
        {
5485
0
            return nullptr;
5486
0
        }
5487
0
        poRet->assignSpatialReference(poGeom->getSpatialReference());
5488
0
        if (eType == wkbLineString)
5489
0
            poGeom.reset(
5490
0
                OGRCurve::CastToLineString(poGeom.release()->toCurve()));
5491
0
        poRet->toGeometryCollection()->addGeometry(std::move(poGeom));
5492
0
        poRet->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
5493
0
        poRet->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
5494
0
        return poRet;
5495
0
    }
5496
5497
0
    const bool bIsCurve = CPL_TO_BOOL(OGR_GT_IsCurve(eType));
5498
0
    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
0
    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
0
    else if (eType == wkbLineString &&
5525
0
             OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface))
5526
0
    {
5527
0
        auto poTmp = forceTo(std::move(poGeom), wkbPolygon, papszOptions);
5528
0
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
5529
0
            return forceTo(std::move(poTmp), eTargetType, papszOptions);
5530
0
        poGeom = std::move(poTmp);
5531
0
    }
5532
0
    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
0
    else if (bIsCurve && eTargetTypeFlat == wkbMultiPolygon)
5540
0
    {
5541
0
        auto poTmp = forceTo(std::move(poGeom), wkbPolygon, papszOptions);
5542
0
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
5543
0
            return forceTo(std::move(poTmp), eTargetType, papszOptions);
5544
0
        poGeom = std::move(poTmp);
5545
0
    }
5546
0
    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
0
    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
0
    else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
5564
0
             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
0
    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
0
    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
0
    else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
5595
0
    {
5596
0
        const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
5597
0
        if (poGC->getNumGeometries() == 1)
5598
0
        {
5599
0
            const OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
5600
0
            if (poSubGeom)
5601
0
            {
5602
0
                auto poSubGeomClone =
5603
0
                    std::unique_ptr<OGRGeometry>(poSubGeom->clone());
5604
0
                poSubGeomClone->assignSpatialReference(
5605
0
                    poGeom->getSpatialReference());
5606
0
                auto poRet = forceTo(std::move(poSubGeomClone), eTargetType,
5607
0
                                     papszOptions);
5608
0
                if (poRet &&
5609
0
                    OGR_GT_IsSubClassOf(wkbFlatten(poRet->getGeometryType()),
5610
0
                                        eTargetType))
5611
0
                {
5612
0
                    return poRet;
5613
0
                }
5614
0
            }
5615
0
        }
5616
0
    }
5617
0
    else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
5618
0
             (OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface) ||
5619
0
              OGR_GT_IsSubClassOf(eTargetType, wkbMultiCurve)))
5620
0
    {
5621
0
        const OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
5622
0
        if (poCP->getNumInteriorRings() == 0)
5623
0
        {
5624
0
            const OGRCurve *poRing = poCP->getExteriorRingCurve();
5625
0
            auto poRingClone = std::unique_ptr<OGRGeometry>(poRing->clone());
5626
0
            poRingClone->assignSpatialReference(poGeom->getSpatialReference());
5627
0
            const OGRwkbGeometryType eRingType = poRing->getGeometryType();
5628
0
            auto poRet =
5629
0
                forceTo(std::move(poRingClone), eTargetType, papszOptions);
5630
0
            if (poRet->getGeometryType() != eRingType &&
5631
0
                !(eTypeFlat == wkbPolygon &&
5632
0
                  eTargetTypeFlat == wkbMultiLineString))
5633
0
            {
5634
0
                return poRet;
5635
0
            }
5636
0
        }
5637
0
    }
5638
5639
0
    if (eTargetTypeFlat == wkbLineString)
5640
0
    {
5641
0
        auto poNewGeom =
5642
0
            std::unique_ptr<OGRGeometry>(forceToLineString(poGeom.release()));
5643
0
        poNewGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
5644
0
        poNewGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
5645
0
        poGeom = std::move(poNewGeom);
5646
0
    }
5647
0
    else if (eTargetTypeFlat == wkbPolygon)
5648
0
    {
5649
0
        auto poNewGeom =
5650
0
            std::unique_ptr<OGRGeometry>(forceToPolygon(poGeom.release()));
5651
0
        if (poNewGeom)
5652
0
        {
5653
0
            poNewGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
5654
0
            poNewGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
5655
0
        }
5656
0
        poGeom = std::move(poNewGeom);
5657
0
    }
5658
0
    else if (eTargetTypeFlat == wkbMultiPolygon)
5659
0
    {
5660
0
        auto poNewGeom =
5661
0
            std::unique_ptr<OGRGeometry>(forceToMultiPolygon(poGeom.release()));
5662
0
        if (poNewGeom)
5663
0
        {
5664
0
            poNewGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
5665
0
            poNewGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
5666
0
        }
5667
0
        poGeom = std::move(poNewGeom);
5668
0
    }
5669
0
    else if (eTargetTypeFlat == wkbMultiLineString)
5670
0
    {
5671
0
        auto poNewGeom = std::unique_ptr<OGRGeometry>(
5672
0
            forceToMultiLineString(poGeom.release()));
5673
0
        poNewGeom->set3D(CPL_TO_BOOL(OGR_GT_HasZ(eTargetType)));
5674
0
        poNewGeom->setMeasured(CPL_TO_BOOL(OGR_GT_HasM(eTargetType)));
5675
0
        poGeom = std::move(poNewGeom);
5676
0
    }
5677
0
    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
0
    return poGeom;
5687
0
}
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
0
{
5709
0
    return OGRGeometry::ToHandle(
5710
0
        OGRGeometryFactory::forceTo(
5711
0
            std::unique_ptr<OGRGeometry>(OGRGeometry::FromHandle(hGeom)),
5712
0
            eTargetType, papszOptions)
5713
0
            .release());
5714
0
}
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
0
{
5815
0
    if (std::isnan(x0) || std::isnan(y0) || std::isnan(x1) || std::isnan(y1) ||
5816
0
        std::isnan(x2) || std::isnan(y2))
5817
0
    {
5818
0
        return FALSE;
5819
0
    }
5820
5821
    // Circle.
5822
0
    if (x0 == x2 && y0 == y2)
5823
0
    {
5824
0
        if (x0 != x1 || y0 != y1)
5825
0
        {
5826
0
            cx = (x0 + x1) / 2;
5827
0
            cy = (y0 + y1) / 2;
5828
0
            R = DISTANCE(cx, cy, x0, y0);
5829
            // Arbitrarily pick counter-clock-wise order (like PostGIS does).
5830
0
            alpha0 = atan2(y0 - cy, x0 - cx);
5831
0
            alpha1 = alpha0 + M_PI;
5832
0
            alpha2 = alpha0 + 2 * M_PI;
5833
0
            return TRUE;
5834
0
        }
5835
0
        else
5836
0
        {
5837
0
            return FALSE;
5838
0
        }
5839
0
    }
5840
5841
0
    double dx01 = x1 - x0;
5842
0
    double dy01 = y1 - y0;
5843
0
    double dx12 = x2 - x1;
5844
0
    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
0
    double dfScale = fabs(dx01);
5849
0
    if (fabs(dy01) > dfScale)
5850
0
        dfScale = fabs(dy01);
5851
0
    if (fabs(dx12) > dfScale)
5852
0
        dfScale = fabs(dx12);
5853
0
    if (fabs(dy12) > dfScale)
5854
0
        dfScale = fabs(dy12);
5855
0
    const double dfInvScale = 1.0 / dfScale;
5856
0
    dx01 *= dfInvScale;
5857
0
    dy01 *= dfInvScale;
5858
0
    dx12 *= dfInvScale;
5859
0
    dy12 *= dfInvScale;
5860
5861
0
    const double det = dx01 * dy12 - dx12 * dy01;
5862
0
    if (fabs(det) < 1.0e-8 || std::isnan(det))
5863
0
    {
5864
0
        return FALSE;
5865
0
    }
5866
0
    const double x01_mid = (x0 + x1) * dfInvScale;
5867
0
    const double x12_mid = (x1 + x2) * dfInvScale;
5868
0
    const double y01_mid = (y0 + y1) * dfInvScale;
5869
0
    const double y12_mid = (y1 + y2) * dfInvScale;
5870
0
    const double c01 = dx01 * x01_mid + dy01 * y01_mid;
5871
0
    const double c12 = dx12 * x12_mid + dy12 * y12_mid;
5872
0
    cx = 0.5 * dfScale * (c01 * dy12 - c12 * dy01) / det;
5873
0
    cy = 0.5 * dfScale * (-c01 * dx12 + c12 * dx01) / det;
5874
5875
0
    alpha0 = atan2((y0 - cy) * dfInvScale, (x0 - cx) * dfInvScale);
5876
0
    alpha1 = atan2((y1 - cy) * dfInvScale, (x1 - cx) * dfInvScale);
5877
0
    alpha2 = atan2((y2 - cy) * dfInvScale, (x2 - cx) * dfInvScale);
5878
0
    R = DISTANCE(cx, cy, x0, y0);
5879
5880
    // If det is negative, the orientation if clockwise.
5881
0
    if (det < 0)
5882
0
    {
5883
0
        if (alpha1 > alpha0)
5884
0
            alpha1 -= 2 * M_PI;
5885
0
        if (alpha2 > alpha1)
5886
0
            alpha2 -= 2 * M_PI;
5887
0
    }
5888
0
    else
5889
0
    {
5890
0
        if (alpha1 < alpha0)
5891
0
            alpha1 += 2 * M_PI;
5892
0
        if (alpha2 < alpha1)
5893
0
            alpha2 += 2 * M_PI;
5894
0
    }
5895
5896
0
    CPLAssert((alpha0 <= alpha1 && alpha1 <= alpha2) ||
5897
0
              (alpha0 >= alpha1 && alpha1 >= alpha2));
5898
5899
0
    return TRUE;
5900
0
}
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
0
{
5912
0
    const int nSign = dfStep > 0 ? 1 : -1;
5913
5914
    // Constant angle between all points, so as to not depend on winding order.
5915
0
    const double dfNumSteps = fabs((alpha1 - alpha0) / dfStep) + 0.5;
5916
0
    if (dfNumSteps >= std::numeric_limits<int>::max() ||
5917
0
        dfNumSteps <= std::numeric_limits<int>::min() || std::isnan(dfNumSteps))
5918
0
    {
5919
0
        CPLError(CE_Warning, CPLE_AppDefined,
5920
0
                 "OGRGeometryFactoryStrokeArc: bogus steps: "
5921
0
                 "%lf %lf %lf %lf",
5922
0
                 alpha0, alpha1, dfStep, dfNumSteps);
5923
0
        return;
5924
0
    }
5925
5926
0
    int nSteps = static_cast<int>(dfNumSteps);
5927
0
    if (bStealthConstraints)
5928
0
    {
5929
        // We need at least 6 intermediate vertex, and if more additional
5930
        // multiples of 2.
5931
0
        if (nSteps < 1 + 6)
5932
0
            nSteps = 1 + 6;
5933
0
        else
5934
0
            nSteps = 1 + 6 + 2 * ((nSteps - (1 + 6) + (2 - 1)) / 2);
5935
0
    }
5936
0
    else if (nSteps < 4)
5937
0
    {
5938
0
        nSteps = 4;
5939
0
    }
5940
0
    dfStep = nSign * fabs((alpha1 - alpha0) / nSteps);
5941
0
    double alpha = alpha0 + dfStep;
5942
5943
0
    for (; (alpha - alpha1) * nSign < -1e-8; alpha += dfStep)
5944
0
    {
5945
0
        const double dfX = cx + R * cos(alpha);
5946
0
        const double dfY = cy + R * sin(alpha);
5947
0
        if (bHasZ)
5948
0
        {
5949
0
            const double z =
5950
0
                z0 + (z1 - z0) * (alpha - alpha0) / (alpha1 - alpha0);
5951
0
            poLine->addPoint(dfX, dfY, z);
5952
0
        }
5953
0
        else
5954
0
        {
5955
0
            poLine->addPoint(dfX, dfY);
5956
0
        }
5957
0
    }
5958
0
}
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
0
{
5981
0
    GByte abyData[8] = {};
5982
5983
0
    memcpy(abyData, &dfX, sizeof(double));
5984
0
    abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue & 0xFF);
5985
0
    memcpy(&dfX, abyData, sizeof(double));
5986
5987
0
    memcpy(abyData, &dfY, sizeof(double));
5988
0
    abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue >> 8);
5989
0
    memcpy(&dfY, abyData, sizeof(double));
5990
0
}
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
0
{
6016
0
    return x0 < x2 || (x0 == x2 && y0 < y2);
6017
0
}
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
0
{
6076
    // So as to make sure the same curve followed in both direction results
6077
    // in perfectly(=binary identical) symmetrical points.
6078
0
    if (OGRGF_NeedSwithArcOrder(x0, y0, x2, y2))
6079
0
    {
6080
0
        OGRLineString *poLS =
6081
0
            curveToLineString(x2, y2, z2, x1, y1, z1, x0, y0, z0, bHasZ,
6082
0
                              dfMaxAngleStepSizeDegrees, papszOptions);
6083
0
        poLS->reversePoints();
6084
0
        return poLS;
6085
0
    }
6086
6087
0
    double R = 0.0;
6088
0
    double cx = 0.0;
6089
0
    double cy = 0.0;
6090
0
    double alpha0 = 0.0;
6091
0
    double alpha1 = 0.0;
6092
0
    double alpha2 = 0.0;
6093
6094
0
    OGRLineString *poLine = new OGRLineString();
6095
0
    bool bIsArc = true;
6096
0
    if (!GetCurveParameters(x0, y0, x1, y1, x2, y2, R, cx, cy, alpha0, alpha1,
6097
0
                            alpha2))
6098
0
    {
6099
0
        bIsArc = false;
6100
0
        cx = 0.0;
6101
0
        cy = 0.0;
6102
0
        R = 0.0;
6103
0
        alpha0 = 0.0;
6104
0
        alpha1 = 0.0;
6105
0
        alpha2 = 0.0;
6106
0
    }
6107
6108
0
    const int nSign = alpha1 >= alpha0 ? 1 : -1;
6109
6110
    // support default arc step setting.
6111
0
    if (dfMaxAngleStepSizeDegrees < 1e-6)
6112
0
    {
6113
0
        dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
6114
0
    }
6115
6116
0
    double dfStep = dfMaxAngleStepSizeDegrees / 180 * M_PI;
6117
0
    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
0
    dfStep *= nSign;
6124
6125
0
    if (bHasZ)
6126
0
        poLine->addPoint(x0, y0, z0);
6127
0
    else
6128
0
        poLine->addPoint(x0, y0);
6129
6130
0
    bool bAddIntermediatePoint = false;
6131
0
    bool bStealth = true;
6132
0
    for (const char *const *papszIter = papszOptions; papszIter && *papszIter;
6133
0
         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
0
    if (!bIsArc || bAddIntermediatePoint)
6165
0
    {
6166
0
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z1, bHasZ, alpha0,
6167
0
                                    alpha1, dfStep, FALSE);
6168
6169
0
        if (bHasZ)
6170
0
            poLine->addPoint(x1, y1, z1);
6171
0
        else
6172
0
            poLine->addPoint(x1, y1);
6173
6174
0
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z1, z2, bHasZ, alpha1,
6175
0
                                    alpha2, dfStep, FALSE);
6176
0
    }
6177
0
    else
6178
0
    {
6179
0
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z2, bHasZ, alpha0,
6180
0
                                    alpha2, dfStep, bStealth);
6181
6182
0
        if (bStealth && poLine->getNumPoints() > 6)
6183
0
        {
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
0
            const double dfRatio = (alpha1 - alpha0) / (alpha2 - alpha0);
6191
0
            double dfAlphaRatio = 0.5 + HIDDEN_ALPHA_SCALE * dfRatio;
6192
0
            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
0
            else if (dfAlphaRatio >= std::numeric_limits<GUInt32>::max() ||
6199
0
                     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
0
            const GUInt32 nAlphaRatio = static_cast<GUInt32>(dfAlphaRatio);
6206
0
            const GUInt16 nAlphaRatioLow = nAlphaRatio & HIDDEN_ALPHA_HALF_MASK;
6207
0
            const GUInt16 nAlphaRatioHigh =
6208
0
                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
0
            CPLAssert(((poLine->getNumPoints() - 1 - 6) % 2) == 0);
6214
6215
0
            for (int i = 1; i + 1 < poLine->getNumPoints(); i += 2)
6216
0
            {
6217
0
                GUInt16 nVal = 0xFFFF;
6218
6219
0
                double dfX = poLine->getX(i);
6220
0
                double dfY = poLine->getY(i);
6221
0
                if (i == 1)
6222
0
                    nVal = nAlphaRatioLow;
6223
0
                else if (i == poLine->getNumPoints() - 2)
6224
0
                    nVal = nAlphaRatioHigh;
6225
0
                OGRGF_SetHiddenValue(nVal, dfX, dfY);
6226
0
                poLine->setPoint(i, dfX, dfY);
6227
6228
0
                dfX = poLine->getX(i + 1);
6229
0
                dfY = poLine->getY(i + 1);
6230
0
                if (i == 1)
6231
0
                    nVal = nAlphaRatioHigh;
6232
0
                else if (i == poLine->getNumPoints() - 2)
6233
0
                    nVal = nAlphaRatioLow;
6234
0
                OGRGF_SetHiddenValue(nVal, dfX, dfY);
6235
0
                poLine->setPoint(i + 1, dfX, dfY);
6236
0
            }
6237
0
        }
6238
0
    }
6239
6240
0
    if (bHasZ)
6241
0
        poLine->addPoint(x2, y2, z2);
6242
0
    else
6243
0
        poLine->addPoint(x2, y2);
6244
6245
0
    return poLine;
6246
0
}
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
}