Coverage Report

Created: 2026-04-10 07:04

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