Coverage Report

Created: 2026-02-14 06:52

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