Coverage Report

Created: 2025-11-16 06:25

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
2.95k
{
344
2.95k
    const char *pszInput = *ppszData;
345
2.95k
    *ppoReturn = nullptr;
346
347
    /* -------------------------------------------------------------------- */
348
    /*      Get the first token, which should be the geometry type.         */
349
    /* -------------------------------------------------------------------- */
350
2.95k
    char szToken[OGR_WKT_TOKEN_MAX] = {};
351
2.95k
    if (OGRWktReadToken(pszInput, szToken) == nullptr)
352
0
        return OGRERR_CORRUPT_DATA;
353
354
    /* -------------------------------------------------------------------- */
355
    /*      Instantiate a geometry of the appropriate type.                 */
356
    /* -------------------------------------------------------------------- */
357
2.95k
    OGRGeometry *poGeom = nullptr;
358
2.95k
    if (STARTS_WITH_CI(szToken, "POINT"))
359
16
    {
360
16
        poGeom = new OGRPoint();
361
16
    }
362
2.94k
    else if (STARTS_WITH_CI(szToken, "LINESTRING"))
363
433
    {
364
433
        poGeom = new OGRLineString();
365
433
    }
366
2.50k
    else if (STARTS_WITH_CI(szToken, "POLYGON"))
367
637
    {
368
637
        poGeom = new OGRPolygon();
369
637
    }
370
1.87k
    else if (STARTS_WITH_CI(szToken, "TRIANGLE"))
371
1
    {
372
1
        poGeom = new OGRTriangle();
373
1
    }
374
1.86k
    else if (STARTS_WITH_CI(szToken, "GEOMETRYCOLLECTION"))
375
50
    {
376
50
        poGeom = new OGRGeometryCollection();
377
50
    }
378
1.81k
    else if (STARTS_WITH_CI(szToken, "MULTIPOLYGON"))
379
526
    {
380
526
        poGeom = new OGRMultiPolygon();
381
526
    }
382
1.29k
    else if (STARTS_WITH_CI(szToken, "MULTIPOINT"))
383
650
    {
384
650
        poGeom = new OGRMultiPoint();
385
650
    }
386
643
    else if (STARTS_WITH_CI(szToken, "MULTILINESTRING"))
387
98
    {
388
98
        poGeom = new OGRMultiLineString();
389
98
    }
390
545
    else if (STARTS_WITH_CI(szToken, "CIRCULARSTRING"))
391
11
    {
392
11
        poGeom = new OGRCircularString();
393
11
    }
394
534
    else if (STARTS_WITH_CI(szToken, "COMPOUNDCURVE"))
395
48
    {
396
48
        poGeom = new OGRCompoundCurve();
397
48
    }
398
486
    else if (STARTS_WITH_CI(szToken, "CURVEPOLYGON"))
399
107
    {
400
107
        poGeom = new OGRCurvePolygon();
401
107
    }
402
379
    else if (STARTS_WITH_CI(szToken, "MULTICURVE"))
403
154
    {
404
154
        poGeom = new OGRMultiCurve();
405
154
    }
406
225
    else if (STARTS_WITH_CI(szToken, "MULTISURFACE"))
407
2
    {
408
2
        poGeom = new OGRMultiSurface();
409
2
    }
410
411
223
    else if (STARTS_WITH_CI(szToken, "POLYHEDRALSURFACE"))
412
93
    {
413
93
        poGeom = new OGRPolyhedralSurface();
414
93
    }
415
416
130
    else if (STARTS_WITH_CI(szToken, "TIN"))
417
2
    {
418
2
        poGeom = new OGRTriangulatedSurface();
419
2
    }
420
421
128
    else
422
128
    {
423
128
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
424
128
    }
425
426
    /* -------------------------------------------------------------------- */
427
    /*      Do the import.                                                  */
428
    /* -------------------------------------------------------------------- */
429
2.82k
    const OGRErr eErr = poGeom->importFromWkt(&pszInput);
430
431
    /* -------------------------------------------------------------------- */
432
    /*      Assign spatial reference system.                                */
433
    /* -------------------------------------------------------------------- */
434
2.82k
    if (eErr == OGRERR_NONE)
435
2.24k
    {
436
2.24k
        if (poGeom->hasCurveGeometry() &&
437
14
            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.24k
        poGeom->assignSpatialReference(poSR);
444
2.24k
        *ppoReturn = poGeom;
445
2.24k
        *ppszData = pszInput;
446
2.24k
    }
447
586
    else
448
586
    {
449
586
        delete poGeom;
450
586
    }
451
452
2.82k
    return eErr;
453
2.95k
}
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.20k
{
543
2.20k
    return OGRGeometryFactory::createFromWkt(
544
2.20k
        const_cast<const char **>(ppszData),
545
2.20k
        OGRSpatialReference::FromHandle(hSRS),
546
2.20k
        reinterpret_cast<OGRGeometry **>(phGeometry));
547
2.20k
}
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
156
{
608
156
    OGRGeometry *poGeom = nullptr;
609
156
    switch (wkbFlatten(eGeometryType))
610
156
    {
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
156
        case wkbPolygon:
620
156
            poGeom = new (std::nothrow) OGRPolygon();
621
156
            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
156
    }
682
156
    if (poGeom)
683
156
    {
684
156
        if (OGR_GT_HasZ(eGeometryType))
685
0
            poGeom->set3D(true);
686
156
        if (OGR_GT_HasM(eGeometryType))
687
0
            poGeom->setMeasured(true);
688
156
    }
689
156
    return poGeom;
690
156
}
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.53k
{
757
1.53k
    delete OGRGeometry::FromHandle(hGeom);
758
1.53k
}
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
    OGRCurvePolygon *poPolygon = nullptr;
1537
    OGREnvelope sEnvelope{};
1538
    OGRPoint sPoint{};
1539
    int 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
1638
OGRGeometry *OGRGeometryFactory::organizePolygons(OGRGeometry **papoPolygons,
1639
                                                  int nPolygonCount,
1640
                                                  int *pbIsValidGeometry,
1641
                                                  const char **papszOptions)
1642
0
{
1643
0
    if (nPolygonCount == 0)
1644
0
    {
1645
0
        if (pbIsValidGeometry)
1646
0
            *pbIsValidGeometry = TRUE;
1647
1648
0
        return new OGRPolygon();
1649
0
    }
1650
1651
0
    OGRGeometry *geom = nullptr;
1652
0
    OrganizePolygonMethod method = METHOD_NORMAL;
1653
0
    bool bHasCurves = false;
1654
1655
    /* -------------------------------------------------------------------- */
1656
    /*      Trivial case of a single polygon.                               */
1657
    /* -------------------------------------------------------------------- */
1658
0
    if (nPolygonCount == 1)
1659
0
    {
1660
0
        OGRwkbGeometryType eType =
1661
0
            wkbFlatten(papoPolygons[0]->getGeometryType());
1662
1663
0
        bool bIsValid = true;
1664
1665
0
        if (eType != wkbPolygon && eType != wkbCurvePolygon)
1666
0
        {
1667
0
            CPLError(CE_Warning, CPLE_AppDefined,
1668
0
                     "organizePolygons() received a non-Polygon geometry.");
1669
0
            bIsValid = false;
1670
0
            delete papoPolygons[0];
1671
0
            geom = new OGRPolygon();
1672
0
        }
1673
0
        else
1674
0
        {
1675
0
            geom = papoPolygons[0];
1676
0
        }
1677
1678
0
        papoPolygons[0] = nullptr;
1679
1680
0
        if (pbIsValidGeometry)
1681
0
            *pbIsValidGeometry = bIsValid;
1682
1683
0
        return geom;
1684
0
    }
1685
1686
0
    bool bUseFastVersion = true;
1687
0
    if (CPLTestBool(CPLGetConfigOption("OGR_DEBUG_ORGANIZE_POLYGONS", "NO")))
1688
0
    {
1689
        /* ------------------------------------------------------------------ */
1690
        /*      A wee bit of a warning.                                       */
1691
        /* ------------------------------------------------------------------ */
1692
0
        static int firstTime = 1;
1693
        // cppcheck-suppress knownConditionTrueFalse
1694
0
        if (!haveGEOS() && firstTime)
1695
0
        {
1696
0
            CPLDebug(
1697
0
                "OGR",
1698
0
                "In OGR_DEBUG_ORGANIZE_POLYGONS mode, GDAL should be built "
1699
0
                "with GEOS support enabled in order "
1700
0
                "OGRGeometryFactory::organizePolygons to provide reliable "
1701
0
                "results on complex polygons.");
1702
0
            firstTime = 0;
1703
0
        }
1704
        // cppcheck-suppress knownConditionTrueFalse
1705
0
        bUseFastVersion = !haveGEOS();
1706
0
    }
1707
1708
    /* -------------------------------------------------------------------- */
1709
    /*      Setup per polygon envelope and area information.                */
1710
    /* -------------------------------------------------------------------- */
1711
0
    std::vector<sPolyExtended> asPolyEx;
1712
0
    asPolyEx.reserve(nPolygonCount);
1713
1714
0
    bool bValidTopology = true;
1715
0
    bool bMixedUpGeometries = false;
1716
0
    bool bFoundCCW = false;
1717
1718
0
    const char *pszMethodValue = CSLFetchNameValue(papszOptions, "METHOD");
1719
0
    const char *pszMethodValueOption =
1720
0
        CPLGetConfigOption("OGR_ORGANIZE_POLYGONS", nullptr);
1721
0
    if (pszMethodValueOption != nullptr && pszMethodValueOption[0] != '\0')
1722
0
        pszMethodValue = pszMethodValueOption;
1723
1724
0
    if (pszMethodValue != nullptr)
1725
0
    {
1726
0
        if (EQUAL(pszMethodValue, "SKIP"))
1727
0
        {
1728
0
            method = METHOD_SKIP;
1729
0
            bMixedUpGeometries = true;
1730
0
        }
1731
0
        else if (EQUAL(pszMethodValue, "ONLY_CCW"))
1732
0
        {
1733
0
            method = METHOD_ONLY_CCW;
1734
0
        }
1735
0
        else if (EQUAL(pszMethodValue, "CCW_INNER_JUST_AFTER_CW_OUTER"))
1736
0
        {
1737
0
            method = METHOD_CCW_INNER_JUST_AFTER_CW_OUTER;
1738
0
        }
1739
0
        else if (!EQUAL(pszMethodValue, "DEFAULT"))
1740
0
        {
1741
0
            CPLError(CE_Warning, CPLE_AppDefined,
1742
0
                     "Unrecognized value for METHOD option : %s",
1743
0
                     pszMethodValue);
1744
0
        }
1745
0
    }
1746
1747
0
    int nCountCWPolygon = 0;
1748
0
    int indexOfCWPolygon = -1;
1749
0
    OGREnvelope sGlobalEnvelope;
1750
1751
0
    for (int i = 0; i < nPolygonCount; i++)
1752
0
    {
1753
0
        const OGRwkbGeometryType eType =
1754
0
            wkbFlatten(papoPolygons[i]->getGeometryType());
1755
1756
0
        if (eType != wkbPolygon && eType != wkbCurvePolygon)
1757
0
        {
1758
            // Ignore any points or lines that find their way in here.
1759
0
            CPLError(CE_Warning, CPLE_AppDefined,
1760
0
                     "organizePolygons() received a non-Polygon geometry.");
1761
0
            delete papoPolygons[i];
1762
0
            continue;
1763
0
        }
1764
1765
0
        sPolyExtended sPolyEx;
1766
1767
0
        sPolyEx.nInitialIndex = i;
1768
0
        sPolyEx.poPolygon = papoPolygons[i]->toCurvePolygon();
1769
1770
0
        papoPolygons[i]->getEnvelope(&sPolyEx.sEnvelope);
1771
0
        sGlobalEnvelope.Merge(sPolyEx.sEnvelope);
1772
1773
0
        if (eType == wkbCurvePolygon)
1774
0
            bHasCurves = true;
1775
0
        if (!sPolyEx.poPolygon->IsEmpty() &&
1776
0
            sPolyEx.poPolygon->getNumInteriorRings() == 0 &&
1777
0
            sPolyEx.poPolygon->getExteriorRingCurve()->getNumPoints() >= 4)
1778
0
        {
1779
0
            if (method != METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
1780
0
                sPolyEx.dfArea = sPolyEx.poPolygon->get_Area();
1781
0
            auto *poExteriorRing = sPolyEx.poPolygon->getExteriorRingCurve();
1782
0
            poExteriorRing->StartPoint(&sPolyEx.sPoint);
1783
0
            if (eType == wkbPolygon)
1784
0
            {
1785
0
                sPolyEx.bIsCW =
1786
0
                    CPL_TO_BOOL(poExteriorRing->toLinearRing()->isClockwise());
1787
0
                sPolyEx.bIsPolygon = true;
1788
0
            }
1789
0
            else
1790
0
            {
1791
0
                OGRLineString *poLS = poExteriorRing->CurveToLine();
1792
0
                OGRLinearRing oLR;
1793
0
                oLR.addSubLineString(poLS);
1794
0
                sPolyEx.bIsCW = CPL_TO_BOOL(oLR.isClockwise());
1795
0
                sPolyEx.bIsPolygon = false;
1796
0
                delete poLS;
1797
0
            }
1798
0
            if (sPolyEx.bIsCW)
1799
0
            {
1800
0
                indexOfCWPolygon = i;
1801
0
                nCountCWPolygon++;
1802
0
            }
1803
0
            if (!bFoundCCW)
1804
0
                bFoundCCW = !(sPolyEx.bIsCW);
1805
0
        }
1806
0
        else
1807
0
        {
1808
0
            if (!bMixedUpGeometries)
1809
0
            {
1810
0
                CPLError(CE_Warning, CPLE_AppDefined,
1811
0
                         "organizePolygons() received an unexpected geometry.  "
1812
0
                         "Either a polygon with interior rings, or a polygon "
1813
0
                         "with less than 4 points, or a non-Polygon geometry.  "
1814
0
                         "Return arguments as a collection.");
1815
0
                bMixedUpGeometries = true;
1816
0
            }
1817
0
        }
1818
1819
0
        asPolyEx.push_back(std::move(sPolyEx));
1820
0
    }
1821
1822
    // If we are in ONLY_CCW mode and that we have found that there is only one
1823
    // outer ring, then it is pretty easy : we can assume that all other rings
1824
    // are inside.
1825
0
    if ((method == METHOD_ONLY_CCW ||
1826
0
         method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER) &&
1827
0
        nCountCWPolygon == 1 && bUseFastVersion)
1828
0
    {
1829
0
        OGRCurvePolygon *poCP = asPolyEx[indexOfCWPolygon].poPolygon;
1830
0
        for (int i = 0; i < static_cast<int>(asPolyEx.size()); i++)
1831
0
        {
1832
0
            if (i != indexOfCWPolygon)
1833
0
            {
1834
0
                poCP->addRingDirectly(
1835
0
                    asPolyEx[i].poPolygon->stealExteriorRingCurve());
1836
0
                delete asPolyEx[i].poPolygon;
1837
0
            }
1838
0
        }
1839
1840
0
        if (pbIsValidGeometry)
1841
0
            *pbIsValidGeometry = TRUE;
1842
0
        return poCP;
1843
0
    }
1844
1845
0
    if (method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER && asPolyEx[0].bIsCW)
1846
0
    {
1847
        // Inner rings are CCW oriented and follow immediately the outer
1848
        // ring (that is CW oriented) in which they are included.
1849
0
        OGRMultiSurface *poMulti = nullptr;
1850
0
        OGRCurvePolygon *poCur = asPolyEx[0].poPolygon;
1851
0
        OGRGeometry *poRet = poCur;
1852
        // We have already checked that the first ring is CW.
1853
0
        OGREnvelope *psEnvelope = &(asPolyEx[0].sEnvelope);
1854
0
        for (std::size_t i = 1; i < asPolyEx.size(); i++)
1855
0
        {
1856
0
            if (asPolyEx[i].bIsCW)
1857
0
            {
1858
0
                if (poMulti == nullptr)
1859
0
                {
1860
0
                    if (bHasCurves)
1861
0
                        poMulti = new OGRMultiSurface();
1862
0
                    else
1863
0
                        poMulti = new OGRMultiPolygon();
1864
0
                    poRet = poMulti;
1865
0
                    poMulti->addGeometryDirectly(poCur);
1866
0
                }
1867
0
                poCur = asPolyEx[i].poPolygon;
1868
0
                poMulti->addGeometryDirectly(poCur);
1869
0
                psEnvelope = &(asPolyEx[i].sEnvelope);
1870
0
            }
1871
0
            else
1872
0
            {
1873
0
                poCur->addRingDirectly(
1874
0
                    asPolyEx[i].poPolygon->stealExteriorRingCurve());
1875
0
                if (!(asPolyEx[i].sPoint.getX() >= psEnvelope->MinX &&
1876
0
                      asPolyEx[i].sPoint.getX() <= psEnvelope->MaxX &&
1877
0
                      asPolyEx[i].sPoint.getY() >= psEnvelope->MinY &&
1878
0
                      asPolyEx[i].sPoint.getY() <= psEnvelope->MaxY))
1879
0
                {
1880
0
                    CPLError(CE_Warning, CPLE_AppDefined,
1881
0
                             "Part %d does not respect "
1882
0
                             "CCW_INNER_JUST_AFTER_CW_OUTER rule",
1883
0
                             static_cast<int>(i));
1884
0
                }
1885
0
                delete asPolyEx[i].poPolygon;
1886
0
            }
1887
0
        }
1888
1889
0
        if (pbIsValidGeometry)
1890
0
            *pbIsValidGeometry = TRUE;
1891
0
        return poRet;
1892
0
    }
1893
0
    else if (method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
1894
0
    {
1895
0
        method = METHOD_ONLY_CCW;
1896
0
        for (std::size_t i = 0; i < asPolyEx.size(); i++)
1897
0
            asPolyEx[i].dfArea = asPolyEx[i].poPolygon->get_Area();
1898
0
    }
1899
1900
    // Emits a warning if the number of parts is sufficiently big to anticipate
1901
    // for very long computation time, and the user didn't specify an explicit
1902
    // method.
1903
0
    if (nPolygonCount > N_CRITICAL_PART_NUMBER && method == METHOD_NORMAL &&
1904
0
        pszMethodValue == nullptr)
1905
0
    {
1906
0
        static int firstTime = 1;
1907
0
        if (firstTime)
1908
0
        {
1909
0
            if (bFoundCCW)
1910
0
            {
1911
0
                CPLError(
1912
0
                    CE_Warning, CPLE_AppDefined,
1913
0
                    "organizePolygons() received a polygon with more than %d "
1914
0
                    "parts. The processing may be really slow.  "
1915
0
                    "You can skip the processing by setting METHOD=SKIP, "
1916
0
                    "or only make it analyze counter-clock wise parts by "
1917
0
                    "setting METHOD=ONLY_CCW if you can assume that the "
1918
0
                    "outline of holes is counter-clock wise defined",
1919
0
                    N_CRITICAL_PART_NUMBER);
1920
0
            }
1921
0
            else
1922
0
            {
1923
0
                CPLError(
1924
0
                    CE_Warning, CPLE_AppDefined,
1925
0
                    "organizePolygons() received a polygon with more than %d "
1926
0
                    "parts.  The processing may be really slow.  "
1927
0
                    "You can skip the processing by setting METHOD=SKIP.",
1928
0
                    N_CRITICAL_PART_NUMBER);
1929
0
            }
1930
0
            firstTime = 0;
1931
0
        }
1932
0
    }
1933
1934
    /* This a nulti-step algorithm :
1935
       1) Sort polygons by descending areas
1936
       2) For each polygon of rank i, find its smallest enclosing polygon
1937
          among the polygons of rank [i-1 ... 0]. If there are no such polygon,
1938
          this is a top-level polygon. Otherwise, depending on if the enclosing
1939
          polygon is top-level or not, we can decide if we are top-level or not
1940
       3) Re-sort the polygons to retrieve their initial order (nicer for
1941
          some applications)
1942
       4) For each non top-level polygon (= inner ring), add it to its
1943
          outer ring
1944
       5) Add the top-level polygons to the multipolygon
1945
1946
       Complexity : O(nPolygonCount^2)
1947
    */
1948
1949
    /* Compute how each polygon relate to the other ones
1950
       To save a bit of computation we always begin the computation by a test
1951
       on the envelope. We also take into account the areas to avoid some
1952
       useless tests.  (A contains B implies envelop(A) contains envelop(B)
1953
       and area(A) > area(B)) In practice, we can hope that few full geometry
1954
       intersection of inclusion test is done:
1955
       * if the polygons are well separated geographically (a set of islands
1956
       for example), no full geometry intersection or inclusion test is done.
1957
       (the envelopes don't intersect each other)
1958
1959
       * if the polygons are 'lake inside an island inside a lake inside an
1960
       area' and that each polygon is much smaller than its enclosing one,
1961
       their bounding boxes are strictly contained into each other, and thus,
1962
       no full geometry intersection or inclusion test is done
1963
    */
1964
1965
0
    if (!bMixedUpGeometries)
1966
0
    {
1967
        // STEP 1: Sort polygons by descending area.
1968
0
        std::sort(asPolyEx.begin(), asPolyEx.end(),
1969
0
                  OGRGeometryFactoryCompareAreaDescending);
1970
0
    }
1971
0
    papoPolygons = nullptr;  // Just to use to avoid it afterwards.
1972
1973
    /* -------------------------------------------------------------------- */
1974
    /*      Build a quadtree of polygons that can be exterior rings.        */
1975
    /* -------------------------------------------------------------------- */
1976
1977
0
    CPLRectObj sRect;
1978
0
    sRect.minx = sGlobalEnvelope.MinX;
1979
0
    sRect.miny = sGlobalEnvelope.MinY;
1980
0
    sRect.maxx = sGlobalEnvelope.MaxX;
1981
0
    sRect.maxy = sGlobalEnvelope.MaxY;
1982
0
    std::unique_ptr<CPLQuadTree, decltype(&CPLQuadTreeDestroy)> poQuadTree(
1983
0
        CPLQuadTreeCreate(&sRect, sPolyExtended::GetBoundsFromPolyEx),
1984
0
        CPLQuadTreeDestroy);
1985
0
    for (auto &sPolyEx : asPolyEx)
1986
0
    {
1987
0
        if (method == METHOD_ONLY_CCW && sPolyEx.bIsCW == false)
1988
0
        {
1989
            // In that mode, we are interested only in indexing clock-wise
1990
            // polygons, which are the exterior rings
1991
0
            continue;
1992
0
        }
1993
1994
0
        CPLQuadTreeInsert(poQuadTree.get(), &sPolyEx);
1995
0
    }
1996
1997
    /* -------------------------------------------------------------------- */
1998
    /*      Compute relationships, if things seem well structured.          */
1999
    /* -------------------------------------------------------------------- */
2000
2001
    // The first (largest) polygon is necessarily top-level.
2002
0
    asPolyEx[0].bIsTopLevel = true;
2003
0
    asPolyEx[0].poEnclosingPolygon = nullptr;
2004
2005
0
    int nCountTopLevel = 1;
2006
2007
    // STEP 2.
2008
0
    for (int i = 1; !bMixedUpGeometries && bValidTopology &&
2009
0
                    i < static_cast<int>(asPolyEx.size());
2010
0
         i++)
2011
0
    {
2012
0
        auto &thisPoly = asPolyEx[i];
2013
2014
0
        if (method == METHOD_ONLY_CCW && thisPoly.bIsCW)
2015
0
        {
2016
0
            nCountTopLevel++;
2017
0
            thisPoly.bIsTopLevel = true;
2018
0
            thisPoly.poEnclosingPolygon = nullptr;
2019
0
            continue;
2020
0
        }
2021
2022
        // Look for candidate rings that intersect the current ring
2023
0
        CPLRectObj aoi;
2024
0
        aoi.minx = thisPoly.sEnvelope.MinX;
2025
0
        aoi.miny = thisPoly.sEnvelope.MinY;
2026
0
        aoi.maxx = thisPoly.sEnvelope.MaxX;
2027
0
        aoi.maxy = thisPoly.sEnvelope.MaxY;
2028
0
        int nCandidates = 0;
2029
0
        std::unique_ptr<void *, decltype(&CPLFree)> aphCandidateShells(
2030
0
            CPLQuadTreeSearch(poQuadTree.get(), &aoi, &nCandidates), CPLFree);
2031
2032
        // Sort candidate outer rings by increasing area
2033
0
        std::sort(
2034
0
            aphCandidateShells.get(), aphCandidateShells.get() + nCandidates,
2035
0
            [](void *hFeature1, void *hFeature2)
2036
0
            {
2037
0
                const auto &sPoly1 = *(static_cast<sPolyExtended *>(hFeature1));
2038
0
                const auto &sPoly2 = *(static_cast<sPolyExtended *>(hFeature2));
2039
0
                return sPoly1.dfArea < sPoly2.dfArea;
2040
0
            });
2041
2042
0
        int j = 0;
2043
0
        for (; bValidTopology && j < nCandidates; j++)
2044
0
        {
2045
0
            const auto &otherPoly = *static_cast<const sPolyExtended *>(
2046
0
                (aphCandidateShells.get())[j]);
2047
2048
0
            if (method == METHOD_ONLY_CCW && otherPoly.bIsCW == false)
2049
0
            {
2050
                // In that mode, this which is CCW if we reach here can only be
2051
                // included in a CW polygon.
2052
0
                continue;
2053
0
            }
2054
0
            if (otherPoly.dfArea < thisPoly.dfArea || &otherPoly == &thisPoly)
2055
0
            {
2056
0
                continue;
2057
0
            }
2058
2059
0
            bool thisInsideOther = false;
2060
0
            if (otherPoly.sEnvelope.Contains(thisPoly.sEnvelope))
2061
0
            {
2062
0
                if (bUseFastVersion)
2063
0
                {
2064
0
                    if (method == METHOD_ONLY_CCW &&
2065
0
                        (&otherPoly) == (&asPolyEx[0]))
2066
0
                    {
2067
                        // We are testing if a CCW ring is in the biggest CW
2068
                        // ring. It *must* be inside as this is the last
2069
                        // candidate, otherwise the winding order rules is
2070
                        // broken.
2071
0
                        thisInsideOther = true;
2072
0
                    }
2073
0
                    else if (thisPoly.bIsPolygon && otherPoly.bIsPolygon &&
2074
0
                             otherPoly.getExteriorLinearRing()
2075
0
                                 ->isPointOnRingBoundary(&thisPoly.sPoint,
2076
0
                                                         FALSE))
2077
0
                    {
2078
0
                        const OGRLinearRing *poLR_this =
2079
0
                            thisPoly.getExteriorLinearRing();
2080
0
                        const OGRLinearRing *poLR_other =
2081
0
                            otherPoly.getExteriorLinearRing();
2082
2083
                        // If the point of i is on the boundary of other, we will
2084
                        // iterate over the other points of this.
2085
0
                        const int nPoints = poLR_this->getNumPoints();
2086
0
                        int k = 1;  // Used after for.
2087
0
                        OGRPoint previousPoint = thisPoly.sPoint;
2088
0
                        for (; k < nPoints; k++)
2089
0
                        {
2090
0
                            OGRPoint point;
2091
0
                            poLR_this->getPoint(k, &point);
2092
0
                            if (point.getX() == previousPoint.getX() &&
2093
0
                                point.getY() == previousPoint.getY())
2094
0
                            {
2095
0
                                continue;
2096
0
                            }
2097
0
                            if (poLR_other->isPointOnRingBoundary(&point,
2098
0
                                                                  FALSE))
2099
0
                            {
2100
                                // If it is on the boundary of other, iterate again.
2101
0
                            }
2102
0
                            else if (poLR_other->isPointInRing(&point, FALSE))
2103
0
                            {
2104
                                // If then point is strictly included in other, then
2105
                                // this is considered inside other.
2106
0
                                thisInsideOther = true;
2107
0
                                break;
2108
0
                            }
2109
0
                            else
2110
0
                            {
2111
                                // If it is outside, then this cannot be inside other.
2112
0
                                break;
2113
0
                            }
2114
0
                            previousPoint = std::move(point);
2115
0
                        }
2116
0
                        if (!thisInsideOther && k == nPoints && nPoints > 2)
2117
0
                        {
2118
                            // All points of this are on the boundary of other.
2119
                            // Take a point in the middle of a segment of this and
2120
                            // test it against other.
2121
0
                            poLR_this->getPoint(0, &previousPoint);
2122
0
                            for (k = 1; k < nPoints; k++)
2123
0
                            {
2124
0
                                OGRPoint point;
2125
0
                                poLR_this->getPoint(k, &point);
2126
0
                                if (point.getX() == previousPoint.getX() &&
2127
0
                                    point.getY() == previousPoint.getY())
2128
0
                                {
2129
0
                                    continue;
2130
0
                                }
2131
0
                                OGRPoint pointMiddle;
2132
0
                                pointMiddle.setX(
2133
0
                                    (point.getX() + previousPoint.getX()) / 2);
2134
0
                                pointMiddle.setY(
2135
0
                                    (point.getY() + previousPoint.getY()) / 2);
2136
0
                                if (poLR_other->isPointOnRingBoundary(
2137
0
                                        &pointMiddle, FALSE))
2138
0
                                {
2139
                                    // If it is on the boundary of other, iterate
2140
                                    // again.
2141
0
                                }
2142
0
                                else if (poLR_other->isPointInRing(&pointMiddle,
2143
0
                                                                   FALSE))
2144
0
                                {
2145
                                    // If then point is strictly included in other,
2146
                                    // then this is considered inside other.
2147
0
                                    thisInsideOther = true;
2148
0
                                    break;
2149
0
                                }
2150
0
                                else
2151
0
                                {
2152
                                    // If it is outside, then this cannot be inside
2153
                                    // other.
2154
0
                                    break;
2155
0
                                }
2156
0
                                previousPoint = std::move(point);
2157
0
                            }
2158
0
                        }
2159
0
                    }
2160
                    // Note that isPointInRing only test strict inclusion in the
2161
                    // ring.
2162
0
                    else if (thisPoly.bIsPolygon && otherPoly.bIsPolygon &&
2163
0
                             otherPoly.getExteriorLinearRing()->isPointInRing(
2164
0
                                 &thisPoly.sPoint, FALSE))
2165
0
                    {
2166
0
                        thisInsideOther = true;
2167
0
                    }
2168
0
                }
2169
0
                else if (otherPoly.poPolygon->Contains(thisPoly.poPolygon))
2170
0
                {
2171
0
                    thisInsideOther = true;
2172
0
                }
2173
0
            }
2174
2175
0
            if (thisInsideOther)
2176
0
            {
2177
0
                if (otherPoly.bIsTopLevel)
2178
0
                {
2179
                    // We are a lake.
2180
0
                    thisPoly.bIsTopLevel = false;
2181
0
                    thisPoly.poEnclosingPolygon = otherPoly.poPolygon;
2182
0
                }
2183
0
                else
2184
0
                {
2185
                    // We are included in a something not toplevel (a lake),
2186
                    // so in OGCSF we are considered as toplevel too.
2187
0
                    nCountTopLevel++;
2188
0
                    thisPoly.bIsTopLevel = true;
2189
0
                    thisPoly.poEnclosingPolygon = nullptr;
2190
0
                }
2191
0
                break;
2192
0
            }
2193
            // Use Overlaps instead of Intersects to be more
2194
            // tolerant about touching polygons.
2195
0
            else if (bUseFastVersion ||
2196
0
                     !thisPoly.poPolygon->Overlaps(otherPoly.poPolygon))
2197
0
            {
2198
0
            }
2199
0
            else
2200
0
            {
2201
                // Bad... The polygons are intersecting but no one is
2202
                // contained inside the other one. This is a really broken
2203
                // case. We just make a multipolygon with the whole set of
2204
                // polygons.
2205
0
                bValidTopology = false;
2206
0
#ifdef DEBUG
2207
0
                char *wkt1 = nullptr;
2208
0
                char *wkt2 = nullptr;
2209
0
                thisPoly.poPolygon->exportToWkt(&wkt1);
2210
0
                otherPoly.poPolygon->exportToWkt(&wkt2);
2211
0
                const int realJ = static_cast<int>(&otherPoly - &asPolyEx[0]);
2212
0
                CPLDebug("OGR",
2213
0
                         "Bad intersection for polygons %d and %d\n"
2214
0
                         "geom %d: %s\n"
2215
0
                         "geom %d: %s",
2216
0
                         static_cast<int>(i), realJ, static_cast<int>(i), wkt1,
2217
0
                         realJ, wkt2);
2218
0
                CPLFree(wkt1);
2219
0
                CPLFree(wkt2);
2220
0
#endif
2221
0
            }
2222
0
        }
2223
2224
0
        if (j == nCandidates)
2225
0
        {
2226
            // We come here because we are not included in anything.
2227
            // We are toplevel.
2228
0
            nCountTopLevel++;
2229
0
            thisPoly.bIsTopLevel = true;
2230
0
            thisPoly.poEnclosingPolygon = nullptr;
2231
0
        }
2232
0
    }
2233
2234
0
    if (pbIsValidGeometry)
2235
0
        *pbIsValidGeometry = bValidTopology && !bMixedUpGeometries;
2236
2237
    /* --------------------------------------------------------------------- */
2238
    /*      Things broke down - just mark everything as top-level so it gets */
2239
    /*      turned into a multipolygon.                                      */
2240
    /* --------------------------------------------------------------------- */
2241
0
    if (!bValidTopology || bMixedUpGeometries)
2242
0
    {
2243
0
        for (auto &sPolyEx : asPolyEx)
2244
0
        {
2245
0
            sPolyEx.bIsTopLevel = true;
2246
0
        }
2247
0
        nCountTopLevel = static_cast<int>(asPolyEx.size());
2248
0
    }
2249
2250
    /* -------------------------------------------------------------------- */
2251
    /*      Try to turn into one or more polygons based on the ring         */
2252
    /*      relationships.                                                  */
2253
    /* -------------------------------------------------------------------- */
2254
    // STEP 3: Sort again in initial order.
2255
0
    std::sort(asPolyEx.begin(), asPolyEx.end(),
2256
0
              OGRGeometryFactoryCompareByIndex);
2257
2258
    // STEP 4: Add holes as rings of their enclosing polygon.
2259
0
    for (auto &sPolyEx : asPolyEx)
2260
0
    {
2261
0
        if (sPolyEx.bIsTopLevel == false)
2262
0
        {
2263
0
            sPolyEx.poEnclosingPolygon->addRingDirectly(
2264
0
                sPolyEx.poPolygon->stealExteriorRingCurve());
2265
0
            delete sPolyEx.poPolygon;
2266
0
        }
2267
0
        else if (nCountTopLevel == 1)
2268
0
        {
2269
0
            geom = sPolyEx.poPolygon;
2270
0
        }
2271
0
    }
2272
2273
    // STEP 5: Add toplevel polygons.
2274
0
    if (nCountTopLevel > 1)
2275
0
    {
2276
0
        OGRGeometryCollection *poGC =
2277
0
            bHasCurves ? new OGRMultiSurface() : new OGRMultiPolygon();
2278
0
        for (auto &sPolyEx : asPolyEx)
2279
0
        {
2280
0
            if (sPolyEx.bIsTopLevel)
2281
0
            {
2282
0
                poGC->addGeometryDirectly(sPolyEx.poPolygon);
2283
0
            }
2284
0
        }
2285
0
        geom = poGC;
2286
0
    }
2287
2288
0
    return geom;
2289
0
}
2290
2291
/************************************************************************/
2292
/*                           createFromGML()                            */
2293
/************************************************************************/
2294
2295
/**
2296
 * \brief Create geometry from GML.
2297
 *
2298
 * This method translates a fragment of GML containing only the geometry
2299
 * portion into a corresponding OGRGeometry.  There are many limitations
2300
 * on the forms of GML geometries supported by this parser, but they are
2301
 * too numerous to list here.
2302
 *
2303
 * The following GML2 elements are parsed : Point, LineString, Polygon,
2304
 * MultiPoint, MultiLineString, MultiPolygon, MultiGeometry.
2305
 *
2306
 * The following GML3 elements are parsed : Surface,
2307
 * MultiSurface, PolygonPatch, Triangle, Rectangle, Curve, MultiCurve,
2308
 * LineStringSegment, Arc, Circle, CompositeSurface, OrientableSurface, Solid,
2309
 * Shell, Tin, TriangulatedSurface.
2310
 *
2311
 * Arc and Circle elements are returned as curves by default. Stroking to
2312
 * linestrings can be done with
2313
 * OGR_G_ForceTo(hGeom, OGR_GT_GetLinear(OGR_G_GetGeometryType(hGeom)), NULL).
2314
 * A 4 degrees step is used by default, unless the user
2315
 * has overridden the value with the OGR_ARC_STEPSIZE configuration variable.
2316
 *
2317
 * The C function OGR_G_CreateFromGML() is the same as this method.
2318
 *
2319
 * @param pszData The GML fragment for the geometry.
2320
 *
2321
 * @return a geometry on success, or NULL on error.
2322
 *
2323
 * @see OGR_G_ForceTo()
2324
 * @see OGR_GT_GetLinear()
2325
 * @see OGR_G_GetGeometryType()
2326
 */
2327
2328
OGRGeometry *OGRGeometryFactory::createFromGML(const char *pszData)
2329
2330
0
{
2331
0
    OGRGeometryH hGeom;
2332
2333
0
    hGeom = OGR_G_CreateFromGML(pszData);
2334
2335
0
    return OGRGeometry::FromHandle(hGeom);
2336
0
}
2337
2338
/************************************************************************/
2339
/*                           createFromGEOS()                           */
2340
/************************************************************************/
2341
2342
/** Builds a OGRGeometry* from a GEOSGeom.
2343
 * @param hGEOSCtxt GEOS context
2344
 * @param geosGeom GEOS geometry
2345
 * @return a OGRGeometry*
2346
 */
2347
OGRGeometry *OGRGeometryFactory::createFromGEOS(
2348
    UNUSED_IF_NO_GEOS GEOSContextHandle_t hGEOSCtxt,
2349
    UNUSED_IF_NO_GEOS GEOSGeom geosGeom)
2350
2351
0
{
2352
0
#ifndef HAVE_GEOS
2353
2354
0
    CPLError(CE_Failure, CPLE_NotSupported, "GEOS support not enabled.");
2355
0
    return nullptr;
2356
2357
#else
2358
2359
    size_t nSize = 0;
2360
    unsigned char *pabyBuf = nullptr;
2361
    OGRGeometry *poGeometry = nullptr;
2362
2363
    // Special case as POINT EMPTY cannot be translated to WKB.
2364
    if (GEOSGeomTypeId_r(hGEOSCtxt, geosGeom) == GEOS_POINT &&
2365
        GEOSisEmpty_r(hGEOSCtxt, geosGeom))
2366
        return new OGRPoint();
2367
2368
    const int nCoordDim =
2369
        GEOSGeom_getCoordinateDimension_r(hGEOSCtxt, geosGeom);
2370
    GEOSWKBWriter *wkbwriter = GEOSWKBWriter_create_r(hGEOSCtxt);
2371
    GEOSWKBWriter_setOutputDimension_r(hGEOSCtxt, wkbwriter, nCoordDim);
2372
    pabyBuf = GEOSWKBWriter_write_r(hGEOSCtxt, wkbwriter, geosGeom, &nSize);
2373
    GEOSWKBWriter_destroy_r(hGEOSCtxt, wkbwriter);
2374
2375
    if (pabyBuf == nullptr || nSize == 0)
2376
    {
2377
        return nullptr;
2378
    }
2379
2380
    if (OGRGeometryFactory::createFromWkb(pabyBuf, nullptr, &poGeometry,
2381
                                          static_cast<int>(nSize)) !=
2382
        OGRERR_NONE)
2383
    {
2384
        poGeometry = nullptr;
2385
    }
2386
2387
    GEOSFree_r(hGEOSCtxt, pabyBuf);
2388
2389
    return poGeometry;
2390
2391
#endif  // HAVE_GEOS
2392
0
}
2393
2394
/************************************************************************/
2395
/*                              haveGEOS()                              */
2396
/************************************************************************/
2397
2398
/**
2399
 * \brief Test if GEOS enabled.
2400
 *
2401
 * This static method returns TRUE if GEOS support is built into OGR,
2402
 * otherwise it returns FALSE.
2403
 *
2404
 * @return TRUE if available, otherwise FALSE.
2405
 */
2406
2407
bool OGRGeometryFactory::haveGEOS()
2408
2409
0
{
2410
0
#ifndef HAVE_GEOS
2411
0
    return false;
2412
#else
2413
    return true;
2414
#endif
2415
0
}
2416
2417
/************************************************************************/
2418
/*                           createFromFgf()                            */
2419
/************************************************************************/
2420
2421
/**
2422
 * \brief Create a geometry object of the appropriate type from its FGF (FDO
2423
 * Geometry Format) binary representation.
2424
 *
2425
 * Also note that this is a static method, and that there
2426
 * is no need to instantiate an OGRGeometryFactory object.
2427
 *
2428
 * The C function OGR_G_CreateFromFgf() is the same as this method.
2429
 *
2430
 * @param pabyData pointer to the input BLOB data.
2431
 * @param poSR pointer to the spatial reference to be assigned to the
2432
 *             created geometry object.  This may be NULL.
2433
 * @param ppoReturn the newly created geometry object will be assigned to the
2434
 *                  indicated pointer on return.  This will be NULL in case
2435
 *                  of failure, but NULL might be a valid return for a NULL
2436
 * shape.
2437
 * @param nBytes the number of bytes available in pabyData.
2438
 * @param pnBytesConsumed if not NULL, it will be set to the number of bytes
2439
 * consumed (at most nBytes).
2440
 *
2441
 * @return OGRERR_NONE if all goes well, otherwise any of
2442
 * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
2443
 * OGRERR_CORRUPT_DATA may be returned.
2444
 */
2445
2446
OGRErr OGRGeometryFactory::createFromFgf(const void *pabyData,
2447
                                         OGRSpatialReference *poSR,
2448
                                         OGRGeometry **ppoReturn, int nBytes,
2449
                                         int *pnBytesConsumed)
2450
2451
0
{
2452
0
    return createFromFgfInternal(static_cast<const GByte *>(pabyData), poSR,
2453
0
                                 ppoReturn, nBytes, pnBytesConsumed, 0);
2454
0
}
2455
2456
/************************************************************************/
2457
/*                       createFromFgfInternal()                        */
2458
/************************************************************************/
2459
2460
OGRErr OGRGeometryFactory::createFromFgfInternal(
2461
    const unsigned char *pabyData, OGRSpatialReference *poSR,
2462
    OGRGeometry **ppoReturn, int nBytes, int *pnBytesConsumed, int nRecLevel)
2463
0
{
2464
    // Arbitrary value, but certainly large enough for reasonable usages.
2465
0
    if (nRecLevel == 32)
2466
0
    {
2467
0
        CPLError(CE_Failure, CPLE_AppDefined,
2468
0
                 "Too many recursion levels (%d) while parsing FGF geometry.",
2469
0
                 nRecLevel);
2470
0
        return OGRERR_CORRUPT_DATA;
2471
0
    }
2472
2473
0
    *ppoReturn = nullptr;
2474
2475
0
    if (nBytes < 4)
2476
0
        return OGRERR_NOT_ENOUGH_DATA;
2477
2478
    /* -------------------------------------------------------------------- */
2479
    /*      Decode the geometry type.                                       */
2480
    /* -------------------------------------------------------------------- */
2481
0
    GInt32 nGType = 0;
2482
0
    memcpy(&nGType, pabyData + 0, 4);
2483
0
    CPL_LSBPTR32(&nGType);
2484
2485
0
    if (nGType < 0 || nGType > 13)
2486
0
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
2487
2488
    /* -------------------------------------------------------------------- */
2489
    /*      Decode the dimensionality if appropriate.                       */
2490
    /* -------------------------------------------------------------------- */
2491
0
    int nTupleSize = 0;
2492
0
    GInt32 nGDim = 0;
2493
2494
    // TODO: Why is this a switch?
2495
0
    switch (nGType)
2496
0
    {
2497
0
        case 1:  // Point
2498
0
        case 2:  // LineString
2499
0
        case 3:  // Polygon
2500
0
            if (nBytes < 8)
2501
0
                return OGRERR_NOT_ENOUGH_DATA;
2502
2503
0
            memcpy(&nGDim, pabyData + 4, 4);
2504
0
            CPL_LSBPTR32(&nGDim);
2505
2506
0
            if (nGDim < 0 || nGDim > 3)
2507
0
                return OGRERR_CORRUPT_DATA;
2508
2509
0
            nTupleSize = 2;
2510
0
            if (nGDim & 0x01)  // Z
2511
0
                nTupleSize++;
2512
0
            if (nGDim & 0x02)  // M
2513
0
                nTupleSize++;
2514
2515
0
            break;
2516
2517
0
        default:
2518
0
            break;
2519
0
    }
2520
2521
0
    OGRGeometry *poGeom = nullptr;
2522
2523
    /* -------------------------------------------------------------------- */
2524
    /*      None                                                            */
2525
    /* -------------------------------------------------------------------- */
2526
0
    if (nGType == 0)
2527
0
    {
2528
0
        if (pnBytesConsumed)
2529
0
            *pnBytesConsumed = 4;
2530
0
    }
2531
2532
    /* -------------------------------------------------------------------- */
2533
    /*      Point                                                           */
2534
    /* -------------------------------------------------------------------- */
2535
0
    else if (nGType == 1)
2536
0
    {
2537
0
        if (nBytes < nTupleSize * 8 + 8)
2538
0
            return OGRERR_NOT_ENOUGH_DATA;
2539
2540
0
        double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
2541
0
        memcpy(adfTuple, pabyData + 8, nTupleSize * 8);
2542
#ifdef CPL_MSB
2543
        for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2544
            CPL_SWAP64PTR(adfTuple + iOrdinal);
2545
#endif
2546
0
        if (nTupleSize > 2)
2547
0
            poGeom = new OGRPoint(adfTuple[0], adfTuple[1], adfTuple[2]);
2548
0
        else
2549
0
            poGeom = new OGRPoint(adfTuple[0], adfTuple[1]);
2550
2551
0
        if (pnBytesConsumed)
2552
0
            *pnBytesConsumed = 8 + nTupleSize * 8;
2553
0
    }
2554
2555
    /* -------------------------------------------------------------------- */
2556
    /*      LineString                                                      */
2557
    /* -------------------------------------------------------------------- */
2558
0
    else if (nGType == 2)
2559
0
    {
2560
0
        if (nBytes < 12)
2561
0
            return OGRERR_NOT_ENOUGH_DATA;
2562
2563
0
        GInt32 nPointCount = 0;
2564
0
        memcpy(&nPointCount, pabyData + 8, 4);
2565
0
        CPL_LSBPTR32(&nPointCount);
2566
2567
0
        if (nPointCount < 0 || nPointCount > INT_MAX / (nTupleSize * 8))
2568
0
            return OGRERR_CORRUPT_DATA;
2569
2570
0
        if (nBytes - 12 < nTupleSize * 8 * nPointCount)
2571
0
            return OGRERR_NOT_ENOUGH_DATA;
2572
2573
0
        OGRLineString *poLS = new OGRLineString();
2574
0
        poGeom = poLS;
2575
0
        poLS->setNumPoints(nPointCount);
2576
2577
0
        for (int iPoint = 0; iPoint < nPointCount; iPoint++)
2578
0
        {
2579
0
            double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
2580
0
            memcpy(adfTuple, pabyData + 12 + 8 * nTupleSize * iPoint,
2581
0
                   nTupleSize * 8);
2582
#ifdef CPL_MSB
2583
            for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2584
                CPL_SWAP64PTR(adfTuple + iOrdinal);
2585
#endif
2586
0
            if (nTupleSize > 2)
2587
0
                poLS->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
2588
0
            else
2589
0
                poLS->setPoint(iPoint, adfTuple[0], adfTuple[1]);
2590
0
        }
2591
2592
0
        if (pnBytesConsumed)
2593
0
            *pnBytesConsumed = 12 + nTupleSize * 8 * nPointCount;
2594
0
    }
2595
2596
    /* -------------------------------------------------------------------- */
2597
    /*      Polygon                                                         */
2598
    /* -------------------------------------------------------------------- */
2599
0
    else if (nGType == 3)
2600
0
    {
2601
0
        if (nBytes < 12)
2602
0
            return OGRERR_NOT_ENOUGH_DATA;
2603
2604
0
        GInt32 nRingCount = 0;
2605
0
        memcpy(&nRingCount, pabyData + 8, 4);
2606
0
        CPL_LSBPTR32(&nRingCount);
2607
2608
0
        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
2609
0
            return OGRERR_CORRUPT_DATA;
2610
2611
        // Each ring takes at least 4 bytes.
2612
0
        if (nBytes - 12 < nRingCount * 4)
2613
0
            return OGRERR_NOT_ENOUGH_DATA;
2614
2615
0
        int nNextByte = 12;
2616
2617
0
        OGRPolygon *poPoly = new OGRPolygon();
2618
0
        poGeom = poPoly;
2619
2620
0
        for (int iRing = 0; iRing < nRingCount; iRing++)
2621
0
        {
2622
0
            if (nBytes - nNextByte < 4)
2623
0
            {
2624
0
                delete poGeom;
2625
0
                return OGRERR_NOT_ENOUGH_DATA;
2626
0
            }
2627
2628
0
            GInt32 nPointCount = 0;
2629
0
            memcpy(&nPointCount, pabyData + nNextByte, 4);
2630
0
            CPL_LSBPTR32(&nPointCount);
2631
2632
0
            if (nPointCount < 0 || nPointCount > INT_MAX / (nTupleSize * 8))
2633
0
            {
2634
0
                delete poGeom;
2635
0
                return OGRERR_CORRUPT_DATA;
2636
0
            }
2637
2638
0
            nNextByte += 4;
2639
2640
0
            if (nBytes - nNextByte < nTupleSize * 8 * nPointCount)
2641
0
            {
2642
0
                delete poGeom;
2643
0
                return OGRERR_NOT_ENOUGH_DATA;
2644
0
            }
2645
2646
0
            OGRLinearRing *poLR = new OGRLinearRing();
2647
0
            poLR->setNumPoints(nPointCount);
2648
2649
0
            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
2650
0
            {
2651
0
                double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
2652
0
                memcpy(adfTuple, pabyData + nNextByte, nTupleSize * 8);
2653
0
                nNextByte += nTupleSize * 8;
2654
2655
#ifdef CPL_MSB
2656
                for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2657
                    CPL_SWAP64PTR(adfTuple + iOrdinal);
2658
#endif
2659
0
                if (nTupleSize > 2)
2660
0
                    poLR->setPoint(iPoint, adfTuple[0], adfTuple[1],
2661
0
                                   adfTuple[2]);
2662
0
                else
2663
0
                    poLR->setPoint(iPoint, adfTuple[0], adfTuple[1]);
2664
0
            }
2665
2666
0
            poPoly->addRingDirectly(poLR);
2667
0
        }
2668
2669
0
        if (pnBytesConsumed)
2670
0
            *pnBytesConsumed = nNextByte;
2671
0
    }
2672
2673
    /* -------------------------------------------------------------------- */
2674
    /*      GeometryCollections of various kinds.                           */
2675
    /* -------------------------------------------------------------------- */
2676
0
    else if (nGType == 4      // MultiPoint
2677
0
             || nGType == 5   // MultiLineString
2678
0
             || nGType == 6   // MultiPolygon
2679
0
             || nGType == 7)  // MultiGeometry
2680
0
    {
2681
0
        if (nBytes < 8)
2682
0
            return OGRERR_NOT_ENOUGH_DATA;
2683
2684
0
        GInt32 nGeomCount = 0;
2685
0
        memcpy(&nGeomCount, pabyData + 4, 4);
2686
0
        CPL_LSBPTR32(&nGeomCount);
2687
2688
0
        if (nGeomCount < 0 || nGeomCount > INT_MAX / 4)
2689
0
            return OGRERR_CORRUPT_DATA;
2690
2691
        // Each geometry takes at least 4 bytes.
2692
0
        if (nBytes - 8 < 4 * nGeomCount)
2693
0
            return OGRERR_NOT_ENOUGH_DATA;
2694
2695
0
        OGRGeometryCollection *poGC = nullptr;
2696
0
        if (nGType == 4)
2697
0
            poGC = new OGRMultiPoint();
2698
0
        else if (nGType == 5)
2699
0
            poGC = new OGRMultiLineString();
2700
0
        else if (nGType == 6)
2701
0
            poGC = new OGRMultiPolygon();
2702
0
        else if (nGType == 7)
2703
0
            poGC = new OGRGeometryCollection();
2704
2705
0
        int nBytesUsed = 8;
2706
2707
0
        for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
2708
0
        {
2709
0
            int nThisGeomSize = 0;
2710
0
            OGRGeometry *poThisGeom = nullptr;
2711
2712
0
            const OGRErr eErr = createFromFgfInternal(
2713
0
                pabyData + nBytesUsed, poSR, &poThisGeom, nBytes - nBytesUsed,
2714
0
                &nThisGeomSize, nRecLevel + 1);
2715
0
            if (eErr != OGRERR_NONE)
2716
0
            {
2717
0
                delete poGC;
2718
0
                return eErr;
2719
0
            }
2720
2721
0
            nBytesUsed += nThisGeomSize;
2722
0
            if (poThisGeom != nullptr)
2723
0
            {
2724
0
                const OGRErr eErr2 = poGC->addGeometryDirectly(poThisGeom);
2725
0
                if (eErr2 != OGRERR_NONE)
2726
0
                {
2727
0
                    delete poGC;
2728
0
                    delete poThisGeom;
2729
0
                    return eErr2;
2730
0
                }
2731
0
            }
2732
0
        }
2733
2734
0
        poGeom = poGC;
2735
0
        if (pnBytesConsumed)
2736
0
            *pnBytesConsumed = nBytesUsed;
2737
0
    }
2738
2739
    /* -------------------------------------------------------------------- */
2740
    /*      Currently unsupported geometry.                                 */
2741
    /*                                                                      */
2742
    /*      We need to add 10/11/12/13 curve types in some fashion.         */
2743
    /* -------------------------------------------------------------------- */
2744
0
    else
2745
0
    {
2746
0
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
2747
0
    }
2748
2749
    /* -------------------------------------------------------------------- */
2750
    /*      Assign spatial reference system.                                */
2751
    /* -------------------------------------------------------------------- */
2752
0
    if (poGeom != nullptr && poSR)
2753
0
        poGeom->assignSpatialReference(poSR);
2754
0
    *ppoReturn = poGeom;
2755
2756
0
    return OGRERR_NONE;
2757
0
}
2758
2759
/************************************************************************/
2760
/*                        OGR_G_CreateFromFgf()                         */
2761
/************************************************************************/
2762
2763
/**
2764
 * \brief Create a geometry object of the appropriate type from its FGF
2765
 * (FDO Geometry Format) binary representation.
2766
 *
2767
 * See OGRGeometryFactory::createFromFgf() */
2768
OGRErr CPL_DLL OGR_G_CreateFromFgf(const void *pabyData,
2769
                                   OGRSpatialReferenceH hSRS,
2770
                                   OGRGeometryH *phGeometry, int nBytes,
2771
                                   int *pnBytesConsumed)
2772
2773
0
{
2774
0
    return OGRGeometryFactory::createFromFgf(
2775
0
        pabyData, OGRSpatialReference::FromHandle(hSRS),
2776
0
        reinterpret_cast<OGRGeometry **>(phGeometry), nBytes, pnBytesConsumed);
2777
0
}
2778
2779
/************************************************************************/
2780
/*                SplitLineStringAtDateline()                           */
2781
/************************************************************************/
2782
2783
static void SplitLineStringAtDateline(OGRGeometryCollection *poMulti,
2784
                                      const OGRLineString *poLS,
2785
                                      double dfDateLineOffset, double dfXOffset)
2786
0
{
2787
0
    const double dfLeftBorderX = 180 - dfDateLineOffset;
2788
0
    const double dfRightBorderX = -180 + dfDateLineOffset;
2789
0
    const double dfDiffSpace = 360 - dfDateLineOffset;
2790
2791
0
    const bool bIs3D = poLS->getCoordinateDimension() == 3;
2792
0
    OGRLineString *poNewLS = new OGRLineString();
2793
0
    poMulti->addGeometryDirectly(poNewLS);
2794
0
    for (int i = 0; i < poLS->getNumPoints(); i++)
2795
0
    {
2796
0
        const double dfX = poLS->getX(i) + dfXOffset;
2797
0
        if (i > 0 && fabs(dfX - (poLS->getX(i - 1) + dfXOffset)) > dfDiffSpace)
2798
0
        {
2799
0
            double dfX1 = poLS->getX(i - 1) + dfXOffset;
2800
0
            double dfY1 = poLS->getY(i - 1);
2801
0
            double dfZ1 = poLS->getY(i - 1);
2802
0
            double dfX2 = poLS->getX(i) + dfXOffset;
2803
0
            double dfY2 = poLS->getY(i);
2804
0
            double dfZ2 = poLS->getY(i);
2805
2806
0
            if (dfX1 > -180 && dfX1 < dfRightBorderX && dfX2 == 180 &&
2807
0
                i + 1 < poLS->getNumPoints() &&
2808
0
                poLS->getX(i + 1) + dfXOffset > -180 &&
2809
0
                poLS->getX(i + 1) + dfXOffset < dfRightBorderX)
2810
0
            {
2811
0
                if (bIs3D)
2812
0
                    poNewLS->addPoint(-180, poLS->getY(i), poLS->getZ(i));
2813
0
                else
2814
0
                    poNewLS->addPoint(-180, poLS->getY(i));
2815
2816
0
                i++;
2817
2818
0
                if (bIs3D)
2819
0
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i),
2820
0
                                      poLS->getZ(i));
2821
0
                else
2822
0
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i));
2823
0
                continue;
2824
0
            }
2825
0
            else if (dfX1 > dfLeftBorderX && dfX1 < 180 && dfX2 == -180 &&
2826
0
                     i + 1 < poLS->getNumPoints() &&
2827
0
                     poLS->getX(i + 1) + dfXOffset > dfLeftBorderX &&
2828
0
                     poLS->getX(i + 1) + dfXOffset < 180)
2829
0
            {
2830
0
                if (bIs3D)
2831
0
                    poNewLS->addPoint(180, poLS->getY(i), poLS->getZ(i));
2832
0
                else
2833
0
                    poNewLS->addPoint(180, poLS->getY(i));
2834
2835
0
                i++;
2836
2837
0
                if (bIs3D)
2838
0
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i),
2839
0
                                      poLS->getZ(i));
2840
0
                else
2841
0
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i));
2842
0
                continue;
2843
0
            }
2844
2845
0
            if (dfX1 < dfRightBorderX && dfX2 > dfLeftBorderX)
2846
0
            {
2847
0
                std::swap(dfX1, dfX2);
2848
0
                std::swap(dfY1, dfY2);
2849
0
                std::swap(dfZ1, dfZ2);
2850
0
            }
2851
0
            if (dfX1 > dfLeftBorderX && dfX2 < dfRightBorderX)
2852
0
                dfX2 += 360;
2853
2854
0
            if (dfX1 <= 180 && dfX2 >= 180 && dfX1 < dfX2)
2855
0
            {
2856
0
                const double dfRatio = (180 - dfX1) / (dfX2 - dfX1);
2857
0
                const double dfY = dfRatio * dfY2 + (1 - dfRatio) * dfY1;
2858
0
                const double dfZ = dfRatio * dfZ2 + (1 - dfRatio) * dfZ1;
2859
0
                double dfNewX =
2860
0
                    poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? 180 : -180;
2861
0
                if (poNewLS->getNumPoints() == 0 ||
2862
0
                    poNewLS->getX(poNewLS->getNumPoints() - 1) != dfNewX ||
2863
0
                    poNewLS->getY(poNewLS->getNumPoints() - 1) != dfY)
2864
0
                {
2865
0
                    if (bIs3D)
2866
0
                        poNewLS->addPoint(dfNewX, dfY, dfZ);
2867
0
                    else
2868
0
                        poNewLS->addPoint(dfNewX, dfY);
2869
0
                }
2870
0
                poNewLS = new OGRLineString();
2871
0
                if (bIs3D)
2872
0
                    poNewLS->addPoint(
2873
0
                        poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? -180
2874
0
                                                                      : 180,
2875
0
                        dfY, dfZ);
2876
0
                else
2877
0
                    poNewLS->addPoint(
2878
0
                        poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? -180
2879
0
                                                                      : 180,
2880
0
                        dfY);
2881
0
                poMulti->addGeometryDirectly(poNewLS);
2882
0
            }
2883
0
            else
2884
0
            {
2885
0
                poNewLS = new OGRLineString();
2886
0
                poMulti->addGeometryDirectly(poNewLS);
2887
0
            }
2888
0
        }
2889
0
        if (bIs3D)
2890
0
            poNewLS->addPoint(dfX, poLS->getY(i), poLS->getZ(i));
2891
0
        else
2892
0
            poNewLS->addPoint(dfX, poLS->getY(i));
2893
0
    }
2894
0
}
2895
2896
/************************************************************************/
2897
/*               FixPolygonCoordinatesAtDateLine()                      */
2898
/************************************************************************/
2899
2900
#ifdef HAVE_GEOS
2901
static void FixPolygonCoordinatesAtDateLine(OGRPolygon *poPoly,
2902
                                            double dfDateLineOffset)
2903
{
2904
    const double dfLeftBorderX = 180 - dfDateLineOffset;
2905
    const double dfRightBorderX = -180 + dfDateLineOffset;
2906
    const double dfDiffSpace = 360 - dfDateLineOffset;
2907
2908
    for (int iPart = 0; iPart < 1 + poPoly->getNumInteriorRings(); iPart++)
2909
    {
2910
        OGRLineString *poLS = (iPart == 0) ? poPoly->getExteriorRing()
2911
                                           : poPoly->getInteriorRing(iPart - 1);
2912
        bool bGoEast = false;
2913
        const bool bIs3D = poLS->getCoordinateDimension() == 3;
2914
        for (int i = 1; i < poLS->getNumPoints(); i++)
2915
        {
2916
            double dfX = poLS->getX(i);
2917
            const double dfPrevX = poLS->getX(i - 1);
2918
            const double dfDiffLong = fabs(dfX - dfPrevX);
2919
            if (dfDiffLong > dfDiffSpace)
2920
            {
2921
                if ((dfPrevX > dfLeftBorderX && dfX < dfRightBorderX) ||
2922
                    (dfX < 0 && bGoEast))
2923
                {
2924
                    dfX += 360;
2925
                    bGoEast = true;
2926
                    if (bIs3D)
2927
                        poLS->setPoint(i, dfX, poLS->getY(i), poLS->getZ(i));
2928
                    else
2929
                        poLS->setPoint(i, dfX, poLS->getY(i));
2930
                }
2931
                else if (dfPrevX < dfRightBorderX && dfX > dfLeftBorderX)
2932
                {
2933
                    for (int j = i - 1; j >= 0; j--)
2934
                    {
2935
                        dfX = poLS->getX(j);
2936
                        if (dfX < 0)
2937
                        {
2938
                            if (bIs3D)
2939
                                poLS->setPoint(j, dfX + 360, poLS->getY(j),
2940
                                               poLS->getZ(j));
2941
                            else
2942
                                poLS->setPoint(j, dfX + 360, poLS->getY(j));
2943
                        }
2944
                    }
2945
                    bGoEast = false;
2946
                }
2947
                else
2948
                {
2949
                    bGoEast = false;
2950
                }
2951
            }
2952
        }
2953
    }
2954
}
2955
#endif
2956
2957
/************************************************************************/
2958
/*                            AddOffsetToLon()                          */
2959
/************************************************************************/
2960
2961
static void AddOffsetToLon(OGRGeometry *poGeom, double dfOffset)
2962
0
{
2963
0
    switch (wkbFlatten(poGeom->getGeometryType()))
2964
0
    {
2965
0
        case wkbPolygon:
2966
0
        {
2967
0
            for (auto poSubGeom : *(poGeom->toPolygon()))
2968
0
            {
2969
0
                AddOffsetToLon(poSubGeom, dfOffset);
2970
0
            }
2971
2972
0
            break;
2973
0
        }
2974
2975
0
        case wkbMultiLineString:
2976
0
        case wkbMultiPolygon:
2977
0
        case wkbGeometryCollection:
2978
0
        {
2979
0
            for (auto poSubGeom : *(poGeom->toGeometryCollection()))
2980
0
            {
2981
0
                AddOffsetToLon(poSubGeom, dfOffset);
2982
0
            }
2983
2984
0
            break;
2985
0
        }
2986
2987
0
        case wkbLineString:
2988
0
        {
2989
0
            OGRLineString *poLineString = poGeom->toLineString();
2990
0
            const int nPointCount = poLineString->getNumPoints();
2991
0
            const int nCoordDim = poLineString->getCoordinateDimension();
2992
0
            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
2993
0
            {
2994
0
                if (nCoordDim == 2)
2995
0
                    poLineString->setPoint(
2996
0
                        iPoint, poLineString->getX(iPoint) + dfOffset,
2997
0
                        poLineString->getY(iPoint));
2998
0
                else
2999
0
                    poLineString->setPoint(
3000
0
                        iPoint, poLineString->getX(iPoint) + dfOffset,
3001
0
                        poLineString->getY(iPoint), poLineString->getZ(iPoint));
3002
0
            }
3003
0
            break;
3004
0
        }
3005
3006
0
        default:
3007
0
            break;
3008
0
    }
3009
0
}
3010
3011
/************************************************************************/
3012
/*                        AddSimpleGeomToMulti()                        */
3013
/************************************************************************/
3014
3015
#ifdef HAVE_GEOS
3016
static void AddSimpleGeomToMulti(OGRGeometryCollection *poMulti,
3017
                                 const OGRGeometry *poGeom)
3018
{
3019
    switch (wkbFlatten(poGeom->getGeometryType()))
3020
    {
3021
        case wkbPolygon:
3022
        case wkbLineString:
3023
            poMulti->addGeometry(poGeom);
3024
            break;
3025
3026
        case wkbMultiLineString:
3027
        case wkbMultiPolygon:
3028
        case wkbGeometryCollection:
3029
        {
3030
            for (const auto poSubGeom : *(poGeom->toGeometryCollection()))
3031
            {
3032
                AddSimpleGeomToMulti(poMulti, poSubGeom);
3033
            }
3034
            break;
3035
        }
3036
3037
        default:
3038
            break;
3039
    }
3040
}
3041
#endif  // #ifdef HAVE_GEOS
3042
3043
/************************************************************************/
3044
/*                       WrapPointDateLine()                            */
3045
/************************************************************************/
3046
3047
static void WrapPointDateLine(OGRPoint *poPoint)
3048
0
{
3049
0
    if (poPoint->getX() > 180)
3050
0
    {
3051
0
        poPoint->setX(fmod(poPoint->getX() + 180, 360) - 180);
3052
0
    }
3053
0
    else if (poPoint->getX() < -180)
3054
0
    {
3055
0
        poPoint->setX(-(fmod(-poPoint->getX() + 180, 360) - 180));
3056
0
    }
3057
0
}
3058
3059
/************************************************************************/
3060
/*                 CutGeometryOnDateLineAndAddToMulti()                 */
3061
/************************************************************************/
3062
3063
static void CutGeometryOnDateLineAndAddToMulti(OGRGeometryCollection *poMulti,
3064
                                               const OGRGeometry *poGeom,
3065
                                               double dfDateLineOffset)
3066
0
{
3067
0
    const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
3068
0
    switch (eGeomType)
3069
0
    {
3070
0
        case wkbPoint:
3071
0
        {
3072
0
            auto poPoint = poGeom->toPoint()->clone();
3073
0
            WrapPointDateLine(poPoint);
3074
0
            poMulti->addGeometryDirectly(poPoint);
3075
0
            break;
3076
0
        }
3077
3078
0
        case wkbPolygon:
3079
0
        case wkbLineString:
3080
0
        {
3081
0
            bool bSplitLineStringAtDateline = false;
3082
0
            OGREnvelope oEnvelope;
3083
3084
0
            poGeom->getEnvelope(&oEnvelope);
3085
0
            const bool bAroundMinus180 = (oEnvelope.MinX < -180.0);
3086
3087
            // Naive heuristics... Place to improve.
3088
#ifdef HAVE_GEOS
3089
            std::unique_ptr<OGRGeometry> poDupGeom;
3090
            bool bWrapDateline = false;
3091
#endif
3092
3093
0
            const double dfLeftBorderX = 180 - dfDateLineOffset;
3094
0
            const double dfRightBorderX = -180 + dfDateLineOffset;
3095
0
            const double dfDiffSpace = 360 - dfDateLineOffset;
3096
3097
0
            const double dfXOffset = (bAroundMinus180) ? 360.0 : 0.0;
3098
0
            if (oEnvelope.MinX < -180 || oEnvelope.MaxX > 180 ||
3099
0
                (oEnvelope.MinX + dfXOffset > dfLeftBorderX &&
3100
0
                 oEnvelope.MaxX + dfXOffset > 180))
3101
0
            {
3102
0
#ifndef HAVE_GEOS
3103
0
                CPLError(CE_Failure, CPLE_NotSupported,
3104
0
                         "GEOS support not enabled.");
3105
#else
3106
                bWrapDateline = true;
3107
#endif
3108
0
            }
3109
0
            else
3110
0
            {
3111
0
                auto poLS = eGeomType == wkbPolygon
3112
0
                                ? poGeom->toPolygon()->getExteriorRing()
3113
0
                                : poGeom->toLineString();
3114
0
                if (poLS)
3115
0
                {
3116
0
                    double dfMaxSmallDiffLong = 0;
3117
0
                    bool bHasBigDiff = false;
3118
                    // Detect big gaps in longitude.
3119
0
                    for (int i = 1; i < poLS->getNumPoints(); i++)
3120
0
                    {
3121
0
                        const double dfPrevX = poLS->getX(i - 1) + dfXOffset;
3122
0
                        const double dfX = poLS->getX(i) + dfXOffset;
3123
0
                        const double dfDiffLong = fabs(dfX - dfPrevX);
3124
3125
0
                        if (dfDiffLong > dfDiffSpace &&
3126
0
                            ((dfX > dfLeftBorderX &&
3127
0
                              dfPrevX < dfRightBorderX) ||
3128
0
                             (dfPrevX > dfLeftBorderX && dfX < dfRightBorderX)))
3129
0
                        {
3130
0
                            constexpr double EPSILON = 1e-5;
3131
0
                            if (!(std::fabs(dfDiffLong - 360) < EPSILON &&
3132
0
                                  std::fabs(std::fabs(poLS->getY(i)) - 90) <
3133
0
                                      EPSILON))
3134
0
                            {
3135
0
                                bHasBigDiff = true;
3136
0
                            }
3137
0
                        }
3138
0
                        else if (dfDiffLong > dfMaxSmallDiffLong)
3139
0
                            dfMaxSmallDiffLong = dfDiffLong;
3140
0
                    }
3141
0
                    if (bHasBigDiff && dfMaxSmallDiffLong < dfDateLineOffset)
3142
0
                    {
3143
0
                        if (eGeomType == wkbLineString)
3144
0
                            bSplitLineStringAtDateline = true;
3145
0
                        else
3146
0
                        {
3147
0
#ifndef HAVE_GEOS
3148
0
                            CPLError(CE_Failure, CPLE_NotSupported,
3149
0
                                     "GEOS support not enabled.");
3150
#else
3151
                            poDupGeom.reset(poGeom->clone());
3152
                            FixPolygonCoordinatesAtDateLine(
3153
                                poDupGeom->toPolygon(), dfDateLineOffset);
3154
3155
                            OGREnvelope sEnvelope;
3156
                            poDupGeom->getEnvelope(&sEnvelope);
3157
                            bWrapDateline = sEnvelope.MinX != sEnvelope.MaxX;
3158
#endif
3159
0
                        }
3160
0
                    }
3161
0
                }
3162
0
            }
3163
3164
0
            if (bSplitLineStringAtDateline)
3165
0
            {
3166
0
                SplitLineStringAtDateline(poMulti, poGeom->toLineString(),
3167
0
                                          dfDateLineOffset,
3168
0
                                          (bAroundMinus180) ? 360.0 : 0.0);
3169
0
            }
3170
#ifdef HAVE_GEOS
3171
            else if (bWrapDateline)
3172
            {
3173
                const OGRGeometry *poWorkGeom =
3174
                    poDupGeom ? poDupGeom.get() : poGeom;
3175
                assert(poWorkGeom);
3176
                OGRGeometry *poRectangle1 = nullptr;
3177
                OGRGeometry *poRectangle2 = nullptr;
3178
                const char *pszWKT1 =
3179
                    !bAroundMinus180
3180
                        ? "POLYGON((-180 90,180 90,180 -90,-180 -90,-180 90))"
3181
                        : "POLYGON((180 90,-180 90,-180 -90,180 -90,180 90))";
3182
                const char *pszWKT2 =
3183
                    !bAroundMinus180
3184
                        ? "POLYGON((180 90,360 90,360 -90,180 -90,180 90))"
3185
                        : "POLYGON((-180 90,-360 90,-360 -90,-180 -90,-180 "
3186
                          "90))";
3187
                OGRGeometryFactory::createFromWkt(pszWKT1, nullptr,
3188
                                                  &poRectangle1);
3189
                OGRGeometryFactory::createFromWkt(pszWKT2, nullptr,
3190
                                                  &poRectangle2);
3191
                auto poGeom1 = std::unique_ptr<OGRGeometry>(
3192
                    poWorkGeom->Intersection(poRectangle1));
3193
                auto poGeom2 = std::unique_ptr<OGRGeometry>(
3194
                    poWorkGeom->Intersection(poRectangle2));
3195
                delete poRectangle1;
3196
                delete poRectangle2;
3197
3198
                if (poGeom1 != nullptr && poGeom2 != nullptr)
3199
                {
3200
                    AddSimpleGeomToMulti(poMulti, poGeom1.get());
3201
                    AddOffsetToLon(poGeom2.get(),
3202
                                   !bAroundMinus180 ? -360.0 : 360.0);
3203
                    AddSimpleGeomToMulti(poMulti, poGeom2.get());
3204
                }
3205
                else
3206
                {
3207
                    AddSimpleGeomToMulti(poMulti, poGeom);
3208
                }
3209
            }
3210
#endif
3211
0
            else
3212
0
            {
3213
0
                poMulti->addGeometry(poGeom);
3214
0
            }
3215
0
            break;
3216
0
        }
3217
3218
0
        case wkbMultiLineString:
3219
0
        case wkbMultiPolygon:
3220
0
        case wkbGeometryCollection:
3221
0
        {
3222
0
            for (const auto poSubGeom : *(poGeom->toGeometryCollection()))
3223
0
            {
3224
0
                CutGeometryOnDateLineAndAddToMulti(poMulti, poSubGeom,
3225
0
                                                   dfDateLineOffset);
3226
0
            }
3227
0
            break;
3228
0
        }
3229
3230
0
        default:
3231
0
            break;
3232
0
    }
3233
0
}
3234
3235
#ifdef HAVE_GEOS
3236
3237
/************************************************************************/
3238
/*                             RemovePoint()                            */
3239
/************************************************************************/
3240
3241
static void RemovePoint(OGRGeometry *poGeom, OGRPoint *poPoint)
3242
{
3243
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3244
    switch (eType)
3245
    {
3246
        case wkbLineString:
3247
        {
3248
            OGRLineString *poLS = poGeom->toLineString();
3249
            const bool bIs3D = (poLS->getCoordinateDimension() == 3);
3250
            int j = 0;
3251
            for (int i = 0; i < poLS->getNumPoints(); i++)
3252
            {
3253
                if (poLS->getX(i) != poPoint->getX() ||
3254
                    poLS->getY(i) != poPoint->getY())
3255
                {
3256
                    if (i > j)
3257
                    {
3258
                        if (bIs3D)
3259
                        {
3260
                            poLS->setPoint(j, poLS->getX(i), poLS->getY(i),
3261
                                           poLS->getZ(i));
3262
                        }
3263
                        else
3264
                        {
3265
                            poLS->setPoint(j, poLS->getX(i), poLS->getY(i));
3266
                        }
3267
                    }
3268
                    j++;
3269
                }
3270
            }
3271
            poLS->setNumPoints(j);
3272
            break;
3273
        }
3274
3275
        case wkbPolygon:
3276
        {
3277
            OGRPolygon *poPoly = poGeom->toPolygon();
3278
            if (poPoly->getExteriorRing() != nullptr)
3279
            {
3280
                RemovePoint(poPoly->getExteriorRing(), poPoint);
3281
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
3282
                {
3283
                    RemovePoint(poPoly->getInteriorRing(i), poPoint);
3284
                }
3285
            }
3286
            break;
3287
        }
3288
3289
        case wkbMultiLineString:
3290
        case wkbMultiPolygon:
3291
        case wkbGeometryCollection:
3292
        {
3293
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
3294
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
3295
            {
3296
                RemovePoint(poGC->getGeometryRef(i), poPoint);
3297
            }
3298
            break;
3299
        }
3300
3301
        default:
3302
            break;
3303
    }
3304
}
3305
3306
/************************************************************************/
3307
/*                              GetDist()                               */
3308
/************************************************************************/
3309
3310
static double GetDist(double dfDeltaX, double dfDeltaY)
3311
{
3312
    return sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY);
3313
}
3314
3315
/************************************************************************/
3316
/*                             AlterPole()                              */
3317
/*                                                                      */
3318
/* Replace and point at the pole by points really close to the pole,    */
3319
/* but on the previous and later segments.                              */
3320
/************************************************************************/
3321
3322
static void AlterPole(OGRGeometry *poGeom, OGRPoint *poPole,
3323
                      bool bIsRing = false)
3324
{
3325
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3326
    switch (eType)
3327
    {
3328
        case wkbLineString:
3329
        {
3330
            if (!bIsRing)
3331
                return;
3332
            OGRLineString *poLS = poGeom->toLineString();
3333
            const int nNumPoints = poLS->getNumPoints();
3334
            if (nNumPoints >= 4)
3335
            {
3336
                const bool bIs3D = (poLS->getCoordinateDimension() == 3);
3337
                std::vector<OGRRawPoint> aoPoints;
3338
                std::vector<double> adfZ;
3339
                bool bMustClose = false;
3340
                for (int i = 0; i < nNumPoints; i++)
3341
                {
3342
                    const double dfX = poLS->getX(i);
3343
                    const double dfY = poLS->getY(i);
3344
                    if (dfX == poPole->getX() && dfY == poPole->getY())
3345
                    {
3346
                        // Replace the pole by points really close to it
3347
                        if (i == 0)
3348
                            bMustClose = true;
3349
                        if (i == nNumPoints - 1)
3350
                            continue;
3351
                        const int iBefore = i > 0 ? i - 1 : nNumPoints - 2;
3352
                        double dfXBefore = poLS->getX(iBefore);
3353
                        double dfYBefore = poLS->getY(iBefore);
3354
                        double dfNorm =
3355
                            GetDist(dfXBefore - dfX, dfYBefore - dfY);
3356
                        double dfXInterp =
3357
                            dfX + (dfXBefore - dfX) / dfNorm * 1.0e-7;
3358
                        double dfYInterp =
3359
                            dfY + (dfYBefore - dfY) / dfNorm * 1.0e-7;
3360
                        OGRRawPoint oPoint;
3361
                        oPoint.x = dfXInterp;
3362
                        oPoint.y = dfYInterp;
3363
                        aoPoints.push_back(oPoint);
3364
                        adfZ.push_back(poLS->getZ(i));
3365
3366
                        const int iAfter = i + 1;
3367
                        double dfXAfter = poLS->getX(iAfter);
3368
                        double dfYAfter = poLS->getY(iAfter);
3369
                        dfNorm = GetDist(dfXAfter - dfX, dfYAfter - dfY);
3370
                        dfXInterp = dfX + (dfXAfter - dfX) / dfNorm * 1e-7;
3371
                        dfYInterp = dfY + (dfYAfter - dfY) / dfNorm * 1e-7;
3372
                        oPoint.x = dfXInterp;
3373
                        oPoint.y = dfYInterp;
3374
                        aoPoints.push_back(oPoint);
3375
                        adfZ.push_back(poLS->getZ(i));
3376
                    }
3377
                    else
3378
                    {
3379
                        OGRRawPoint oPoint;
3380
                        oPoint.x = dfX;
3381
                        oPoint.y = dfY;
3382
                        aoPoints.push_back(oPoint);
3383
                        adfZ.push_back(poLS->getZ(i));
3384
                    }
3385
                }
3386
                if (bMustClose)
3387
                {
3388
                    aoPoints.push_back(aoPoints[0]);
3389
                    adfZ.push_back(adfZ[0]);
3390
                }
3391
3392
                poLS->setPoints(static_cast<int>(aoPoints.size()),
3393
                                &(aoPoints[0]), bIs3D ? &adfZ[0] : nullptr);
3394
            }
3395
            break;
3396
        }
3397
3398
        case wkbPolygon:
3399
        {
3400
            OGRPolygon *poPoly = poGeom->toPolygon();
3401
            if (poPoly->getExteriorRing() != nullptr)
3402
            {
3403
                AlterPole(poPoly->getExteriorRing(), poPole, true);
3404
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
3405
                {
3406
                    AlterPole(poPoly->getInteriorRing(i), poPole, true);
3407
                }
3408
            }
3409
            break;
3410
        }
3411
3412
        case wkbMultiLineString:
3413
        case wkbMultiPolygon:
3414
        case wkbGeometryCollection:
3415
        {
3416
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
3417
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
3418
            {
3419
                AlterPole(poGC->getGeometryRef(i), poPole);
3420
            }
3421
            break;
3422
        }
3423
3424
        default:
3425
            break;
3426
    }
3427
}
3428
3429
/************************************************************************/
3430
/*                        IsPolarToGeographic()                         */
3431
/*                                                                      */
3432
/* Returns true if poCT transforms from a projection that includes one  */
3433
/* of the pole in a continuous way.                                     */
3434
/************************************************************************/
3435
3436
static bool IsPolarToGeographic(OGRCoordinateTransformation *poCT,
3437
                                OGRCoordinateTransformation *poRevCT,
3438
                                bool &bIsNorthPolarOut)
3439
{
3440
    bool bIsNorthPolar = false;
3441
    bool bIsSouthPolar = false;
3442
    double x = 0.0;
3443
    double y = 90.0;
3444
3445
    CPLErrorStateBackuper oErrorBackuper(CPLQuietErrorHandler);
3446
3447
    const bool bBackupEmitErrors = poCT->GetEmitErrors();
3448
    poRevCT->SetEmitErrors(false);
3449
    poCT->SetEmitErrors(false);
3450
3451
    if (poRevCT->Transform(1, &x, &y) &&
3452
        // Surprisingly, pole south projects correctly back &
3453
        // forth for antarctic polar stereographic.  Therefore, check that
3454
        // the projected value is not too big.
3455
        fabs(x) < 1e10 && fabs(y) < 1e10)
3456
    {
3457
        double x_tab[] = {x, x - 1e5, x + 1e5};
3458
        double y_tab[] = {y, y - 1e5, y + 1e5};
3459
        if (poCT->Transform(3, x_tab, y_tab) &&
3460
            fabs(y_tab[0] - (90.0)) < 1e-10 &&
3461
            fabs(x_tab[2] - x_tab[1]) > 170 &&
3462
            fabs(y_tab[2] - y_tab[1]) < 1e-10)
3463
        {
3464
            bIsNorthPolar = true;
3465
        }
3466
    }
3467
3468
    x = 0.0;
3469
    y = -90.0;
3470
    if (poRevCT->Transform(1, &x, &y) && fabs(x) < 1e10 && fabs(y) < 1e10)
3471
    {
3472
        double x_tab[] = {x, x - 1e5, x + 1e5};
3473
        double y_tab[] = {y, y - 1e5, y + 1e5};
3474
        if (poCT->Transform(3, x_tab, y_tab) &&
3475
            fabs(y_tab[0] - (-90.0)) < 1e-10 &&
3476
            fabs(x_tab[2] - x_tab[1]) > 170 &&
3477
            fabs(y_tab[2] - y_tab[1]) < 1e-10)
3478
        {
3479
            bIsSouthPolar = true;
3480
        }
3481
    }
3482
3483
    poCT->SetEmitErrors(bBackupEmitErrors);
3484
3485
    if (bIsNorthPolar && bIsSouthPolar)
3486
    {
3487
        bIsNorthPolar = false;
3488
        bIsSouthPolar = false;
3489
    }
3490
3491
    bIsNorthPolarOut = bIsNorthPolar;
3492
    return bIsNorthPolar || bIsSouthPolar;
3493
}
3494
3495
/************************************************************************/
3496
/*                             ContainsPole()                           */
3497
/************************************************************************/
3498
3499
static bool ContainsPole(const OGRGeometry *poGeom, const OGRPoint *poPole)
3500
{
3501
    switch (wkbFlatten(poGeom->getGeometryType()))
3502
    {
3503
        case wkbPolygon:
3504
        case wkbCurvePolygon:
3505
        {
3506
            const auto poPoly = poGeom->toCurvePolygon();
3507
            if (poPoly->getNumInteriorRings() > 0)
3508
            {
3509
                const auto poRing = poPoly->getExteriorRingCurve();
3510
                OGRPolygon oPolygon;
3511
                oPolygon.addRing(poRing);
3512
                return oPolygon.Contains(poPole);
3513
            }
3514
3515
            return poGeom->Contains(poPole);
3516
        }
3517
3518
        case wkbMultiPolygon:
3519
        case wkbMultiSurface:
3520
        case wkbGeometryCollection:
3521
        {
3522
            for (const auto *poSubGeom : poGeom->toGeometryCollection())
3523
            {
3524
                if (ContainsPole(poSubGeom, poPole))
3525
                    return true;
3526
            }
3527
            return false;
3528
        }
3529
3530
        default:
3531
            break;
3532
    }
3533
    return poGeom->Contains(poPole);
3534
}
3535
3536
/************************************************************************/
3537
/*                 TransformBeforePolarToGeographic()                   */
3538
/*                                                                      */
3539
/* Transform the geometry (by intersection), so as to cut each geometry */
3540
/* that crosses the pole, in 2 parts. Do also tricks for geometries     */
3541
/* that just touch the pole.                                            */
3542
/************************************************************************/
3543
3544
static std::unique_ptr<OGRGeometry> TransformBeforePolarToGeographic(
3545
    OGRCoordinateTransformation *poRevCT, bool bIsNorthPolar,
3546
    std::unique_ptr<OGRGeometry> poDstGeom, bool &bNeedPostCorrectionOut)
3547
{
3548
    const int nSign = (bIsNorthPolar) ? 1 : -1;
3549
3550
    // Does the geometry fully contains the pole ? */
3551
    double dfXPole = 0.0;
3552
    double dfYPole = nSign * 90.0;
3553
    poRevCT->Transform(1, &dfXPole, &dfYPole);
3554
    OGRPoint oPole(dfXPole, dfYPole);
3555
    const bool bContainsPole = ContainsPole(poDstGeom.get(), &oPole);
3556
3557
    const double EPS = 1e-9;
3558
3559
    // Does the geometry touches the pole and intersects the antimeridian ?
3560
    double dfNearPoleAntiMeridianX = 180.0;
3561
    double dfNearPoleAntiMeridianY = nSign * (90.0 - EPS);
3562
    poRevCT->Transform(1, &dfNearPoleAntiMeridianX, &dfNearPoleAntiMeridianY);
3563
    OGRPoint oNearPoleAntimeridian(dfNearPoleAntiMeridianX,
3564
                                   dfNearPoleAntiMeridianY);
3565
    const bool bContainsNearPoleAntimeridian =
3566
        CPL_TO_BOOL(poDstGeom->Contains(&oNearPoleAntimeridian));
3567
3568
    // Does the geometry intersects the antimeridian ?
3569
    OGRLineString oAntiMeridianLine;
3570
    oAntiMeridianLine.addPoint(180.0, nSign * (90.0 - EPS));
3571
    oAntiMeridianLine.addPoint(180.0, 0);
3572
    oAntiMeridianLine.transform(poRevCT);
3573
    const bool bIntersectsAntimeridian =
3574
        bContainsNearPoleAntimeridian ||
3575
        CPL_TO_BOOL(poDstGeom->Intersects(&oAntiMeridianLine));
3576
3577
    // Does the geometry touches the pole (but not intersect the antimeridian) ?
3578
    const bool bRegularTouchesPole =
3579
        !bContainsPole && !bContainsNearPoleAntimeridian &&
3580
        !bIntersectsAntimeridian && CPL_TO_BOOL(poDstGeom->Touches(&oPole));
3581
3582
    // Create a polygon of nearly a full hemisphere, but excluding the anti
3583
    // meridian and the pole.
3584
    OGRPolygon oCutter;
3585
    OGRLinearRing *poRing = new OGRLinearRing();
3586
    poRing->addPoint(180.0 - EPS, 0);
3587
    poRing->addPoint(180.0 - EPS, nSign * (90.0 - EPS));
3588
    // If the geometry doesn't contain the pole, then we add it to the cutter
3589
    // geometry, but will later remove it completely (geometry touching the
3590
    // pole but intersecting the antimeridian), or will replace it by 2
3591
    // close points (geometry touching the pole without intersecting the
3592
    // antimeridian)
3593
    if (!bContainsPole)
3594
        poRing->addPoint(180.0, nSign * 90);
3595
    poRing->addPoint(-180.0 + EPS, nSign * (90.0 - EPS));
3596
    poRing->addPoint(-180.0 + EPS, 0);
3597
    poRing->addPoint(180.0 - EPS, 0);
3598
    oCutter.addRingDirectly(poRing);
3599
3600
    if (oCutter.transform(poRevCT) == OGRERR_NONE &&
3601
        // Check that longitudes +/- 180 are continuous
3602
        // in the polar projection
3603
        fabs(poRing->getX(0) - poRing->getX(poRing->getNumPoints() - 2)) < 1 &&
3604
        (bContainsPole || bIntersectsAntimeridian ||
3605
         bContainsNearPoleAntimeridian || bRegularTouchesPole))
3606
    {
3607
        if (bContainsPole || bIntersectsAntimeridian ||
3608
            bContainsNearPoleAntimeridian)
3609
        {
3610
            auto poNewGeom =
3611
                std::unique_ptr<OGRGeometry>(poDstGeom->Difference(&oCutter));
3612
            if (poNewGeom)
3613
            {
3614
                if (bContainsNearPoleAntimeridian)
3615
                    RemovePoint(poNewGeom.get(), &oPole);
3616
                poDstGeom = std::move(poNewGeom);
3617
            }
3618
        }
3619
3620
        if (bRegularTouchesPole)
3621
        {
3622
            AlterPole(poDstGeom.get(), &oPole);
3623
        }
3624
3625
        bNeedPostCorrectionOut = true;
3626
    }
3627
    return poDstGeom;
3628
}
3629
3630
/************************************************************************/
3631
/*                   IsAntimeridianProjToGeographic()                   */
3632
/*                                                                      */
3633
/* Returns true if poCT transforms from a projection that includes the  */
3634
/* antimeridian in a continuous way.                                    */
3635
/************************************************************************/
3636
3637
static bool IsAntimeridianProjToGeographic(OGRCoordinateTransformation *poCT,
3638
                                           OGRCoordinateTransformation *poRevCT,
3639
                                           OGRGeometry *poDstGeometry)
3640
{
3641
    const bool bBackupEmitErrors = poCT->GetEmitErrors();
3642
    poRevCT->SetEmitErrors(false);
3643
    poCT->SetEmitErrors(false);
3644
3645
    // Find a reasonable latitude for the geometry
3646
    OGREnvelope sEnvelope;
3647
    poDstGeometry->getEnvelope(&sEnvelope);
3648
    OGRPoint pMean(sEnvelope.MinX, (sEnvelope.MinY + sEnvelope.MaxY) / 2);
3649
    if (pMean.transform(poCT) != OGRERR_NONE)
3650
    {
3651
        poCT->SetEmitErrors(bBackupEmitErrors);
3652
        return false;
3653
    }
3654
    const double dfMeanLat = pMean.getY();
3655
3656
    // Check that close points on each side of the antimeridian in (long, lat)
3657
    // project to close points in the source projection, and check that they
3658
    // roundtrip correctly.
3659
    const double EPS = 1.0e-8;
3660
    double x1 = 180 - EPS;
3661
    double y1 = dfMeanLat;
3662
    double x2 = -180 + EPS;
3663
    double y2 = dfMeanLat;
3664
    if (!poRevCT->Transform(1, &x1, &y1) || !poRevCT->Transform(1, &x2, &y2) ||
3665
        GetDist(x2 - x1, y2 - y1) > 1 || !poCT->Transform(1, &x1, &y1) ||
3666
        !poCT->Transform(1, &x2, &y2) ||
3667
        GetDist(x1 - (180 - EPS), y1 - dfMeanLat) > 2 * EPS ||
3668
        GetDist(x2 - (-180 + EPS), y2 - dfMeanLat) > 2 * EPS)
3669
    {
3670
        poCT->SetEmitErrors(bBackupEmitErrors);
3671
        return false;
3672
    }
3673
3674
    poCT->SetEmitErrors(bBackupEmitErrors);
3675
3676
    return true;
3677
}
3678
3679
/************************************************************************/
3680
/*                      CollectPointsOnAntimeridian()                   */
3681
/*                                                                      */
3682
/* Collect points that are the intersection of the lines of the geometry*/
3683
/* with the antimeridian.                                               */
3684
/************************************************************************/
3685
3686
static void CollectPointsOnAntimeridian(OGRGeometry *poGeom,
3687
                                        OGRCoordinateTransformation *poCT,
3688
                                        OGRCoordinateTransformation *poRevCT,
3689
                                        std::vector<OGRRawPoint> &aoPoints)
3690
{
3691
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3692
    switch (eType)
3693
    {
3694
        case wkbLineString:
3695
        {
3696
            OGRLineString *poLS = poGeom->toLineString();
3697
            const int nNumPoints = poLS->getNumPoints();
3698
            for (int i = 0; i < nNumPoints - 1; i++)
3699
            {
3700
                const double dfX = poLS->getX(i);
3701
                const double dfY = poLS->getY(i);
3702
                const double dfX2 = poLS->getX(i + 1);
3703
                const double dfY2 = poLS->getY(i + 1);
3704
                double dfXTrans = dfX;
3705
                double dfYTrans = dfY;
3706
                double dfX2Trans = dfX2;
3707
                double dfY2Trans = dfY2;
3708
                poCT->Transform(1, &dfXTrans, &dfYTrans);
3709
                poCT->Transform(1, &dfX2Trans, &dfY2Trans);
3710
                // Are we crossing the antimeridian ? (detecting by inversion of
3711
                // sign of X)
3712
                if ((dfX2 - dfX) * (dfX2Trans - dfXTrans) < 0 ||
3713
                    (dfX == dfX2 && dfX2Trans * dfXTrans < 0 &&
3714
                     fabs(fabs(dfXTrans) - 180) < 10 &&
3715
                     fabs(fabs(dfX2Trans) - 180) < 10))
3716
                {
3717
                    double dfXStart = dfX;
3718
                    double dfYStart = dfY;
3719
                    double dfXEnd = dfX2;
3720
                    double dfYEnd = dfY2;
3721
                    double dfXStartTrans = dfXTrans;
3722
                    double dfXEndTrans = dfX2Trans;
3723
                    int iIter = 0;
3724
                    const double EPS = 1e-8;
3725
                    // Find point of the segment intersecting the antimeridian
3726
                    // by dichotomy
3727
                    for (;
3728
                         iIter < 50 && (fabs(fabs(dfXStartTrans) - 180) > EPS ||
3729
                                        fabs(fabs(dfXEndTrans) - 180) > EPS);
3730
                         ++iIter)
3731
                    {
3732
                        double dfXMid = (dfXStart + dfXEnd) / 2;
3733
                        double dfYMid = (dfYStart + dfYEnd) / 2;
3734
                        double dfXMidTrans = dfXMid;
3735
                        double dfYMidTrans = dfYMid;
3736
                        poCT->Transform(1, &dfXMidTrans, &dfYMidTrans);
3737
                        if ((dfXMid - dfXStart) *
3738
                                    (dfXMidTrans - dfXStartTrans) <
3739
                                0 ||
3740
                            (dfXMid == dfXStart &&
3741
                             dfXMidTrans * dfXStartTrans < 0))
3742
                        {
3743
                            dfXEnd = dfXMid;
3744
                            dfYEnd = dfYMid;
3745
                            dfXEndTrans = dfXMidTrans;
3746
                        }
3747
                        else
3748
                        {
3749
                            dfXStart = dfXMid;
3750
                            dfYStart = dfYMid;
3751
                            dfXStartTrans = dfXMidTrans;
3752
                        }
3753
                    }
3754
                    if (iIter < 50)
3755
                    {
3756
                        OGRRawPoint oPoint;
3757
                        oPoint.x = (dfXStart + dfXEnd) / 2;
3758
                        oPoint.y = (dfYStart + dfYEnd) / 2;
3759
                        poCT->Transform(1, &(oPoint.x), &(oPoint.y));
3760
                        oPoint.x = 180.0;
3761
                        aoPoints.push_back(oPoint);
3762
                    }
3763
                }
3764
            }
3765
            break;
3766
        }
3767
3768
        case wkbPolygon:
3769
        {
3770
            OGRPolygon *poPoly = poGeom->toPolygon();
3771
            if (poPoly->getExteriorRing() != nullptr)
3772
            {
3773
                CollectPointsOnAntimeridian(poPoly->getExteriorRing(), poCT,
3774
                                            poRevCT, aoPoints);
3775
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
3776
                {
3777
                    CollectPointsOnAntimeridian(poPoly->getInteriorRing(i),
3778
                                                poCT, poRevCT, aoPoints);
3779
                }
3780
            }
3781
            break;
3782
        }
3783
3784
        case wkbMultiLineString:
3785
        case wkbMultiPolygon:
3786
        case wkbGeometryCollection:
3787
        {
3788
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
3789
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
3790
            {
3791
                CollectPointsOnAntimeridian(poGC->getGeometryRef(i), poCT,
3792
                                            poRevCT, aoPoints);
3793
            }
3794
            break;
3795
        }
3796
3797
        default:
3798
            break;
3799
    }
3800
}
3801
3802
/************************************************************************/
3803
/*                         SortPointsByAscendingY()                     */
3804
/************************************************************************/
3805
3806
struct SortPointsByAscendingY
3807
{
3808
    bool operator()(const OGRRawPoint &a, const OGRRawPoint &b)
3809
    {
3810
        return a.y < b.y;
3811
    }
3812
};
3813
3814
/************************************************************************/
3815
/*              TransformBeforeAntimeridianToGeographic()               */
3816
/*                                                                      */
3817
/* Transform the geometry (by intersection), so as to cut each geometry */
3818
/* that crosses the antimeridian, in 2 parts.                           */
3819
/************************************************************************/
3820
3821
static std::unique_ptr<OGRGeometry> TransformBeforeAntimeridianToGeographic(
3822
    OGRCoordinateTransformation *poCT, OGRCoordinateTransformation *poRevCT,
3823
    std::unique_ptr<OGRGeometry> poDstGeom, bool &bNeedPostCorrectionOut)
3824
{
3825
    OGREnvelope sEnvelope;
3826
    poDstGeom->getEnvelope(&sEnvelope);
3827
    OGRPoint pMean(sEnvelope.MinX, (sEnvelope.MinY + sEnvelope.MaxY) / 2);
3828
    pMean.transform(poCT);
3829
    const double dfMeanLat = pMean.getY();
3830
    pMean.setX(180.0);
3831
    pMean.setY(dfMeanLat);
3832
    pMean.transform(poRevCT);
3833
    // Check if the antimeridian crosses the bbox of our geometry
3834
    if (!(pMean.getX() >= sEnvelope.MinX && pMean.getY() >= sEnvelope.MinY &&
3835
          pMean.getX() <= sEnvelope.MaxX && pMean.getY() <= sEnvelope.MaxY))
3836
    {
3837
        return poDstGeom;
3838
    }
3839
3840
    // Collect points that are the intersection of the lines of the geometry
3841
    // with the antimeridian
3842
    std::vector<OGRRawPoint> aoPoints;
3843
    CollectPointsOnAntimeridian(poDstGeom.get(), poCT, poRevCT, aoPoints);
3844
    if (aoPoints.empty())
3845
        return poDstGeom;
3846
3847
    SortPointsByAscendingY sortFunc;
3848
    std::sort(aoPoints.begin(), aoPoints.end(), sortFunc);
3849
3850
    const double EPS = 1e-9;
3851
3852
    // Build a very thin polygon cutting the antimeridian at our points
3853
    OGRLinearRing *poLR = new OGRLinearRing;
3854
    {
3855
        double x = 180.0 - EPS;
3856
        double y = aoPoints[0].y - EPS;
3857
        poRevCT->Transform(1, &x, &y);
3858
        poLR->addPoint(x, y);
3859
    }
3860
    for (const auto &oPoint : aoPoints)
3861
    {
3862
        double x = 180.0 - EPS;
3863
        double y = oPoint.y;
3864
        poRevCT->Transform(1, &x, &y);
3865
        poLR->addPoint(x, y);
3866
    }
3867
    {
3868
        double x = 180.0 - EPS;
3869
        double y = aoPoints.back().y + EPS;
3870
        poRevCT->Transform(1, &x, &y);
3871
        poLR->addPoint(x, y);
3872
    }
3873
    {
3874
        double x = 180.0 + EPS;
3875
        double y = aoPoints.back().y + EPS;
3876
        poRevCT->Transform(1, &x, &y);
3877
        poLR->addPoint(x, y);
3878
    }
3879
    for (size_t i = aoPoints.size(); i > 0;)
3880
    {
3881
        --i;
3882
        const OGRRawPoint &oPoint = aoPoints[i];
3883
        double x = 180.0 + EPS;
3884
        double y = oPoint.y;
3885
        poRevCT->Transform(1, &x, &y);
3886
        poLR->addPoint(x, y);
3887
    }
3888
    {
3889
        double x = 180.0 + EPS;
3890
        double y = aoPoints[0].y - EPS;
3891
        poRevCT->Transform(1, &x, &y);
3892
        poLR->addPoint(x, y);
3893
    }
3894
    poLR->closeRings();
3895
3896
    OGRPolygon oPolyToCut;
3897
    oPolyToCut.addRingDirectly(poLR);
3898
3899
#if DEBUG_VERBOSE
3900
    char *pszWKT = NULL;
3901
    oPolyToCut.exportToWkt(&pszWKT);
3902
    CPLDebug("OGR", "Geometry to cut: %s", pszWKT);
3903
    CPLFree(pszWKT);
3904
#endif
3905
3906
    // Get the geometry without the antimeridian
3907
    auto poInter =
3908
        std::unique_ptr<OGRGeometry>(poDstGeom->Difference(&oPolyToCut));
3909
    if (poInter != nullptr)
3910
    {
3911
        poDstGeom = std::move(poInter);
3912
        bNeedPostCorrectionOut = true;
3913
    }
3914
3915
    return poDstGeom;
3916
}
3917
3918
/************************************************************************/
3919
/*                 SnapCoordsCloseToLatLongBounds()                     */
3920
/*                                                                      */
3921
/* This function snaps points really close to the antimerdian or poles  */
3922
/* to their exact longitudes/latitudes.                                 */
3923
/************************************************************************/
3924
3925
static void SnapCoordsCloseToLatLongBounds(OGRGeometry *poGeom)
3926
{
3927
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3928
    switch (eType)
3929
    {
3930
        case wkbLineString:
3931
        {
3932
            OGRLineString *poLS = poGeom->toLineString();
3933
            const double EPS = 1e-8;
3934
            for (int i = 0; i < poLS->getNumPoints(); i++)
3935
            {
3936
                OGRPoint p;
3937
                poLS->getPoint(i, &p);
3938
                if (fabs(p.getX() - 180.0) < EPS)
3939
                {
3940
                    p.setX(180.0);
3941
                    poLS->setPoint(i, &p);
3942
                }
3943
                else if (fabs(p.getX() - -180.0) < EPS)
3944
                {
3945
                    p.setX(-180.0);
3946
                    poLS->setPoint(i, &p);
3947
                }
3948
3949
                if (fabs(p.getY() - 90.0) < EPS)
3950
                {
3951
                    p.setY(90.0);
3952
                    poLS->setPoint(i, &p);
3953
                }
3954
                else if (fabs(p.getY() - -90.0) < EPS)
3955
                {
3956
                    p.setY(-90.0);
3957
                    poLS->setPoint(i, &p);
3958
                }
3959
            }
3960
            break;
3961
        }
3962
3963
        case wkbPolygon:
3964
        {
3965
            OGRPolygon *poPoly = poGeom->toPolygon();
3966
            if (poPoly->getExteriorRing() != nullptr)
3967
            {
3968
                SnapCoordsCloseToLatLongBounds(poPoly->getExteriorRing());
3969
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
3970
                {
3971
                    SnapCoordsCloseToLatLongBounds(poPoly->getInteriorRing(i));
3972
                }
3973
            }
3974
            break;
3975
        }
3976
3977
        case wkbMultiLineString:
3978
        case wkbMultiPolygon:
3979
        case wkbGeometryCollection:
3980
        {
3981
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
3982
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
3983
            {
3984
                SnapCoordsCloseToLatLongBounds(poGC->getGeometryRef(i));
3985
            }
3986
            break;
3987
        }
3988
3989
        default:
3990
            break;
3991
    }
3992
}
3993
3994
#endif
3995
3996
/************************************************************************/
3997
/*                  TransformWithOptionsCache::Private                  */
3998
/************************************************************************/
3999
4000
struct OGRGeometryFactory::TransformWithOptionsCache::Private
4001
{
4002
    const OGRSpatialReference *poSourceCRS = nullptr;
4003
    const OGRSpatialReference *poTargetCRS = nullptr;
4004
    const OGRCoordinateTransformation *poCT = nullptr;
4005
    std::unique_ptr<OGRCoordinateTransformation> poRevCT{};
4006
    bool bIsPolar = false;
4007
    bool bIsNorthPolar = false;
4008
4009
    void clear()
4010
0
    {
4011
0
        poSourceCRS = nullptr;
4012
0
        poTargetCRS = nullptr;
4013
0
        poCT = nullptr;
4014
0
        poRevCT.reset();
4015
0
        bIsPolar = false;
4016
0
        bIsNorthPolar = false;
4017
0
    }
4018
};
4019
4020
/************************************************************************/
4021
/*                     TransformWithOptionsCache()                      */
4022
/************************************************************************/
4023
4024
OGRGeometryFactory::TransformWithOptionsCache::TransformWithOptionsCache()
4025
0
    : d(new Private())
4026
0
{
4027
0
}
4028
4029
/************************************************************************/
4030
/*                     ~TransformWithOptionsCache()                      */
4031
/************************************************************************/
4032
4033
OGRGeometryFactory::TransformWithOptionsCache::~TransformWithOptionsCache()
4034
0
{
4035
0
}
4036
4037
/************************************************************************/
4038
/*              isTransformWithOptionsRegularTransform()                */
4039
/************************************************************************/
4040
4041
#ifdef HAVE_GEOS
4042
static bool MayBePolarToGeographic(const OGRSpatialReference *poSourceCRS,
4043
                                   const OGRSpatialReference *poTargetCRS)
4044
{
4045
    if (poSourceCRS && poTargetCRS && poSourceCRS->IsProjected() &&
4046
        poTargetCRS->IsGeographic() &&
4047
        poTargetCRS->GetAxisMappingStrategy() == OAMS_TRADITIONAL_GIS_ORDER &&
4048
        // check that angular units is degree
4049
        std::fabs(poTargetCRS->GetAngularUnits(nullptr) -
4050
                  CPLAtof(SRS_UA_DEGREE_CONV)) <=
4051
            1e-8 * CPLAtof(SRS_UA_DEGREE_CONV))
4052
    {
4053
        double dfWestLong = 0.0;
4054
        double dfSouthLat = 0.0;
4055
        double dfEastLong = 0.0;
4056
        double dfNorthLat = 0.0;
4057
        if (poSourceCRS->GetAreaOfUse(&dfWestLong, &dfSouthLat, &dfEastLong,
4058
                                      &dfNorthLat, nullptr) &&
4059
            !(dfSouthLat == -90.0 || dfNorthLat == 90.0 ||
4060
              dfWestLong == -180.0 || dfEastLong == 180.0 ||
4061
              dfWestLong > dfEastLong))
4062
        {
4063
            // Not a global geographic CRS
4064
            return false;
4065
        }
4066
        return true;
4067
    }
4068
    return false;
4069
}
4070
#endif
4071
4072
//! @cond Doxygen_Suppress
4073
/*static */
4074
bool OGRGeometryFactory::isTransformWithOptionsRegularTransform(
4075
    [[maybe_unused]] const OGRSpatialReference *poSourceCRS,
4076
    [[maybe_unused]] const OGRSpatialReference *poTargetCRS,
4077
    CSLConstList papszOptions)
4078
0
{
4079
0
    if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "WRAPDATELINE", "NO")) &&
4080
0
        poTargetCRS && poTargetCRS->IsGeographic())
4081
0
    {
4082
0
        return false;
4083
0
    }
4084
4085
#ifdef HAVE_GEOS
4086
    if (MayBePolarToGeographic(poSourceCRS, poTargetCRS))
4087
    {
4088
        return false;
4089
    }
4090
#endif
4091
4092
0
    return true;
4093
0
}
4094
4095
//! @endcond
4096
4097
/************************************************************************/
4098
/*                       transformWithOptions()                         */
4099
/************************************************************************/
4100
4101
/** Transform a geometry.
4102
 *
4103
 * This is an enhanced version of OGRGeometry::Transform().
4104
 *
4105
 * When reprojecting geometries from a Polar Stereographic projection or a
4106
 * projection naturally crossing the antimeridian (like UTM Zone 60) to a
4107
 * geographic CRS, it will cut geometries along the antimeridian. So a
4108
 * LineString might be returned as a MultiLineString.
4109
 *
4110
 * The WRAPDATELINE=YES option might be specified for circumstances to correct
4111
 * geometries that incorrectly go from a longitude on a side of the antimeridian
4112
 * to the other side, like a LINESTRING(-179 0,179 0) will be transformed to
4113
 * a MULTILINESTRING ((-179 0,-180 0),(180 0,179 0)). For that use case, hCT
4114
 * might be NULL.
4115
 *
4116
 * Supported options in papszOptions are:
4117
 * <ul>
4118
 * <li>WRAPDATELINE=YES</li>
4119
 * <li>DATELINEOFFSET=longitude_gap_in_degree. Defaults to 10.</li>
4120
 * </ul>
4121
 *
4122
 * This is the same as the C function OGR_GeomTransformer_Transform().
4123
 *
4124
 * @param poSrcGeom source geometry
4125
 * @param poCT coordinate transformation object, or NULL.
4126
 * @param papszOptions NULL terminated list of options, or NULL.
4127
 * @param cache Cache. May increase performance if persisted between invocations
4128
 * @return (new) transformed geometry.
4129
 */
4130
OGRGeometry *OGRGeometryFactory::transformWithOptions(
4131
    const OGRGeometry *poSrcGeom, OGRCoordinateTransformation *poCT,
4132
    char **papszOptions, CPL_UNUSED const TransformWithOptionsCache &cache)
4133
0
{
4134
0
    auto poDstGeom = std::unique_ptr<OGRGeometry>(poSrcGeom->clone());
4135
0
    if (poCT)
4136
0
    {
4137
#ifdef HAVE_GEOS
4138
        bool bNeedPostCorrection = false;
4139
        const auto poSourceCRS = poCT->GetSourceCS();
4140
        const auto poTargetCRS = poCT->GetTargetCS();
4141
        const auto eSrcGeomType = wkbFlatten(poSrcGeom->getGeometryType());
4142
        // Check if we are transforming from projected coordinates to
4143
        // geographic coordinates, with a chance that there might be polar or
4144
        // anti-meridian discontinuities. If so, create the inverse transform.
4145
        if (eSrcGeomType != wkbPoint && eSrcGeomType != wkbMultiPoint &&
4146
            (poSourceCRS != cache.d->poSourceCRS ||
4147
             poTargetCRS != cache.d->poTargetCRS || poCT != cache.d->poCT))
4148
        {
4149
            cache.d->clear();
4150
            cache.d->poSourceCRS = poSourceCRS;
4151
            cache.d->poTargetCRS = poTargetCRS;
4152
            cache.d->poCT = poCT;
4153
            if (MayBePolarToGeographic(poSourceCRS, poTargetCRS))
4154
            {
4155
                cache.d->poRevCT.reset(OGRCreateCoordinateTransformation(
4156
                    poTargetCRS, poSourceCRS));
4157
                cache.d->bIsNorthPolar = false;
4158
                cache.d->bIsPolar = false;
4159
                cache.d->poRevCT.reset(poCT->GetInverse());
4160
                if (cache.d->poRevCT &&
4161
                    IsPolarToGeographic(poCT, cache.d->poRevCT.get(),
4162
                                        cache.d->bIsNorthPolar))
4163
                {
4164
                    cache.d->bIsPolar = true;
4165
                }
4166
            }
4167
        }
4168
4169
        if (auto poRevCT = cache.d->poRevCT.get())
4170
        {
4171
            if (cache.d->bIsPolar)
4172
            {
4173
                poDstGeom = TransformBeforePolarToGeographic(
4174
                    poRevCT, cache.d->bIsNorthPolar, std::move(poDstGeom),
4175
                    bNeedPostCorrection);
4176
            }
4177
            else if (IsAntimeridianProjToGeographic(poCT, poRevCT,
4178
                                                    poDstGeom.get()))
4179
            {
4180
                poDstGeom = TransformBeforeAntimeridianToGeographic(
4181
                    poCT, poRevCT, std::move(poDstGeom), bNeedPostCorrection);
4182
            }
4183
        }
4184
#endif
4185
0
        OGRErr eErr = poDstGeom->transform(poCT);
4186
0
        if (eErr != OGRERR_NONE)
4187
0
        {
4188
0
            return nullptr;
4189
0
        }
4190
#ifdef HAVE_GEOS
4191
        if (bNeedPostCorrection)
4192
        {
4193
            SnapCoordsCloseToLatLongBounds(poDstGeom.get());
4194
        }
4195
#endif
4196
0
    }
4197
4198
0
    if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "WRAPDATELINE", "NO")))
4199
0
    {
4200
0
        const auto poDstGeomSRS = poDstGeom->getSpatialReference();
4201
0
        if (poDstGeomSRS && !poDstGeomSRS->IsGeographic())
4202
0
        {
4203
0
            CPLDebugOnce(
4204
0
                "OGR", "WRAPDATELINE is without effect when reprojecting to a "
4205
0
                       "non-geographic CRS");
4206
0
            return poDstGeom.release();
4207
0
        }
4208
        // TODO and we should probably also test that the axis order + data axis
4209
        // mapping is long-lat...
4210
0
        const OGRwkbGeometryType eType =
4211
0
            wkbFlatten(poDstGeom->getGeometryType());
4212
0
        if (eType == wkbPoint)
4213
0
        {
4214
0
            OGRPoint *poDstPoint = poDstGeom->toPoint();
4215
0
            WrapPointDateLine(poDstPoint);
4216
0
        }
4217
0
        else if (eType == wkbMultiPoint)
4218
0
        {
4219
0
            for (auto *poDstPoint : *(poDstGeom->toMultiPoint()))
4220
0
            {
4221
0
                WrapPointDateLine(poDstPoint);
4222
0
            }
4223
0
        }
4224
0
        else
4225
0
        {
4226
0
            OGREnvelope sEnvelope;
4227
0
            poDstGeom->getEnvelope(&sEnvelope);
4228
0
            if (sEnvelope.MinX >= -360.0 && sEnvelope.MaxX <= -180.0)
4229
0
                AddOffsetToLon(poDstGeom.get(), 360.0);
4230
0
            else if (sEnvelope.MinX >= 180.0 && sEnvelope.MaxX <= 360.0)
4231
0
                AddOffsetToLon(poDstGeom.get(), -360.0);
4232
0
            else
4233
0
            {
4234
0
                OGRwkbGeometryType eNewType;
4235
0
                if (eType == wkbPolygon || eType == wkbMultiPolygon)
4236
0
                    eNewType = wkbMultiPolygon;
4237
0
                else if (eType == wkbLineString || eType == wkbMultiLineString)
4238
0
                    eNewType = wkbMultiLineString;
4239
0
                else
4240
0
                    eNewType = wkbGeometryCollection;
4241
4242
0
                auto poMulti = std::unique_ptr<OGRGeometryCollection>(
4243
0
                    createGeometry(eNewType)->toGeometryCollection());
4244
4245
0
                double dfDateLineOffset = CPLAtofM(
4246
0
                    CSLFetchNameValueDef(papszOptions, "DATELINEOFFSET", "10"));
4247
0
                if (dfDateLineOffset <= 0.0 || dfDateLineOffset >= 360.0)
4248
0
                    dfDateLineOffset = 10.0;
4249
4250
0
                CutGeometryOnDateLineAndAddToMulti(
4251
0
                    poMulti.get(), poDstGeom.get(), dfDateLineOffset);
4252
4253
0
                if (poMulti->getNumGeometries() == 0)
4254
0
                {
4255
                    // do nothing
4256
0
                }
4257
0
                else if (poMulti->getNumGeometries() == 1 &&
4258
0
                         (eType == wkbPolygon || eType == wkbLineString))
4259
0
                {
4260
0
                    poDstGeom = poMulti->stealGeometry(0);
4261
0
                }
4262
0
                else
4263
0
                {
4264
0
                    poDstGeom = std::move(poMulti);
4265
0
                }
4266
0
            }
4267
0
        }
4268
0
    }
4269
4270
0
    return poDstGeom.release();
4271
0
}
4272
4273
/************************************************************************/
4274
/*                         OGRGeomTransformer()                         */
4275
/************************************************************************/
4276
4277
struct OGRGeomTransformer
4278
{
4279
    std::unique_ptr<OGRCoordinateTransformation> poCT{};
4280
    OGRGeometryFactory::TransformWithOptionsCache cache{};
4281
    CPLStringList aosOptions{};
4282
4283
0
    OGRGeomTransformer() = default;
4284
    OGRGeomTransformer(const OGRGeomTransformer &) = delete;
4285
    OGRGeomTransformer &operator=(const OGRGeomTransformer &) = delete;
4286
};
4287
4288
/************************************************************************/
4289
/*                     OGR_GeomTransformer_Create()                     */
4290
/************************************************************************/
4291
4292
/** Create a geometry transformer.
4293
 *
4294
 * This is an enhanced version of OGR_G_Transform().
4295
 *
4296
 * When reprojecting geometries from a Polar Stereographic projection or a
4297
 * projection naturally crossing the antimeridian (like UTM Zone 60) to a
4298
 * geographic CRS, it will cut geometries along the antimeridian. So a
4299
 * LineString might be returned as a MultiLineString.
4300
 *
4301
 * The WRAPDATELINE=YES option might be specified for circumstances to correct
4302
 * geometries that incorrectly go from a longitude on a side of the antimeridian
4303
 * to the other side, like a LINESTRING(-179 0,179 0) will be transformed to
4304
 * a MULTILINESTRING ((-179 0,-180 0),(180 0,179 0)). For that use case, hCT
4305
 * might be NULL.
4306
 *
4307
 * Supported options in papszOptions are:
4308
 * <ul>
4309
 * <li>WRAPDATELINE=YES</li>
4310
 * <li>DATELINEOFFSET=longitude_gap_in_degree. Defaults to 10.</li>
4311
 * </ul>
4312
 *
4313
 * This is the same as the C++ method OGRGeometryFactory::transformWithOptions().
4314
4315
 * @param hCT Coordinate transformation object (will be cloned) or NULL.
4316
 * @param papszOptions NULL terminated list of options, or NULL.
4317
 * @return transformer object to free with OGR_GeomTransformer_Destroy()
4318
 * @since GDAL 3.1
4319
 */
4320
OGRGeomTransformerH OGR_GeomTransformer_Create(OGRCoordinateTransformationH hCT,
4321
                                               CSLConstList papszOptions)
4322
0
{
4323
0
    OGRGeomTransformer *transformer = new OGRGeomTransformer;
4324
0
    if (hCT)
4325
0
    {
4326
0
        transformer->poCT.reset(
4327
0
            OGRCoordinateTransformation::FromHandle(hCT)->Clone());
4328
0
    }
4329
0
    transformer->aosOptions.Assign(CSLDuplicate(papszOptions));
4330
0
    return transformer;
4331
0
}
4332
4333
/************************************************************************/
4334
/*                     OGR_GeomTransformer_Transform()                  */
4335
/************************************************************************/
4336
4337
/** Transforms a geometry.
4338
 *
4339
 * @param hTransformer transformer object.
4340
 * @param hGeom Source geometry.
4341
 * @return a new geometry (or NULL) to destroy with OGR_G_DestroyGeometry()
4342
 * @since GDAL 3.1
4343
 */
4344
OGRGeometryH OGR_GeomTransformer_Transform(OGRGeomTransformerH hTransformer,
4345
                                           OGRGeometryH hGeom)
4346
0
{
4347
0
    VALIDATE_POINTER1(hTransformer, "OGR_GeomTransformer_Transform", nullptr);
4348
0
    VALIDATE_POINTER1(hGeom, "OGR_GeomTransformer_Transform", nullptr);
4349
4350
0
    return OGRGeometry::ToHandle(OGRGeometryFactory::transformWithOptions(
4351
0
        OGRGeometry::FromHandle(hGeom), hTransformer->poCT.get(),
4352
0
        hTransformer->aosOptions.List(), hTransformer->cache));
4353
0
}
4354
4355
/************************************************************************/
4356
/*                      OGR_GeomTransformer_Destroy()                   */
4357
/************************************************************************/
4358
4359
/** Destroy a geometry transformer allocated with OGR_GeomTransformer_Create()
4360
 *
4361
 * @param hTransformer transformer object.
4362
 * @since GDAL 3.1
4363
 */
4364
void OGR_GeomTransformer_Destroy(OGRGeomTransformerH hTransformer)
4365
0
{
4366
0
    delete hTransformer;
4367
0
}
4368
4369
/************************************************************************/
4370
/*                OGRGeometryFactory::GetDefaultArcStepSize()           */
4371
/************************************************************************/
4372
4373
/** Return the default value of the angular step used when stroking curves
4374
 * as lines. Defaults to 4 degrees.
4375
 * Can be modified by setting the OGR_ARC_STEPSIZE configuration option.
4376
 * Valid values are in [1e-2, 180] degree range.
4377
 * @since 3.11
4378
 */
4379
4380
/* static */
4381
double OGRGeometryFactory::GetDefaultArcStepSize()
4382
0
{
4383
0
    const double dfVal = CPLAtofM(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4"));
4384
0
    constexpr double MIN_VAL = 1e-2;
4385
0
    if (dfVal < MIN_VAL)
4386
0
    {
4387
0
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
4388
0
                     "Too small value for OGR_ARC_STEPSIZE. Clamping it to %f",
4389
0
                     MIN_VAL);
4390
0
        return MIN_VAL;
4391
0
    }
4392
0
    constexpr double MAX_VAL = 180;
4393
0
    if (dfVal > MAX_VAL)
4394
0
    {
4395
0
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
4396
0
                     "Too large value for OGR_ARC_STEPSIZE. Clamping it to %f",
4397
0
                     MAX_VAL);
4398
0
        return MAX_VAL;
4399
0
    }
4400
0
    return dfVal;
4401
0
}
4402
4403
/************************************************************************/
4404
/*                              DISTANCE()                              */
4405
/************************************************************************/
4406
4407
static inline double DISTANCE(double x1, double y1, double x2, double y2)
4408
0
{
4409
0
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
4410
0
}
4411
4412
/************************************************************************/
4413
/*                        approximateArcAngles()                        */
4414
/************************************************************************/
4415
4416
/**
4417
 * Stroke arc to linestring.
4418
 *
4419
 * Stroke an arc of a circle to a linestring based on a center
4420
 * point, radius, start angle and end angle, all angles in degrees.
4421
 *
4422
 * If the dfMaxAngleStepSizeDegrees is zero, then a default value will be
4423
 * used.  This is currently 4 degrees unless the user has overridden the
4424
 * value with the OGR_ARC_STEPSIZE configuration variable.
4425
 *
4426
 * If the OGR_ARC_MAX_GAP configuration variable is set, the straight-line
4427
 * distance between adjacent pairs of interpolated points will be limited to
4428
 * the specified distance. If the distance between a pair of points exceeds
4429
 * this maximum, additional points are interpolated between the two points.
4430
 *
4431
 * @see CPLSetConfigOption()
4432
 *
4433
 * @param dfCenterX center X
4434
 * @param dfCenterY center Y
4435
 * @param dfZ center Z
4436
 * @param dfPrimaryRadius X radius of ellipse.
4437
 * @param dfSecondaryRadius Y radius of ellipse.
4438
 * @param dfRotation rotation of the ellipse clockwise.
4439
 * @param dfStartAngle angle to first point on arc (clockwise of X-positive)
4440
 * @param dfEndAngle angle to last point on arc (clockwise of X-positive)
4441
 * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
4442
 * arc, zero to use the default setting.
4443
 * @param bUseMaxGap Optional: whether to honor OGR_ARC_MAX_GAP.
4444
 *
4445
 * @return OGRLineString geometry representing an approximation of the arc.
4446
 *
4447
 */
4448
4449
OGRGeometry *OGRGeometryFactory::approximateArcAngles(
4450
    double dfCenterX, double dfCenterY, double dfZ, double dfPrimaryRadius,
4451
    double dfSecondaryRadius, double dfRotation, double dfStartAngle,
4452
    double dfEndAngle, double dfMaxAngleStepSizeDegrees,
4453
    const bool bUseMaxGap /* = false */)
4454
4455
0
{
4456
0
    OGRLineString *poLine = new OGRLineString();
4457
0
    const double dfRotationRadians = dfRotation * M_PI / 180.0;
4458
4459
    // Support default arc step setting.
4460
0
    if (dfMaxAngleStepSizeDegrees < 1e-6)
4461
0
    {
4462
0
        dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
4463
0
    }
4464
4465
    // Determine maximum interpolation gap. This is the largest straight-line
4466
    // distance allowed between pairs of interpolated points. Default zero,
4467
    // meaning no gap.
4468
    // coverity[tainted_data]
4469
0
    const double dfMaxInterpolationGap =
4470
0
        bUseMaxGap ? CPLAtofM(CPLGetConfigOption("OGR_ARC_MAX_GAP", "0")) : 0.0;
4471
4472
    // Is this a full circle?
4473
0
    const bool bIsFullCircle = fabs(dfEndAngle - dfStartAngle) == 360.0;
4474
4475
    // Switch direction.
4476
0
    dfStartAngle *= -1;
4477
0
    dfEndAngle *= -1;
4478
4479
    // Figure out the number of slices to make this into.
4480
0
    int nVertexCount =
4481
0
        std::max(2, static_cast<int>(ceil(fabs(dfEndAngle - dfStartAngle) /
4482
0
                                          dfMaxAngleStepSizeDegrees) +
4483
0
                                     1));
4484
0
    const double dfSlice = (dfEndAngle - dfStartAngle) / (nVertexCount - 1);
4485
4486
    // If it is a full circle we will work out the last point separately.
4487
0
    if (bIsFullCircle)
4488
0
    {
4489
0
        nVertexCount--;
4490
0
    }
4491
4492
    /* -------------------------------------------------------------------- */
4493
    /*      Compute the interpolated points.                                */
4494
    /* -------------------------------------------------------------------- */
4495
0
    double dfLastX = 0.0;
4496
0
    double dfLastY = 0.0;
4497
0
    int nTotalAddPoints = 0;
4498
0
    for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
4499
0
    {
4500
0
        const double dfAngleOnEllipse =
4501
0
            (dfStartAngle + iPoint * dfSlice) * M_PI / 180.0;
4502
4503
        // Compute position on the unrotated ellipse.
4504
0
        const double dfEllipseX = cos(dfAngleOnEllipse) * dfPrimaryRadius;
4505
0
        const double dfEllipseY = sin(dfAngleOnEllipse) * dfSecondaryRadius;
4506
4507
        // Is this point too far from the previous point?
4508
0
        if (iPoint && dfMaxInterpolationGap != 0.0)
4509
0
        {
4510
0
            const double dfDistFromLast =
4511
0
                DISTANCE(dfLastX, dfLastY, dfEllipseX, dfEllipseY);
4512
4513
0
            if (dfDistFromLast > dfMaxInterpolationGap)
4514
0
            {
4515
0
                const int nAddPoints =
4516
0
                    static_cast<int>(dfDistFromLast / dfMaxInterpolationGap);
4517
0
                const double dfAddSlice = dfSlice / (nAddPoints + 1);
4518
4519
                // Interpolate additional points
4520
0
                for (int iAddPoint = 0; iAddPoint < nAddPoints; iAddPoint++)
4521
0
                {
4522
0
                    const double dfAddAngleOnEllipse =
4523
0
                        (dfStartAngle + (iPoint - 1) * dfSlice +
4524
0
                         (iAddPoint + 1) * dfAddSlice) *
4525
0
                        (M_PI / 180.0);
4526
4527
0
                    poLine->setPoint(
4528
0
                        iPoint + nTotalAddPoints + iAddPoint,
4529
0
                        cos(dfAddAngleOnEllipse) * dfPrimaryRadius,
4530
0
                        sin(dfAddAngleOnEllipse) * dfSecondaryRadius, dfZ);
4531
0
                }
4532
4533
0
                nTotalAddPoints += nAddPoints;
4534
0
            }
4535
0
        }
4536
4537
0
        poLine->setPoint(iPoint + nTotalAddPoints, dfEllipseX, dfEllipseY, dfZ);
4538
0
        dfLastX = dfEllipseX;
4539
0
        dfLastY = dfEllipseY;
4540
0
    }
4541
4542
    /* -------------------------------------------------------------------- */
4543
    /*      Rotate and translate the ellipse.                               */
4544
    /* -------------------------------------------------------------------- */
4545
0
    nVertexCount = poLine->getNumPoints();
4546
0
    for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
4547
0
    {
4548
0
        const double dfEllipseX = poLine->getX(iPoint);
4549
0
        const double dfEllipseY = poLine->getY(iPoint);
4550
4551
        // Rotate this position around the center of the ellipse.
4552
0
        const double dfArcX = dfCenterX + dfEllipseX * cos(dfRotationRadians) +
4553
0
                              dfEllipseY * sin(dfRotationRadians);
4554
0
        const double dfArcY = dfCenterY - dfEllipseX * sin(dfRotationRadians) +
4555
0
                              dfEllipseY * cos(dfRotationRadians);
4556
4557
0
        poLine->setPoint(iPoint, dfArcX, dfArcY, dfZ);
4558
0
    }
4559
4560
    /* -------------------------------------------------------------------- */
4561
    /*      If we're asked to make a full circle, ensure the start and      */
4562
    /*      end points coincide exactly, in spite of any rounding error.    */
4563
    /* -------------------------------------------------------------------- */
4564
0
    if (bIsFullCircle)
4565
0
    {
4566
0
        OGRPoint oPoint;
4567
0
        poLine->getPoint(0, &oPoint);
4568
0
        poLine->setPoint(nVertexCount, &oPoint);
4569
0
    }
4570
4571
0
    return poLine;
4572
0
}
4573
4574
/************************************************************************/
4575
/*                     OGR_G_ApproximateArcAngles()                     */
4576
/************************************************************************/
4577
4578
/**
4579
 * Stroke arc to linestring.
4580
 *
4581
 * Stroke an arc of a circle to a linestring based on a center
4582
 * point, radius, start angle and end angle, all angles in degrees.
4583
 *
4584
 * If the dfMaxAngleStepSizeDegrees is zero, then a default value will be
4585
 * used.  This is currently 4 degrees unless the user has overridden the
4586
 * value with the OGR_ARC_STEPSIZE configuration variable.
4587
 *
4588
 * @see CPLSetConfigOption()
4589
 *
4590
 * @param dfCenterX center X
4591
 * @param dfCenterY center Y
4592
 * @param dfZ center Z
4593
 * @param dfPrimaryRadius X radius of ellipse.
4594
 * @param dfSecondaryRadius Y radius of ellipse.
4595
 * @param dfRotation rotation of the ellipse clockwise.
4596
 * @param dfStartAngle angle to first point on arc (clockwise of X-positive)
4597
 * @param dfEndAngle angle to last point on arc (clockwise of X-positive)
4598
 * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
4599
 * arc, zero to use the default setting.
4600
 *
4601
 * @return OGRLineString geometry representing an approximation of the arc.
4602
 *
4603
 */
4604
4605
OGRGeometryH CPL_DLL OGR_G_ApproximateArcAngles(
4606
    double dfCenterX, double dfCenterY, double dfZ, double dfPrimaryRadius,
4607
    double dfSecondaryRadius, double dfRotation, double dfStartAngle,
4608
    double dfEndAngle, double dfMaxAngleStepSizeDegrees)
4609
4610
0
{
4611
0
    return OGRGeometry::ToHandle(OGRGeometryFactory::approximateArcAngles(
4612
0
        dfCenterX, dfCenterY, dfZ, dfPrimaryRadius, dfSecondaryRadius,
4613
0
        dfRotation, dfStartAngle, dfEndAngle, dfMaxAngleStepSizeDegrees));
4614
0
}
4615
4616
/************************************************************************/
4617
/*                           forceToLineString()                        */
4618
/************************************************************************/
4619
4620
/**
4621
 * \brief Convert to line string.
4622
 *
4623
 * Tries to force the provided geometry to be a line string.  This nominally
4624
 * effects a change on multilinestrings.
4625
 * For polygons or curvepolygons that have a single exterior ring,
4626
 * it will return the ring. For circular strings or compound curves, it will
4627
 * return an approximated line string.
4628
 *
4629
 * The passed in geometry is
4630
 * consumed and a new one returned (or potentially the same one).
4631
 *
4632
 * @param poGeom the input geometry - ownership is passed to the method.
4633
 * @param bOnlyInOrder flag that, if set to FALSE, indicate that the order of
4634
 *                     points in a linestring might be reversed if it enables
4635
 *                     to match the extremity of another linestring. If set
4636
 *                     to TRUE, the start of a linestring must match the end
4637
 *                     of another linestring.
4638
 * @return new geometry.
4639
 */
4640
4641
OGRGeometry *OGRGeometryFactory::forceToLineString(OGRGeometry *poGeom,
4642
                                                   bool bOnlyInOrder)
4643
4644
0
{
4645
0
    if (poGeom == nullptr)
4646
0
        return nullptr;
4647
4648
0
    const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
4649
4650
    /* -------------------------------------------------------------------- */
4651
    /*      If this is already a LineString, nothing to do                  */
4652
    /* -------------------------------------------------------------------- */
4653
0
    if (eGeomType == wkbLineString)
4654
0
    {
4655
        // Except if it is a linearring.
4656
0
        poGeom = OGRCurve::CastToLineString(poGeom->toCurve());
4657
4658
0
        return poGeom;
4659
0
    }
4660
4661
    /* -------------------------------------------------------------------- */
4662
    /*      If it is a polygon with a single ring, return it                 */
4663
    /* -------------------------------------------------------------------- */
4664
0
    if (eGeomType == wkbPolygon || eGeomType == wkbCurvePolygon)
4665
0
    {
4666
0
        OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
4667
0
        if (poCP->getNumInteriorRings() == 0)
4668
0
        {
4669
0
            OGRCurve *poRing = poCP->stealExteriorRingCurve();
4670
0
            delete poCP;
4671
0
            return forceToLineString(poRing);
4672
0
        }
4673
0
        return poGeom;
4674
0
    }
4675
4676
    /* -------------------------------------------------------------------- */
4677
    /*      If it is a curve line, call CurveToLine()                        */
4678
    /* -------------------------------------------------------------------- */
4679
0
    if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
4680
0
    {
4681
0
        OGRGeometry *poNewGeom = poGeom->toCurve()->CurveToLine();
4682
0
        delete poGeom;
4683
0
        return poNewGeom;
4684
0
    }
4685
4686
0
    if (eGeomType != wkbGeometryCollection && eGeomType != wkbMultiLineString &&
4687
0
        eGeomType != wkbMultiCurve)
4688
0
        return poGeom;
4689
4690
    // Build an aggregated linestring from all the linestrings in the container.
4691
0
    OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
4692
0
    if (poGeom->hasCurveGeometry())
4693
0
    {
4694
0
        OGRGeometryCollection *poNewGC =
4695
0
            poGC->getLinearGeometry()->toGeometryCollection();
4696
0
        delete poGC;
4697
0
        poGC = poNewGC;
4698
0
    }
4699
4700
0
    if (poGC->getNumGeometries() == 0)
4701
0
    {
4702
0
        poGeom = new OGRLineString();
4703
0
        poGeom->assignSpatialReference(poGC->getSpatialReference());
4704
0
        delete poGC;
4705
0
        return poGeom;
4706
0
    }
4707
4708
0
    int iGeom0 = 0;
4709
0
    while (iGeom0 < poGC->getNumGeometries())
4710
0
    {
4711
0
        if (wkbFlatten(poGC->getGeometryRef(iGeom0)->getGeometryType()) !=
4712
0
            wkbLineString)
4713
0
        {
4714
0
            iGeom0++;
4715
0
            continue;
4716
0
        }
4717
4718
0
        OGRLineString *poLineString0 =
4719
0
            poGC->getGeometryRef(iGeom0)->toLineString();
4720
0
        if (poLineString0->getNumPoints() < 2)
4721
0
        {
4722
0
            iGeom0++;
4723
0
            continue;
4724
0
        }
4725
4726
0
        OGRPoint pointStart0;
4727
0
        poLineString0->StartPoint(&pointStart0);
4728
0
        OGRPoint pointEnd0;
4729
0
        poLineString0->EndPoint(&pointEnd0);
4730
4731
0
        int iGeom1 = iGeom0 + 1;  // Used after for.
4732
0
        for (; iGeom1 < poGC->getNumGeometries(); iGeom1++)
4733
0
        {
4734
0
            if (wkbFlatten(poGC->getGeometryRef(iGeom1)->getGeometryType()) !=
4735
0
                wkbLineString)
4736
0
                continue;
4737
4738
0
            OGRLineString *poLineString1 =
4739
0
                poGC->getGeometryRef(iGeom1)->toLineString();
4740
0
            if (poLineString1->getNumPoints() < 2)
4741
0
                continue;
4742
4743
0
            OGRPoint pointStart1;
4744
0
            poLineString1->StartPoint(&pointStart1);
4745
0
            OGRPoint pointEnd1;
4746
0
            poLineString1->EndPoint(&pointEnd1);
4747
4748
0
            if (!bOnlyInOrder && (pointEnd0.Equals(&pointEnd1) ||
4749
0
                                  pointStart0.Equals(&pointStart1)))
4750
0
            {
4751
0
                poLineString1->reversePoints();
4752
0
                poLineString1->StartPoint(&pointStart1);
4753
0
                poLineString1->EndPoint(&pointEnd1);
4754
0
            }
4755
4756
0
            if (pointEnd0.Equals(&pointStart1))
4757
0
            {
4758
0
                poLineString0->addSubLineString(poLineString1, 1);
4759
0
                poGC->removeGeometry(iGeom1);
4760
0
                break;
4761
0
            }
4762
4763
0
            if (pointEnd1.Equals(&pointStart0))
4764
0
            {
4765
0
                poLineString1->addSubLineString(poLineString0, 1);
4766
0
                poGC->removeGeometry(iGeom0);
4767
0
                break;
4768
0
            }
4769
0
        }
4770
4771
0
        if (iGeom1 == poGC->getNumGeometries())
4772
0
        {
4773
0
            iGeom0++;
4774
0
        }
4775
0
    }
4776
4777
0
    if (poGC->getNumGeometries() == 1)
4778
0
    {
4779
0
        OGRGeometry *poSingleGeom = poGC->getGeometryRef(0);
4780
0
        poGC->removeGeometry(0, FALSE);
4781
0
        delete poGC;
4782
4783
0
        return poSingleGeom;
4784
0
    }
4785
4786
0
    return poGC;
4787
0
}
4788
4789
/************************************************************************/
4790
/*                      OGR_G_ForceToLineString()                       */
4791
/************************************************************************/
4792
4793
/**
4794
 * \brief Convert to line string.
4795
 *
4796
 * This function is the same as the C++ method
4797
 * OGRGeometryFactory::forceToLineString().
4798
 *
4799
 * @param hGeom handle to the geometry to convert (ownership surrendered).
4800
 * @return the converted geometry (ownership to caller).
4801
 *
4802
 * @since GDAL/OGR 1.10.0
4803
 */
4804
4805
OGRGeometryH OGR_G_ForceToLineString(OGRGeometryH hGeom)
4806
4807
0
{
4808
0
    return OGRGeometry::ToHandle(
4809
0
        OGRGeometryFactory::forceToLineString(OGRGeometry::FromHandle(hGeom)));
4810
0
}
4811
4812
/************************************************************************/
4813
/*                           forceTo()                                  */
4814
/************************************************************************/
4815
4816
/**
4817
 * \brief Convert to another geometry type
4818
 *
4819
 * Tries to force the provided geometry to the specified geometry type.
4820
 *
4821
 * It can promote 'single' geometry type to their corresponding collection type
4822
 * (see OGR_GT_GetCollection()) or the reverse. non-linear geometry type to
4823
 * their corresponding linear geometry type (see OGR_GT_GetLinear()), by
4824
 * possibly approximating circular arcs they may contain.  Regarding conversion
4825
 * from linear geometry types to curve geometry types, only "wrapping" will be
4826
 * done. No attempt to retrieve potential circular arcs by de-approximating
4827
 * stroking will be done. For that, OGRGeometry::getCurveGeometry() can be used.
4828
 *
4829
 * The passed in geometry is consumed and a new one returned (or potentially the
4830
 * same one).
4831
 *
4832
 * Starting with GDAL 3.9, this method honours the dimensionality of eTargetType.
4833
 *
4834
 * @param poGeom the input geometry - ownership is passed to the method.
4835
 * @param eTargetType target output geometry type.
4836
 * @param papszOptions options as a null-terminated list of strings or NULL.
4837
 * @return new geometry, or nullptr in case of error.
4838
 *
4839
 */
4840
4841
OGRGeometry *OGRGeometryFactory::forceTo(OGRGeometry *poGeom,
4842
                                         OGRwkbGeometryType eTargetType,
4843
                                         const char *const *papszOptions)
4844
0
{
4845
0
    if (poGeom == nullptr)
4846
0
        return poGeom;
4847
4848
0
    const OGRwkbGeometryType eTargetTypeFlat = wkbFlatten(eTargetType);
4849
0
    if (eTargetTypeFlat == wkbUnknown)
4850
0
        return poGeom;
4851
4852
0
    if (poGeom->IsEmpty())
4853
0
    {
4854
0
        OGRGeometry *poRet = createGeometry(eTargetType);
4855
0
        if (poRet)
4856
0
        {
4857
0
            poRet->assignSpatialReference(poGeom->getSpatialReference());
4858
0
            poRet->set3D(OGR_GT_HasZ(eTargetType));
4859
0
            poRet->setMeasured(OGR_GT_HasM(eTargetType));
4860
0
        }
4861
0
        delete poGeom;
4862
0
        return poRet;
4863
0
    }
4864
4865
0
    OGRwkbGeometryType eType = poGeom->getGeometryType();
4866
0
    OGRwkbGeometryType eTypeFlat = wkbFlatten(eType);
4867
4868
0
    if (eTargetTypeFlat != eTargetType && (eType == eTypeFlat))
4869
0
    {
4870
0
        auto poGeomNew = forceTo(poGeom, eTargetTypeFlat, papszOptions);
4871
0
        if (poGeomNew)
4872
0
        {
4873
0
            poGeomNew->set3D(OGR_GT_HasZ(eTargetType));
4874
0
            poGeomNew->setMeasured(OGR_GT_HasM(eTargetType));
4875
0
        }
4876
0
        return poGeomNew;
4877
0
    }
4878
4879
0
    if (eTypeFlat == eTargetTypeFlat)
4880
0
    {
4881
0
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
4882
0
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
4883
0
        return poGeom;
4884
0
    }
4885
4886
0
    eType = eTypeFlat;
4887
4888
0
    if (OGR_GT_IsSubClassOf(eType, wkbPolyhedralSurface) &&
4889
0
        (eTargetTypeFlat == wkbMultiSurface ||
4890
0
         eTargetTypeFlat == wkbGeometryCollection))
4891
0
    {
4892
0
        OGRwkbGeometryType eTempGeomType = wkbMultiPolygon;
4893
0
        if (OGR_GT_HasZ(eTargetType))
4894
0
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
4895
0
        if (OGR_GT_HasM(eTargetType))
4896
0
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
4897
0
        return forceTo(forceTo(poGeom, eTempGeomType, papszOptions),
4898
0
                       eTargetType, papszOptions);
4899
0
    }
4900
4901
0
    if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
4902
0
        eTargetTypeFlat == wkbGeometryCollection)
4903
0
    {
4904
0
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
4905
0
        auto poRet = OGRGeometryCollection::CastToGeometryCollection(poGC);
4906
0
        poRet->set3D(OGR_GT_HasZ(eTargetType));
4907
0
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
4908
0
        return poRet;
4909
0
    }
4910
4911
0
    if (eType == wkbTriangle && eTargetTypeFlat == wkbPolyhedralSurface)
4912
0
    {
4913
0
        OGRPolyhedralSurface *poPS = new OGRPolyhedralSurface();
4914
0
        poPS->assignSpatialReference(poGeom->getSpatialReference());
4915
0
        poPS->addGeometryDirectly(OGRTriangle::CastToPolygon(poGeom));
4916
0
        poPS->set3D(OGR_GT_HasZ(eTargetType));
4917
0
        poPS->setMeasured(OGR_GT_HasM(eTargetType));
4918
0
        return poPS;
4919
0
    }
4920
0
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbPolyhedralSurface)
4921
0
    {
4922
0
        OGRPolyhedralSurface *poPS = new OGRPolyhedralSurface();
4923
0
        poPS->assignSpatialReference(poGeom->getSpatialReference());
4924
0
        poPS->addGeometryDirectly(poGeom);
4925
0
        poPS->set3D(OGR_GT_HasZ(eTargetType));
4926
0
        poPS->setMeasured(OGR_GT_HasM(eTargetType));
4927
0
        return poPS;
4928
0
    }
4929
0
    else if (eType == wkbMultiPolygon &&
4930
0
             eTargetTypeFlat == wkbPolyhedralSurface)
4931
0
    {
4932
0
        OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
4933
0
        OGRPolyhedralSurface *poPS = new OGRPolyhedralSurface();
4934
0
        for (int i = 0; i < poMP->getNumGeometries(); ++i)
4935
0
        {
4936
0
            poPS->addGeometry(poMP->getGeometryRef(i));
4937
0
        }
4938
0
        delete poGeom;
4939
0
        poPS->set3D(OGR_GT_HasZ(eTargetType));
4940
0
        poPS->setMeasured(OGR_GT_HasM(eTargetType));
4941
0
        return poPS;
4942
0
    }
4943
0
    else if (eType == wkbTIN && eTargetTypeFlat == wkbPolyhedralSurface)
4944
0
    {
4945
0
        poGeom = OGRTriangulatedSurface::CastToPolyhedralSurface(
4946
0
            poGeom->toTriangulatedSurface());
4947
0
    }
4948
0
    else if (eType == wkbCurvePolygon &&
4949
0
             eTargetTypeFlat == wkbPolyhedralSurface)
4950
0
    {
4951
0
        OGRwkbGeometryType eTempGeomType = wkbPolygon;
4952
0
        if (OGR_GT_HasZ(eTargetType))
4953
0
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
4954
0
        if (OGR_GT_HasM(eTargetType))
4955
0
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
4956
0
        return forceTo(forceTo(poGeom, eTempGeomType, papszOptions),
4957
0
                       eTargetType, papszOptions);
4958
0
    }
4959
0
    else if (eType == wkbMultiSurface &&
4960
0
             eTargetTypeFlat == wkbPolyhedralSurface)
4961
0
    {
4962
0
        OGRwkbGeometryType eTempGeomType = wkbMultiPolygon;
4963
0
        if (OGR_GT_HasZ(eTargetType))
4964
0
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
4965
0
        if (OGR_GT_HasM(eTargetType))
4966
0
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
4967
0
        return forceTo(forceTo(poGeom, eTempGeomType, papszOptions),
4968
0
                       eTargetType, papszOptions);
4969
0
    }
4970
4971
0
    else if (eType == wkbTriangle && eTargetTypeFlat == wkbTIN)
4972
0
    {
4973
0
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
4974
0
        poTS->assignSpatialReference(poGeom->getSpatialReference());
4975
0
        poTS->addGeometryDirectly(poGeom);
4976
0
        poTS->set3D(OGR_GT_HasZ(eTargetType));
4977
0
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
4978
0
        return poTS;
4979
0
    }
4980
0
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbTIN)
4981
0
    {
4982
0
        OGRPolygon *poPoly = poGeom->toPolygon();
4983
0
        OGRLinearRing *poLR = poPoly->getExteriorRing();
4984
0
        if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
4985
0
              poPoly->getNumInteriorRings() == 0))
4986
0
        {
4987
0
            return poGeom;
4988
0
        }
4989
0
        OGRErr eErr = OGRERR_NONE;
4990
0
        OGRTriangle *poTriangle = new OGRTriangle(*poPoly, eErr);
4991
0
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
4992
0
        poTS->assignSpatialReference(poGeom->getSpatialReference());
4993
0
        poTS->addGeometryDirectly(poTriangle);
4994
0
        delete poGeom;
4995
0
        poTS->set3D(OGR_GT_HasZ(eTargetType));
4996
0
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
4997
0
        return poTS;
4998
0
    }
4999
0
    else if (eType == wkbMultiPolygon && eTargetTypeFlat == wkbTIN)
5000
0
    {
5001
0
        OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
5002
0
        for (const auto poPoly : *poMP)
5003
0
        {
5004
0
            const OGRLinearRing *poLR = poPoly->getExteriorRing();
5005
0
            if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
5006
0
                  poPoly->getNumInteriorRings() == 0))
5007
0
            {
5008
0
                return poGeom;
5009
0
            }
5010
0
        }
5011
0
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
5012
0
        poTS->assignSpatialReference(poGeom->getSpatialReference());
5013
0
        for (const auto poPoly : *poMP)
5014
0
        {
5015
0
            OGRErr eErr = OGRERR_NONE;
5016
0
            poTS->addGeometryDirectly(new OGRTriangle(*poPoly, eErr));
5017
0
        }
5018
0
        delete poGeom;
5019
0
        poTS->set3D(OGR_GT_HasZ(eTargetType));
5020
0
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
5021
0
        return poTS;
5022
0
    }
5023
0
    else if (eType == wkbPolyhedralSurface && eTargetTypeFlat == wkbTIN)
5024
0
    {
5025
0
        OGRPolyhedralSurface *poPS = poGeom->toPolyhedralSurface();
5026
0
        for (const auto poPoly : *poPS)
5027
0
        {
5028
0
            const OGRLinearRing *poLR = poPoly->getExteriorRing();
5029
0
            if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
5030
0
                  poPoly->getNumInteriorRings() == 0))
5031
0
            {
5032
0
                poGeom->set3D(OGR_GT_HasZ(eTargetType));
5033
0
                poGeom->setMeasured(OGR_GT_HasM(eTargetType));
5034
0
                return poGeom;
5035
0
            }
5036
0
        }
5037
0
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
5038
0
        poTS->assignSpatialReference(poGeom->getSpatialReference());
5039
0
        for (const auto poPoly : *poPS)
5040
0
        {
5041
0
            OGRErr eErr = OGRERR_NONE;
5042
0
            poTS->addGeometryDirectly(new OGRTriangle(*poPoly, eErr));
5043
0
        }
5044
0
        delete poGeom;
5045
0
        poTS->set3D(OGR_GT_HasZ(eTargetType));
5046
0
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
5047
0
        return poTS;
5048
0
    }
5049
5050
0
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbTriangle)
5051
0
    {
5052
0
        OGRPolygon *poPoly = poGeom->toPolygon();
5053
0
        OGRLinearRing *poLR = poPoly->getExteriorRing();
5054
0
        if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
5055
0
              poPoly->getNumInteriorRings() == 0))
5056
0
        {
5057
0
            poGeom->set3D(OGR_GT_HasZ(eTargetType));
5058
0
            poGeom->setMeasured(OGR_GT_HasM(eTargetType));
5059
0
            return poGeom;
5060
0
        }
5061
0
        OGRErr eErr = OGRERR_NONE;
5062
0
        OGRTriangle *poTriangle = new OGRTriangle(*poPoly, eErr);
5063
0
        delete poGeom;
5064
0
        poTriangle->set3D(OGR_GT_HasZ(eTargetType));
5065
0
        poTriangle->setMeasured(OGR_GT_HasM(eTargetType));
5066
0
        return poTriangle;
5067
0
    }
5068
5069
0
    if (eTargetTypeFlat == wkbTriangle || eTargetTypeFlat == wkbTIN ||
5070
0
        eTargetTypeFlat == wkbPolyhedralSurface)
5071
0
    {
5072
0
        OGRwkbGeometryType eTempGeomType = wkbPolygon;
5073
0
        if (OGR_GT_HasZ(eTargetType))
5074
0
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
5075
0
        if (OGR_GT_HasM(eTargetType))
5076
0
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
5077
0
        OGRGeometry *poPoly = forceTo(poGeom, eTempGeomType, papszOptions);
5078
0
        if (poPoly == poGeom)
5079
0
            return poGeom;
5080
0
        return forceTo(poPoly, eTargetType, papszOptions);
5081
0
    }
5082
5083
0
    if (eType == wkbTriangle && eTargetTypeFlat == wkbGeometryCollection)
5084
0
    {
5085
0
        OGRGeometryCollection *poGC = new OGRGeometryCollection();
5086
0
        poGC->assignSpatialReference(poGeom->getSpatialReference());
5087
0
        poGC->addGeometryDirectly(poGeom);
5088
0
        poGC->set3D(OGR_GT_HasZ(eTargetType));
5089
0
        poGC->setMeasured(OGR_GT_HasM(eTargetType));
5090
0
        return poGC;
5091
0
    }
5092
5093
    // Promote single to multi.
5094
0
    if (!OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
5095
0
        OGR_GT_IsSubClassOf(OGR_GT_GetCollection(eType), eTargetType))
5096
0
    {
5097
0
        OGRGeometry *poRet = createGeometry(eTargetType);
5098
0
        if (poRet == nullptr)
5099
0
        {
5100
0
            delete poGeom;
5101
0
            return nullptr;
5102
0
        }
5103
0
        poRet->assignSpatialReference(poGeom->getSpatialReference());
5104
0
        if (eType == wkbLineString)
5105
0
            poGeom = OGRCurve::CastToLineString(poGeom->toCurve());
5106
0
        poRet->toGeometryCollection()->addGeometryDirectly(poGeom);
5107
0
        poRet->set3D(OGR_GT_HasZ(eTargetType));
5108
0
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
5109
0
        return poRet;
5110
0
    }
5111
5112
0
    const bool bIsCurve = CPL_TO_BOOL(OGR_GT_IsCurve(eType));
5113
0
    if (bIsCurve && eTargetTypeFlat == wkbCompoundCurve)
5114
0
    {
5115
0
        auto poRet = OGRCurve::CastToCompoundCurve(poGeom->toCurve());
5116
0
        if (poRet)
5117
0
        {
5118
0
            poRet->set3D(OGR_GT_HasZ(eTargetType));
5119
0
            poRet->setMeasured(OGR_GT_HasM(eTargetType));
5120
0
        }
5121
0
        return poRet;
5122
0
    }
5123
0
    else if (bIsCurve && eTargetTypeFlat == wkbCurvePolygon)
5124
0
    {
5125
0
        OGRCurve *poCurve = poGeom->toCurve();
5126
0
        if (poCurve->getNumPoints() >= 3 && poCurve->get_IsClosed())
5127
0
        {
5128
0
            OGRCurvePolygon *poCP = new OGRCurvePolygon();
5129
0
            if (poCP->addRingDirectly(poCurve) == OGRERR_NONE)
5130
0
            {
5131
0
                poCP->assignSpatialReference(poGeom->getSpatialReference());
5132
0
                poCP->set3D(OGR_GT_HasZ(eTargetType));
5133
0
                poCP->setMeasured(OGR_GT_HasM(eTargetType));
5134
0
                return poCP;
5135
0
            }
5136
0
            else
5137
0
            {
5138
0
                delete poCP;
5139
0
            }
5140
0
        }
5141
0
    }
5142
0
    else if (eType == wkbLineString &&
5143
0
             OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface))
5144
0
    {
5145
0
        OGRGeometry *poTmp = forceTo(poGeom, wkbPolygon, papszOptions);
5146
0
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
5147
0
            return forceTo(poTmp, eTargetType, papszOptions);
5148
0
    }
5149
0
    else if (bIsCurve && eTargetTypeFlat == wkbMultiSurface)
5150
0
    {
5151
0
        OGRGeometry *poTmp = forceTo(poGeom, wkbCurvePolygon, papszOptions);
5152
0
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
5153
0
            return forceTo(poTmp, eTargetType, papszOptions);
5154
0
    }
5155
0
    else if (bIsCurve && eTargetTypeFlat == wkbMultiPolygon)
5156
0
    {
5157
0
        OGRGeometry *poTmp = forceTo(poGeom, wkbPolygon, papszOptions);
5158
0
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
5159
0
            return forceTo(poTmp, eTargetType, papszOptions);
5160
0
    }
5161
0
    else if (eType == wkbTriangle && eTargetTypeFlat == wkbCurvePolygon)
5162
0
    {
5163
0
        auto poRet = OGRSurface::CastToCurvePolygon(
5164
0
            OGRTriangle::CastToPolygon(poGeom)->toSurface());
5165
0
        poRet->set3D(OGR_GT_HasZ(eTargetType));
5166
0
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
5167
0
        return poRet;
5168
0
    }
5169
0
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbCurvePolygon)
5170
0
    {
5171
0
        auto poRet = OGRSurface::CastToCurvePolygon(poGeom->toPolygon());
5172
0
        poRet->set3D(OGR_GT_HasZ(eTargetType));
5173
0
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
5174
0
        return poRet;
5175
0
    }
5176
0
    else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
5177
0
             eTargetTypeFlat == wkbCompoundCurve)
5178
0
    {
5179
0
        OGRCurvePolygon *poPoly = poGeom->toCurvePolygon();
5180
0
        if (poPoly->getNumInteriorRings() == 0)
5181
0
        {
5182
0
            OGRCurve *poRet = poPoly->stealExteriorRingCurve();
5183
0
            if (poRet)
5184
0
                poRet->assignSpatialReference(poGeom->getSpatialReference());
5185
0
            delete poPoly;
5186
0
            return forceTo(poRet, eTargetType, papszOptions);
5187
0
        }
5188
0
    }
5189
0
    else if (eType == wkbMultiPolygon && eTargetTypeFlat == wkbMultiSurface)
5190
0
    {
5191
0
        auto poRet =
5192
0
            OGRMultiPolygon::CastToMultiSurface(poGeom->toMultiPolygon());
5193
0
        poRet->set3D(OGR_GT_HasZ(eTargetType));
5194
0
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
5195
0
        return poRet;
5196
0
    }
5197
0
    else if (eType == wkbMultiLineString && eTargetTypeFlat == wkbMultiCurve)
5198
0
    {
5199
0
        auto poRet =
5200
0
            OGRMultiLineString::CastToMultiCurve(poGeom->toMultiLineString());
5201
0
        poRet->set3D(OGR_GT_HasZ(eTargetType));
5202
0
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
5203
0
        return poRet;
5204
0
    }
5205
0
    else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
5206
0
    {
5207
0
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
5208
0
        if (poGC->getNumGeometries() == 1)
5209
0
        {
5210
0
            OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
5211
0
            if (poSubGeom)
5212
0
            {
5213
0
                poSubGeom->assignSpatialReference(
5214
0
                    poGeom->getSpatialReference());
5215
0
                poGC->removeGeometry(0, FALSE);
5216
0
                OGRGeometry *poRet =
5217
0
                    forceTo(poSubGeom->clone(), eTargetType, papszOptions);
5218
0
                if (OGR_GT_IsSubClassOf(wkbFlatten(poRet->getGeometryType()),
5219
0
                                        eTargetType))
5220
0
                {
5221
0
                    delete poGC;
5222
0
                    delete poSubGeom;
5223
0
                    return poRet;
5224
0
                }
5225
0
                poGC->addGeometryDirectly(poSubGeom);
5226
0
                poRet->set3D(OGR_GT_HasZ(eTargetType));
5227
0
                poRet->setMeasured(OGR_GT_HasM(eTargetType));
5228
0
                delete poRet;
5229
0
            }
5230
0
        }
5231
0
    }
5232
0
    else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
5233
0
             (OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface) ||
5234
0
              OGR_GT_IsSubClassOf(eTargetType, wkbMultiCurve)))
5235
0
    {
5236
0
        OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
5237
0
        if (poCP->getNumInteriorRings() == 0)
5238
0
        {
5239
0
            OGRCurve *poRing = poCP->getExteriorRingCurve();
5240
0
            poRing->assignSpatialReference(poGeom->getSpatialReference());
5241
0
            OGRwkbGeometryType eRingType = poRing->getGeometryType();
5242
0
            OGRGeometry *poRingDup = poRing->clone();
5243
0
            OGRGeometry *poRet = forceTo(poRingDup, eTargetType, papszOptions);
5244
0
            if (poRet->getGeometryType() != eRingType &&
5245
0
                !(eTypeFlat == wkbPolygon &&
5246
0
                  eTargetTypeFlat == wkbMultiLineString))
5247
0
            {
5248
0
                delete poCP;
5249
0
                return poRet;
5250
0
            }
5251
0
            else
5252
0
            {
5253
0
                delete poRet;
5254
0
            }
5255
0
        }
5256
0
    }
5257
5258
0
    if (eTargetTypeFlat == wkbLineString)
5259
0
    {
5260
0
        poGeom = forceToLineString(poGeom);
5261
0
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
5262
0
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
5263
0
    }
5264
0
    else if (eTargetTypeFlat == wkbPolygon)
5265
0
    {
5266
0
        poGeom = forceToPolygon(poGeom);
5267
0
        if (poGeom)
5268
0
        {
5269
0
            poGeom->set3D(OGR_GT_HasZ(eTargetType));
5270
0
            poGeom->setMeasured(OGR_GT_HasM(eTargetType));
5271
0
        }
5272
0
    }
5273
0
    else if (eTargetTypeFlat == wkbMultiPolygon)
5274
0
    {
5275
0
        poGeom = forceToMultiPolygon(poGeom);
5276
0
        if (poGeom)
5277
0
        {
5278
0
            poGeom->set3D(OGR_GT_HasZ(eTargetType));
5279
0
            poGeom->setMeasured(OGR_GT_HasM(eTargetType));
5280
0
        }
5281
0
    }
5282
0
    else if (eTargetTypeFlat == wkbMultiLineString)
5283
0
    {
5284
0
        poGeom = forceToMultiLineString(poGeom);
5285
0
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
5286
0
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
5287
0
    }
5288
0
    else if (eTargetTypeFlat == wkbMultiPoint)
5289
0
    {
5290
0
        poGeom = forceToMultiPoint(poGeom);
5291
0
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
5292
0
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
5293
0
    }
5294
5295
0
    return poGeom;
5296
0
}
5297
5298
/************************************************************************/
5299
/*                          OGR_G_ForceTo()                             */
5300
/************************************************************************/
5301
5302
/**
5303
 * \brief Convert to another geometry type
5304
 *
5305
 * This function is the same as the C++ method OGRGeometryFactory::forceTo().
5306
 *
5307
 * @param hGeom the input geometry - ownership is passed to the method.
5308
 * @param eTargetType target output geometry type.
5309
 * @param papszOptions options as a null-terminated list of strings or NULL.
5310
 * @return new geometry.
5311
 *
5312
 */
5313
5314
OGRGeometryH OGR_G_ForceTo(OGRGeometryH hGeom, OGRwkbGeometryType eTargetType,
5315
                           char **papszOptions)
5316
5317
0
{
5318
0
    return OGRGeometry::ToHandle(OGRGeometryFactory::forceTo(
5319
0
        OGRGeometry::FromHandle(hGeom), eTargetType, papszOptions));
5320
0
}
5321
5322
/************************************************************************/
5323
/*                        makeCompatibleWith()                          */
5324
/************************************************************************/
5325
5326
/**
5327
 * \brief Adjust a geometry to be compatible with a specified geometry type.
5328
 *
5329
 * This is a soft version of forceTo() that:
5330
 * - converts single geometry type to a multi-geometry type if eTargetType is
5331
 *   a multi-geometry type (e.g. wkbMultiPolygon) and the single geometry type
5332
 *   is compatible with it (e.g. wkbPolygon)
5333
 * - insert components of multi-geometries that are not wkbGeometryCollection
5334
 *   into a GeometryCollection, when eTargetType == wkbGeometryCollection
5335
 * - insert single geometries into a GeometryCollection, when
5336
 *   eTargetType == wkbGeometryCollection.
5337
 * - convert a single-part multi-geometry to the specified target single
5338
 *   geometry type. e.g a MultiPolygon to a Polygon
5339
 * - in other cases, the geometry is returned unmodified.
5340
 *
5341
 * @param poGeom the input geometry - ownership is passed to the method.
5342
 * @param eTargetType target output geometry type.
5343
 *                    Typically a layer geometry type.
5344
 * @return a geometry (potentially poGeom itself)
5345
 *
5346
 * @since GDAL 3.12
5347
 */
5348
5349
std::unique_ptr<OGRGeometry>
5350
OGRGeometryFactory::makeCompatibleWith(std::unique_ptr<OGRGeometry> poGeom,
5351
                                       OGRwkbGeometryType eTargetType)
5352
0
{
5353
0
    const auto eGeomType = poGeom->getGeometryType();
5354
0
    const auto eFlattenTargetType = wkbFlatten(eTargetType);
5355
0
    if (eFlattenTargetType != wkbUnknown &&
5356
0
        eFlattenTargetType != wkbFlatten(eGeomType))
5357
0
    {
5358
0
        if (OGR_GT_GetCollection(eGeomType) == eFlattenTargetType)
5359
0
        {
5360
0
            poGeom.reset(
5361
0
                OGRGeometryFactory::forceTo(poGeom.release(), eTargetType));
5362
0
        }
5363
0
        else if (eGeomType == OGR_GT_GetCollection(eTargetType) &&
5364
0
                 poGeom->toGeometryCollection()->getNumGeometries() == 1)
5365
0
        {
5366
0
            poGeom = poGeom->toGeometryCollection()->stealGeometry(0);
5367
0
        }
5368
0
        else if (eFlattenTargetType == wkbGeometryCollection)
5369
0
        {
5370
0
            auto poGeomColl = std::make_unique<OGRGeometryCollection>();
5371
0
            if (OGR_GT_IsSubClassOf(eGeomType, wkbGeometryCollection))
5372
0
            {
5373
0
                for (const auto *poSubGeom : *(poGeom->toGeometryCollection()))
5374
0
                {
5375
0
                    poGeomColl->addGeometry(poSubGeom);
5376
0
                }
5377
0
            }
5378
0
            else
5379
0
            {
5380
0
                poGeomColl->addGeometry(std::move(poGeom));
5381
0
            }
5382
0
            poGeom = std::move(poGeomColl);
5383
0
        }
5384
0
    }
5385
0
    return poGeom;
5386
0
}
5387
5388
/************************************************************************/
5389
/*                         GetCurveParameters()                          */
5390
/************************************************************************/
5391
5392
/**
5393
 * \brief Returns the parameter of an arc circle.
5394
 *
5395
 * Angles are return in radians, with trigonometic convention (counter clock
5396
 * wise)
5397
 *
5398
 * @param x0 x of first point
5399
 * @param y0 y of first point
5400
 * @param x1 x of intermediate point
5401
 * @param y1 y of intermediate point
5402
 * @param x2 x of final point
5403
 * @param y2 y of final point
5404
 * @param R radius (output)
5405
 * @param cx x of arc center (output)
5406
 * @param cy y of arc center (output)
5407
 * @param alpha0 angle between center and first point, in radians (output)
5408
 * @param alpha1 angle between center and intermediate point, in radians
5409
 * (output)
5410
 * @param alpha2 angle between center and final point, in radians (output)
5411
 * @return TRUE if the points are not aligned and define an arc circle.
5412
 *
5413
 */
5414
5415
int OGRGeometryFactory::GetCurveParameters(double x0, double y0, double x1,
5416
                                           double y1, double x2, double y2,
5417
                                           double &R, double &cx, double &cy,
5418
                                           double &alpha0, double &alpha1,
5419
                                           double &alpha2)
5420
0
{
5421
0
    if (std::isnan(x0) || std::isnan(y0) || std::isnan(x1) || std::isnan(y1) ||
5422
0
        std::isnan(x2) || std::isnan(y2))
5423
0
    {
5424
0
        return FALSE;
5425
0
    }
5426
5427
    // Circle.
5428
0
    if (x0 == x2 && y0 == y2)
5429
0
    {
5430
0
        if (x0 != x1 || y0 != y1)
5431
0
        {
5432
0
            cx = (x0 + x1) / 2;
5433
0
            cy = (y0 + y1) / 2;
5434
0
            R = DISTANCE(cx, cy, x0, y0);
5435
            // Arbitrarily pick counter-clock-wise order (like PostGIS does).
5436
0
            alpha0 = atan2(y0 - cy, x0 - cx);
5437
0
            alpha1 = alpha0 + M_PI;
5438
0
            alpha2 = alpha0 + 2 * M_PI;
5439
0
            return TRUE;
5440
0
        }
5441
0
        else
5442
0
        {
5443
0
            return FALSE;
5444
0
        }
5445
0
    }
5446
5447
0
    double dx01 = x1 - x0;
5448
0
    double dy01 = y1 - y0;
5449
0
    double dx12 = x2 - x1;
5450
0
    double dy12 = y2 - y1;
5451
5452
    // Normalize above values so as to make sure we don't end up with
5453
    // computing a difference of too big values.
5454
0
    double dfScale = fabs(dx01);
5455
0
    if (fabs(dy01) > dfScale)
5456
0
        dfScale = fabs(dy01);
5457
0
    if (fabs(dx12) > dfScale)
5458
0
        dfScale = fabs(dx12);
5459
0
    if (fabs(dy12) > dfScale)
5460
0
        dfScale = fabs(dy12);
5461
0
    const double dfInvScale = 1.0 / dfScale;
5462
0
    dx01 *= dfInvScale;
5463
0
    dy01 *= dfInvScale;
5464
0
    dx12 *= dfInvScale;
5465
0
    dy12 *= dfInvScale;
5466
5467
0
    const double det = dx01 * dy12 - dx12 * dy01;
5468
0
    if (fabs(det) < 1.0e-8 || std::isnan(det))
5469
0
    {
5470
0
        return FALSE;
5471
0
    }
5472
0
    const double x01_mid = (x0 + x1) * dfInvScale;
5473
0
    const double x12_mid = (x1 + x2) * dfInvScale;
5474
0
    const double y01_mid = (y0 + y1) * dfInvScale;
5475
0
    const double y12_mid = (y1 + y2) * dfInvScale;
5476
0
    const double c01 = dx01 * x01_mid + dy01 * y01_mid;
5477
0
    const double c12 = dx12 * x12_mid + dy12 * y12_mid;
5478
0
    cx = 0.5 * dfScale * (c01 * dy12 - c12 * dy01) / det;
5479
0
    cy = 0.5 * dfScale * (-c01 * dx12 + c12 * dx01) / det;
5480
5481
0
    alpha0 = atan2((y0 - cy) * dfInvScale, (x0 - cx) * dfInvScale);
5482
0
    alpha1 = atan2((y1 - cy) * dfInvScale, (x1 - cx) * dfInvScale);
5483
0
    alpha2 = atan2((y2 - cy) * dfInvScale, (x2 - cx) * dfInvScale);
5484
0
    R = DISTANCE(cx, cy, x0, y0);
5485
5486
    // If det is negative, the orientation if clockwise.
5487
0
    if (det < 0)
5488
0
    {
5489
0
        if (alpha1 > alpha0)
5490
0
            alpha1 -= 2 * M_PI;
5491
0
        if (alpha2 > alpha1)
5492
0
            alpha2 -= 2 * M_PI;
5493
0
    }
5494
0
    else
5495
0
    {
5496
0
        if (alpha1 < alpha0)
5497
0
            alpha1 += 2 * M_PI;
5498
0
        if (alpha2 < alpha1)
5499
0
            alpha2 += 2 * M_PI;
5500
0
    }
5501
5502
0
    CPLAssert((alpha0 <= alpha1 && alpha1 <= alpha2) ||
5503
0
              (alpha0 >= alpha1 && alpha1 >= alpha2));
5504
5505
0
    return TRUE;
5506
0
}
5507
5508
/************************************************************************/
5509
/*                      OGRGeometryFactoryStrokeArc()                   */
5510
/************************************************************************/
5511
5512
static void OGRGeometryFactoryStrokeArc(OGRLineString *poLine, double cx,
5513
                                        double cy, double R, double z0,
5514
                                        double z1, int bHasZ, double alpha0,
5515
                                        double alpha1, double dfStep,
5516
                                        int bStealthConstraints)
5517
0
{
5518
0
    const int nSign = dfStep > 0 ? 1 : -1;
5519
5520
    // Constant angle between all points, so as to not depend on winding order.
5521
0
    const double dfNumSteps = fabs((alpha1 - alpha0) / dfStep) + 0.5;
5522
0
    if (dfNumSteps >= std::numeric_limits<int>::max() ||
5523
0
        dfNumSteps <= std::numeric_limits<int>::min() || std::isnan(dfNumSteps))
5524
0
    {
5525
0
        CPLError(CE_Warning, CPLE_AppDefined,
5526
0
                 "OGRGeometryFactoryStrokeArc: bogus steps: "
5527
0
                 "%lf %lf %lf %lf",
5528
0
                 alpha0, alpha1, dfStep, dfNumSteps);
5529
0
        return;
5530
0
    }
5531
5532
0
    int nSteps = static_cast<int>(dfNumSteps);
5533
0
    if (bStealthConstraints)
5534
0
    {
5535
        // We need at least 6 intermediate vertex, and if more additional
5536
        // multiples of 2.
5537
0
        if (nSteps < 1 + 6)
5538
0
            nSteps = 1 + 6;
5539
0
        else
5540
0
            nSteps = 1 + 6 + 2 * ((nSteps - (1 + 6) + (2 - 1)) / 2);
5541
0
    }
5542
0
    else if (nSteps < 4)
5543
0
    {
5544
0
        nSteps = 4;
5545
0
    }
5546
0
    dfStep = nSign * fabs((alpha1 - alpha0) / nSteps);
5547
0
    double alpha = alpha0 + dfStep;
5548
5549
0
    for (; (alpha - alpha1) * nSign < -1e-8; alpha += dfStep)
5550
0
    {
5551
0
        const double dfX = cx + R * cos(alpha);
5552
0
        const double dfY = cy + R * sin(alpha);
5553
0
        if (bHasZ)
5554
0
        {
5555
0
            const double z =
5556
0
                z0 + (z1 - z0) * (alpha - alpha0) / (alpha1 - alpha0);
5557
0
            poLine->addPoint(dfX, dfY, z);
5558
0
        }
5559
0
        else
5560
0
        {
5561
0
            poLine->addPoint(dfX, dfY);
5562
0
        }
5563
0
    }
5564
0
}
5565
5566
/************************************************************************/
5567
/*                         OGRGF_SetHiddenValue()                       */
5568
/************************************************************************/
5569
5570
// TODO(schwehr): Cleanup these static constants.
5571
constexpr int HIDDEN_ALPHA_WIDTH = 32;
5572
constexpr GUInt32 HIDDEN_ALPHA_SCALE =
5573
    static_cast<GUInt32>((static_cast<GUIntBig>(1) << HIDDEN_ALPHA_WIDTH) - 2);
5574
constexpr int HIDDEN_ALPHA_HALF_WIDTH = (HIDDEN_ALPHA_WIDTH / 2);
5575
constexpr int HIDDEN_ALPHA_HALF_MASK = (1 << HIDDEN_ALPHA_HALF_WIDTH) - 1;
5576
5577
// Encode 16-bit nValue in the 8-lsb of dfX and dfY.
5578
5579
#ifdef CPL_LSB
5580
constexpr int DOUBLE_LSB_OFFSET = 0;
5581
#else
5582
constexpr int DOUBLE_LSB_OFFSET = 7;
5583
#endif
5584
5585
static void OGRGF_SetHiddenValue(GUInt16 nValue, double &dfX, double &dfY)
5586
0
{
5587
0
    GByte abyData[8] = {};
5588
5589
0
    memcpy(abyData, &dfX, sizeof(double));
5590
0
    abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue & 0xFF);
5591
0
    memcpy(&dfX, abyData, sizeof(double));
5592
5593
0
    memcpy(abyData, &dfY, sizeof(double));
5594
0
    abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue >> 8);
5595
0
    memcpy(&dfY, abyData, sizeof(double));
5596
0
}
5597
5598
/************************************************************************/
5599
/*                         OGRGF_GetHiddenValue()                       */
5600
/************************************************************************/
5601
5602
// Decode 16-bit nValue from the 8-lsb of dfX and dfY.
5603
static GUInt16 OGRGF_GetHiddenValue(double dfX, double dfY)
5604
0
{
5605
0
    GByte abyData[8] = {};
5606
0
    memcpy(abyData, &dfX, sizeof(double));
5607
0
    GUInt16 nValue = abyData[DOUBLE_LSB_OFFSET];
5608
0
    memcpy(abyData, &dfY, sizeof(double));
5609
0
    nValue |= (abyData[DOUBLE_LSB_OFFSET] << 8);
5610
5611
0
    return nValue;
5612
0
}
5613
5614
/************************************************************************/
5615
/*                     OGRGF_NeedSwithArcOrder()                        */
5616
/************************************************************************/
5617
5618
// We need to define a full ordering between starting point and ending point
5619
// whatever it is.
5620
static bool OGRGF_NeedSwithArcOrder(double x0, double y0, double x2, double y2)
5621
0
{
5622
0
    return x0 < x2 || (x0 == x2 && y0 < y2);
5623
0
}
5624
5625
/************************************************************************/
5626
/*                         curveToLineString()                          */
5627
/************************************************************************/
5628
5629
/* clang-format off */
5630
/**
5631
 * \brief Converts an arc circle into an approximate line string
5632
 *
5633
 * The arc circle is defined by a first point, an intermediate point and a
5634
 * final point.
5635
 *
5636
 * The provided dfMaxAngleStepSizeDegrees is a hint. The discretization
5637
 * algorithm may pick a slightly different value.
5638
 *
5639
 * So as to avoid gaps when rendering curve polygons that share common arcs,
5640
 * this method is guaranteed to return a line with reversed vertex if called
5641
 * with inverted first and final point, and identical intermediate point.
5642
 *
5643
 * @param x0 x of first point
5644
 * @param y0 y of first point
5645
 * @param z0 z of first point
5646
 * @param x1 x of intermediate point
5647
 * @param y1 y of intermediate point
5648
 * @param z1 z of intermediate point
5649
 * @param x2 x of final point
5650
 * @param y2 y of final point
5651
 * @param z2 z of final point
5652
 * @param bHasZ TRUE if z must be taken into account
5653
 * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
5654
 * arc, zero to use the default setting.
5655
 * @param papszOptions options as a null-terminated list of strings or NULL.
5656
 * Recognized options:
5657
 * <ul>
5658
 * <li>ADD_INTERMEDIATE_POINT=STEALTH/YES/NO (Default to STEALTH).
5659
 *         Determine if and how the intermediate point must be output in the
5660
 *         linestring.  If set to STEALTH, no explicit intermediate point is
5661
 *         added but its properties are encoded in low significant bits of
5662
 *         intermediate points and OGRGeometryFactory::curveFromLineString() can
5663
 *         decode them.  This is the best compromise for round-tripping in OGR
5664
 *         and better results with PostGIS
5665
 *         <a href="http://postgis.org/docs/ST_LineToCurve.html">ST_LineToCurve()</a>.
5666
 *         If set to YES, the intermediate point is explicitly added to the
5667
 *         linestring. If set to NO, the intermediate point is not explicitly
5668
 *         added.
5669
 * </li>
5670
 * </ul>
5671
 *
5672
 * @return the converted geometry (ownership to caller).
5673
 *
5674
 */
5675
/* clang-format on */
5676
5677
OGRLineString *OGRGeometryFactory::curveToLineString(
5678
    double x0, double y0, double z0, double x1, double y1, double z1, double x2,
5679
    double y2, double z2, int bHasZ, double dfMaxAngleStepSizeDegrees,
5680
    const char *const *papszOptions)
5681
0
{
5682
    // So as to make sure the same curve followed in both direction results
5683
    // in perfectly(=binary identical) symmetrical points.
5684
0
    if (OGRGF_NeedSwithArcOrder(x0, y0, x2, y2))
5685
0
    {
5686
0
        OGRLineString *poLS =
5687
0
            curveToLineString(x2, y2, z2, x1, y1, z1, x0, y0, z0, bHasZ,
5688
0
                              dfMaxAngleStepSizeDegrees, papszOptions);
5689
0
        poLS->reversePoints();
5690
0
        return poLS;
5691
0
    }
5692
5693
0
    double R = 0.0;
5694
0
    double cx = 0.0;
5695
0
    double cy = 0.0;
5696
0
    double alpha0 = 0.0;
5697
0
    double alpha1 = 0.0;
5698
0
    double alpha2 = 0.0;
5699
5700
0
    OGRLineString *poLine = new OGRLineString();
5701
0
    bool bIsArc = true;
5702
0
    if (!GetCurveParameters(x0, y0, x1, y1, x2, y2, R, cx, cy, alpha0, alpha1,
5703
0
                            alpha2))
5704
0
    {
5705
0
        bIsArc = false;
5706
0
        cx = 0.0;
5707
0
        cy = 0.0;
5708
0
        R = 0.0;
5709
0
        alpha0 = 0.0;
5710
0
        alpha1 = 0.0;
5711
0
        alpha2 = 0.0;
5712
0
    }
5713
5714
0
    const int nSign = alpha1 >= alpha0 ? 1 : -1;
5715
5716
    // support default arc step setting.
5717
0
    if (dfMaxAngleStepSizeDegrees < 1e-6)
5718
0
    {
5719
0
        dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
5720
0
    }
5721
5722
0
    double dfStep = dfMaxAngleStepSizeDegrees / 180 * M_PI;
5723
0
    if (dfStep <= 0.01 / 180 * M_PI)
5724
0
    {
5725
0
        CPLDebug("OGR", "Too small arc step size: limiting to 0.01 degree.");
5726
0
        dfStep = 0.01 / 180 * M_PI;
5727
0
    }
5728
5729
0
    dfStep *= nSign;
5730
5731
0
    if (bHasZ)
5732
0
        poLine->addPoint(x0, y0, z0);
5733
0
    else
5734
0
        poLine->addPoint(x0, y0);
5735
5736
0
    bool bAddIntermediatePoint = false;
5737
0
    bool bStealth = true;
5738
0
    for (const char *const *papszIter = papszOptions; papszIter && *papszIter;
5739
0
         papszIter++)
5740
0
    {
5741
0
        char *pszKey = nullptr;
5742
0
        const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
5743
0
        if (pszKey != nullptr && EQUAL(pszKey, "ADD_INTERMEDIATE_POINT"))
5744
0
        {
5745
0
            if (EQUAL(pszValue, "YES") || EQUAL(pszValue, "TRUE") ||
5746
0
                EQUAL(pszValue, "ON"))
5747
0
            {
5748
0
                bAddIntermediatePoint = true;
5749
0
                bStealth = false;
5750
0
            }
5751
0
            else if (EQUAL(pszValue, "NO") || EQUAL(pszValue, "FALSE") ||
5752
0
                     EQUAL(pszValue, "OFF"))
5753
0
            {
5754
0
                bAddIntermediatePoint = false;
5755
0
                bStealth = false;
5756
0
            }
5757
0
            else if (EQUAL(pszValue, "STEALTH"))
5758
0
            {
5759
                // default.
5760
0
            }
5761
0
        }
5762
0
        else
5763
0
        {
5764
0
            CPLError(CE_Warning, CPLE_NotSupported, "Unsupported option: %s",
5765
0
                     *papszIter);
5766
0
        }
5767
0
        CPLFree(pszKey);
5768
0
    }
5769
5770
0
    if (!bIsArc || bAddIntermediatePoint)
5771
0
    {
5772
0
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z1, bHasZ, alpha0,
5773
0
                                    alpha1, dfStep, FALSE);
5774
5775
0
        if (bHasZ)
5776
0
            poLine->addPoint(x1, y1, z1);
5777
0
        else
5778
0
            poLine->addPoint(x1, y1);
5779
5780
0
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z1, z2, bHasZ, alpha1,
5781
0
                                    alpha2, dfStep, FALSE);
5782
0
    }
5783
0
    else
5784
0
    {
5785
0
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z2, bHasZ, alpha0,
5786
0
                                    alpha2, dfStep, bStealth);
5787
5788
0
        if (bStealth && poLine->getNumPoints() > 6)
5789
0
        {
5790
            // 'Hide' the angle of the intermediate point in the 8
5791
            // low-significant bits of the x, y of the first 2 computed points
5792
            // (so 32 bits), then put 0xFF, and on the last couple points put
5793
            // again the angle but in reverse order, so that overall the
5794
            // low-significant bits of all the points are symmetrical w.r.t the
5795
            // mid-point.
5796
0
            const double dfRatio = (alpha1 - alpha0) / (alpha2 - alpha0);
5797
0
            double dfAlphaRatio = 0.5 + HIDDEN_ALPHA_SCALE * dfRatio;
5798
0
            if (dfAlphaRatio < 0.0)
5799
0
            {
5800
0
                CPLError(CE_Warning, CPLE_AppDefined, "AlphaRation < 0: %lf",
5801
0
                         dfAlphaRatio);
5802
0
                dfAlphaRatio *= -1;
5803
0
            }
5804
0
            else if (dfAlphaRatio >= std::numeric_limits<GUInt32>::max() ||
5805
0
                     std::isnan(dfAlphaRatio))
5806
0
            {
5807
0
                CPLError(CE_Warning, CPLE_AppDefined,
5808
0
                         "AlphaRatio too large: %lf", dfAlphaRatio);
5809
0
                dfAlphaRatio = std::numeric_limits<GUInt32>::max();
5810
0
            }
5811
0
            const GUInt32 nAlphaRatio = static_cast<GUInt32>(dfAlphaRatio);
5812
0
            const GUInt16 nAlphaRatioLow = nAlphaRatio & HIDDEN_ALPHA_HALF_MASK;
5813
0
            const GUInt16 nAlphaRatioHigh =
5814
0
                nAlphaRatio >> HIDDEN_ALPHA_HALF_WIDTH;
5815
            // printf("alpha0=%f, alpha1=%f, alpha2=%f, dfRatio=%f, "/*ok*/
5816
            //        "nAlphaRatio = %u\n",
5817
            //        alpha0, alpha1, alpha2, dfRatio, nAlphaRatio);
5818
5819
0
            CPLAssert(((poLine->getNumPoints() - 1 - 6) % 2) == 0);
5820
5821
0
            for (int i = 1; i + 1 < poLine->getNumPoints(); i += 2)
5822
0
            {
5823
0
                GUInt16 nVal = 0xFFFF;
5824
5825
0
                double dfX = poLine->getX(i);
5826
0
                double dfY = poLine->getY(i);
5827
0
                if (i == 1)
5828
0
                    nVal = nAlphaRatioLow;
5829
0
                else if (i == poLine->getNumPoints() - 2)
5830
0
                    nVal = nAlphaRatioHigh;
5831
0
                OGRGF_SetHiddenValue(nVal, dfX, dfY);
5832
0
                poLine->setPoint(i, dfX, dfY);
5833
5834
0
                dfX = poLine->getX(i + 1);
5835
0
                dfY = poLine->getY(i + 1);
5836
0
                if (i == 1)
5837
0
                    nVal = nAlphaRatioHigh;
5838
0
                else if (i == poLine->getNumPoints() - 2)
5839
0
                    nVal = nAlphaRatioLow;
5840
0
                OGRGF_SetHiddenValue(nVal, dfX, dfY);
5841
0
                poLine->setPoint(i + 1, dfX, dfY);
5842
0
            }
5843
0
        }
5844
0
    }
5845
5846
0
    if (bHasZ)
5847
0
        poLine->addPoint(x2, y2, z2);
5848
0
    else
5849
0
        poLine->addPoint(x2, y2);
5850
5851
0
    return poLine;
5852
0
}
5853
5854
/************************************************************************/
5855
/*                         OGRGF_FixAngle()                             */
5856
/************************************************************************/
5857
5858
// Fix dfAngle by offsets of 2 PI so that it lies between dfAngleStart and
5859
// dfAngleStop, whatever their respective order.
5860
static double OGRGF_FixAngle(double dfAngleStart, double dfAngleStop,
5861
                             double dfAngle)
5862
0
{
5863
0
    if (dfAngleStart < dfAngleStop)
5864
0
    {
5865
0
        while (dfAngle <= dfAngleStart + 1e-8)
5866
0
            dfAngle += 2 * M_PI;
5867
0
    }
5868
0
    else
5869
0
    {
5870
0
        while (dfAngle >= dfAngleStart - 1e-8)
5871
0
            dfAngle -= 2 * M_PI;
5872
0
    }
5873
0
    return dfAngle;
5874
0
}
5875
5876
/************************************************************************/
5877
/*                         OGRGF_DetectArc()                            */
5878
/************************************************************************/
5879
5880
// #define VERBOSE_DEBUG_CURVEFROMLINESTRING
5881
5882
static inline bool IS_ALMOST_INTEGER(double x)
5883
0
{
5884
0
    const double val = fabs(x - floor(x + 0.5));
5885
0
    return val < 1.0e-8;
5886
0
}
5887
5888
static int OGRGF_DetectArc(const OGRLineString *poLS, int i,
5889
                           OGRCompoundCurve *&poCC, OGRCircularString *&poCS,
5890
                           OGRLineString *&poLSNew)
5891
0
{
5892
0
    if (i + 3 >= poLS->getNumPoints())
5893
0
        return -1;
5894
5895
0
    OGRPoint p0;
5896
0
    OGRPoint p1;
5897
0
    OGRPoint p2;
5898
0
    poLS->getPoint(i, &p0);
5899
0
    poLS->getPoint(i + 1, &p1);
5900
0
    poLS->getPoint(i + 2, &p2);
5901
0
    double R_1 = 0.0;
5902
0
    double cx_1 = 0.0;
5903
0
    double cy_1 = 0.0;
5904
0
    double alpha0_1 = 0.0;
5905
0
    double alpha1_1 = 0.0;
5906
0
    double alpha2_1 = 0.0;
5907
0
    if (!(OGRGeometryFactory::GetCurveParameters(
5908
0
              p0.getX(), p0.getY(), p1.getX(), p1.getY(), p2.getX(), p2.getY(),
5909
0
              R_1, cx_1, cy_1, alpha0_1, alpha1_1, alpha2_1) &&
5910
0
          fabs(alpha2_1 - alpha0_1) < 2.0 * 20.0 / 180.0 * M_PI))
5911
0
    {
5912
0
        return -1;
5913
0
    }
5914
5915
0
    const double dfDeltaAlpha10 = alpha1_1 - alpha0_1;
5916
0
    const double dfDeltaAlpha21 = alpha2_1 - alpha1_1;
5917
0
    const double dfMaxDeltaAlpha =
5918
0
        std::max(fabs(dfDeltaAlpha10), fabs(dfDeltaAlpha21));
5919
0
    GUInt32 nAlphaRatioRef =
5920
0
        OGRGF_GetHiddenValue(p1.getX(), p1.getY()) |
5921
0
        (OGRGF_GetHiddenValue(p2.getX(), p2.getY()) << HIDDEN_ALPHA_HALF_WIDTH);
5922
0
    bool bFoundFFFFFFFFPattern = false;
5923
0
    bool bFoundReversedAlphaRatioRef = false;
5924
0
    bool bValidAlphaRatio = nAlphaRatioRef > 0 && nAlphaRatioRef < 0xFFFFFFFF;
5925
0
    int nCountValidAlphaRatio = 1;
5926
5927
0
    double dfScale = std::max(1.0, R_1);
5928
0
    dfScale = std::max(dfScale, fabs(cx_1));
5929
0
    dfScale = std::max(dfScale, fabs(cy_1));
5930
0
    dfScale = pow(10.0, ceil(log10(dfScale)));
5931
0
    const double dfInvScale = 1.0 / dfScale;
5932
5933
0
    const int bInitialConstantStep =
5934
0
        (fabs(dfDeltaAlpha10 - dfDeltaAlpha21) / dfMaxDeltaAlpha) < 1.0e-4;
5935
0
    const double dfDeltaEpsilon =
5936
0
        bInitialConstantStep ? dfMaxDeltaAlpha * 1e-4 : dfMaxDeltaAlpha / 10;
5937
5938
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5939
    printf("----------------------------\n");             /*ok*/
5940
    printf("Curve beginning at offset i = %d\n", i);      /*ok*/
5941
    printf("Initial alpha ratio = %u\n", nAlphaRatioRef); /*ok*/
5942
    /*ok*/ printf("Initial R = %.16g, cx = %.16g, cy = %.16g\n", R_1, cx_1,
5943
                  cy_1);
5944
    printf("dfScale = %f\n", dfScale);   /*ok*/
5945
    printf("bInitialConstantStep = %d, " /*ok*/
5946
           "fabs(dfDeltaAlpha10 - dfDeltaAlpha21)=%.8g, "
5947
           "dfMaxDeltaAlpha = %.8f, "
5948
           "dfDeltaEpsilon = %.8f (%.8f)\n",
5949
           bInitialConstantStep, fabs(dfDeltaAlpha10 - dfDeltaAlpha21),
5950
           dfMaxDeltaAlpha, dfDeltaEpsilon, 1.0 / 180.0 * M_PI);
5951
#endif
5952
0
    int iMidPoint = -1;
5953
0
    double dfLastValidAlpha = alpha2_1;
5954
5955
0
    double dfLastLogRelDiff = 0;
5956
5957
0
    OGRPoint p3;
5958
0
    int j = i + 1;  // Used after for.
5959
0
    for (; j + 2 < poLS->getNumPoints(); j++)
5960
0
    {
5961
0
        poLS->getPoint(j, &p1);
5962
0
        poLS->getPoint(j + 1, &p2);
5963
0
        poLS->getPoint(j + 2, &p3);
5964
0
        double R_2 = 0.0;
5965
0
        double cx_2 = 0.0;
5966
0
        double cy_2 = 0.0;
5967
0
        double alpha0_2 = 0.0;
5968
0
        double alpha1_2 = 0.0;
5969
0
        double alpha2_2 = 0.0;
5970
        // Check that the new candidate arc shares the same
5971
        // radius, center and winding order.
5972
0
        if (!(OGRGeometryFactory::GetCurveParameters(
5973
0
                p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(),
5974
0
                p3.getY(), R_2, cx_2, cy_2, alpha0_2, alpha1_2, alpha2_2)))
5975
0
        {
5976
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5977
            printf("End of curve at j=%d\n : straight line", j); /*ok*/
5978
#endif
5979
0
            break;
5980
0
        }
5981
5982
0
        const double dfRelDiffR = fabs(R_1 - R_2) * dfInvScale;
5983
0
        const double dfRelDiffCx = fabs(cx_1 - cx_2) * dfInvScale;
5984
0
        const double dfRelDiffCy = fabs(cy_1 - cy_2) * dfInvScale;
5985
5986
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5987
        printf("j=%d: R = %.16g, cx = %.16g, cy = %.16g, " /*ok*/
5988
               "rel_diff_R=%.8g rel_diff_cx=%.8g rel_diff_cy=%.8g\n",
5989
               j, R_2, cx_2, cy_2, dfRelDiffR, dfRelDiffCx, dfRelDiffCy);
5990
#endif
5991
5992
0
        if (dfRelDiffR > 1.0e-7 || dfRelDiffCx > 1.0e-7 ||
5993
0
            dfRelDiffCy > 1.0e-7 ||
5994
0
            dfDeltaAlpha10 * (alpha1_2 - alpha0_2) < 0.0)
5995
0
        {
5996
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5997
            printf("End of curve at j=%d\n", j); /*ok*/
5998
#endif
5999
0
            break;
6000
0
        }
6001
6002
0
        if (dfRelDiffR > 0.0 && dfRelDiffCx > 0.0 && dfRelDiffCy > 0.0)
6003
0
        {
6004
0
            const double dfLogRelDiff = std::min(
6005
0
                std::min(fabs(log10(dfRelDiffR)), fabs(log10(dfRelDiffCx))),
6006
0
                fabs(log10(dfRelDiffCy)));
6007
            // printf("dfLogRelDiff = %f, dfLastLogRelDiff=%f, "/*ok*/
6008
            //        "dfLogRelDiff - dfLastLogRelDiff=%f\n",
6009
            //         dfLogRelDiff, dfLastLogRelDiff,
6010
            //         dfLogRelDiff - dfLastLogRelDiff);
6011
0
            if (dfLogRelDiff > 0.0 && dfLastLogRelDiff >= 8.0 &&
6012
0
                dfLogRelDiff <= 8.0 && dfLogRelDiff < dfLastLogRelDiff - 2.0)
6013
0
            {
6014
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6015
                printf("End of curve at j=%d. Significant different in " /*ok*/
6016
                       "relative error w.r.t previous points\n",
6017
                       j);
6018
#endif
6019
0
                break;
6020
0
            }
6021
0
            dfLastLogRelDiff = dfLogRelDiff;
6022
0
        }
6023
6024
0
        const double dfStep10 = fabs(alpha1_2 - alpha0_2);
6025
0
        const double dfStep21 = fabs(alpha2_2 - alpha1_2);
6026
        // Check that the angle step is consistent with the original step.
6027
0
        if (!(dfStep10 < 2.0 * dfMaxDeltaAlpha &&
6028
0
              dfStep21 < 2.0 * dfMaxDeltaAlpha))
6029
0
        {
6030
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6031
            printf("End of curve at j=%d: dfStep10=%f, dfStep21=%f, " /*ok*/
6032
                   "2*dfMaxDeltaAlpha=%f\n",
6033
                   j, dfStep10, dfStep21, 2 * dfMaxDeltaAlpha);
6034
#endif
6035
0
            break;
6036
0
        }
6037
6038
0
        if (bValidAlphaRatio && j > i + 1 && (i % 2) != (j % 2))
6039
0
        {
6040
0
            const GUInt32 nAlphaRatioReversed =
6041
0
                (OGRGF_GetHiddenValue(p1.getX(), p1.getY())
6042
0
                 << HIDDEN_ALPHA_HALF_WIDTH) |
6043
0
                (OGRGF_GetHiddenValue(p2.getX(), p2.getY()));
6044
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6045
            printf("j=%d, nAlphaRatioReversed = %u\n", /*ok*/
6046
                   j, nAlphaRatioReversed);
6047
#endif
6048
0
            if (!bFoundFFFFFFFFPattern && nAlphaRatioReversed == 0xFFFFFFFF)
6049
0
            {
6050
0
                bFoundFFFFFFFFPattern = true;
6051
0
                nCountValidAlphaRatio++;
6052
0
            }
6053
0
            else if (bFoundFFFFFFFFPattern && !bFoundReversedAlphaRatioRef &&
6054
0
                     nAlphaRatioReversed == 0xFFFFFFFF)
6055
0
            {
6056
0
                nCountValidAlphaRatio++;
6057
0
            }
6058
0
            else if (bFoundFFFFFFFFPattern && !bFoundReversedAlphaRatioRef &&
6059
0
                     nAlphaRatioReversed == nAlphaRatioRef)
6060
0
            {
6061
0
                bFoundReversedAlphaRatioRef = true;
6062
0
                nCountValidAlphaRatio++;
6063
0
            }
6064
0
            else
6065
0
            {
6066
0
                if (bInitialConstantStep &&
6067
0
                    fabs(dfLastValidAlpha - alpha0_1) >= M_PI &&
6068
0
                    nCountValidAlphaRatio > 10)
6069
0
                {
6070
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6071
                    printf("End of curve at j=%d: " /*ok*/
6072
                           "fabs(dfLastValidAlpha - alpha0_1)=%f, "
6073
                           "nCountValidAlphaRatio=%d\n",
6074
                           j, fabs(dfLastValidAlpha - alpha0_1),
6075
                           nCountValidAlphaRatio);
6076
#endif
6077
0
                    if (dfLastValidAlpha - alpha0_1 > 0)
6078
0
                    {
6079
0
                        while (dfLastValidAlpha - alpha0_1 - dfMaxDeltaAlpha -
6080
0
                                   M_PI >
6081
0
                               -dfMaxDeltaAlpha / 10)
6082
0
                        {
6083
0
                            dfLastValidAlpha -= dfMaxDeltaAlpha;
6084
0
                            j--;
6085
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6086
                            printf(/*ok*/
6087
                                   "--> corrected as fabs(dfLastValidAlpha - "
6088
                                   "alpha0_1)=%f, j=%d\n",
6089
                                   fabs(dfLastValidAlpha - alpha0_1), j);
6090
#endif
6091
0
                        }
6092
0
                    }
6093
0
                    else
6094
0
                    {
6095
0
                        while (dfLastValidAlpha - alpha0_1 + dfMaxDeltaAlpha +
6096
0
                                   M_PI <
6097
0
                               dfMaxDeltaAlpha / 10)
6098
0
                        {
6099
0
                            dfLastValidAlpha += dfMaxDeltaAlpha;
6100
0
                            j--;
6101
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6102
                            printf(/*ok*/
6103
                                   "--> corrected as fabs(dfLastValidAlpha - "
6104
                                   "alpha0_1)=%f, j=%d\n",
6105
                                   fabs(dfLastValidAlpha - alpha0_1), j);
6106
#endif
6107
0
                        }
6108
0
                    }
6109
0
                    poLS->getPoint(j + 1, &p2);
6110
0
                    break;
6111
0
                }
6112
6113
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6114
                printf("j=%d, nAlphaRatioReversed = %u --> inconsistent " /*ok*/
6115
                       "values across arc. Don't use it\n",
6116
                       j, nAlphaRatioReversed);
6117
#endif
6118
0
                bValidAlphaRatio = false;
6119
0
            }
6120
0
        }
6121
6122
        // Correct current end angle, consistently with start angle.
6123
0
        dfLastValidAlpha = OGRGF_FixAngle(alpha0_1, alpha1_1, alpha2_2);
6124
6125
        // Try to detect the precise intermediate point of the
6126
        // arc circle by detecting irregular angle step
6127
        // This is OK if we don't detect the right point or fail
6128
        // to detect it.
6129
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6130
        printf("j=%d A(0,1)-maxDelta=%.8f A(1,2)-maxDelta=%.8f " /*ok*/
6131
               "x1=%.8f y1=%.8f x2=%.8f y2=%.8f x3=%.8f y3=%.8f\n",
6132
               j, fabs(dfStep10 - dfMaxDeltaAlpha),
6133
               fabs(dfStep21 - dfMaxDeltaAlpha), p1.getX(), p1.getY(),
6134
               p2.getX(), p2.getY(), p3.getX(), p3.getY());
6135
#endif
6136
0
        if (j > i + 1 && iMidPoint < 0 && dfDeltaEpsilon < 1.0 / 180.0 * M_PI)
6137
0
        {
6138
0
            if (fabs(dfStep10 - dfMaxDeltaAlpha) > dfDeltaEpsilon)
6139
0
                iMidPoint = j + ((bInitialConstantStep) ? 0 : 1);
6140
0
            else if (fabs(dfStep21 - dfMaxDeltaAlpha) > dfDeltaEpsilon)
6141
0
                iMidPoint = j + ((bInitialConstantStep) ? 1 : 2);
6142
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6143
            if (iMidPoint >= 0)
6144
            {
6145
                OGRPoint pMid;
6146
                poLS->getPoint(iMidPoint, &pMid);
6147
                printf("Midpoint detected at j = %d, iMidPoint = %d, " /*ok*/
6148
                       "x=%.8f y=%.8f\n",
6149
                       j, iMidPoint, pMid.getX(), pMid.getY());
6150
            }
6151
#endif
6152
0
        }
6153
0
    }
6154
6155
    // Take a minimum threshold of consecutive points
6156
    // on the arc to avoid false positives.
6157
0
    if (j < i + 3)
6158
0
        return -1;
6159
6160
0
    bValidAlphaRatio &= bFoundFFFFFFFFPattern && bFoundReversedAlphaRatioRef;
6161
6162
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6163
    printf("bValidAlphaRatio=%d bFoundFFFFFFFFPattern=%d, " /*ok*/
6164
           "bFoundReversedAlphaRatioRef=%d\n",
6165
           static_cast<int>(bValidAlphaRatio),
6166
           static_cast<int>(bFoundFFFFFFFFPattern),
6167
           static_cast<int>(bFoundReversedAlphaRatioRef));
6168
    printf("alpha0_1=%f dfLastValidAlpha=%f\n", /*ok*/
6169
           alpha0_1, dfLastValidAlpha);
6170
#endif
6171
6172
0
    if (poLSNew != nullptr)
6173
0
    {
6174
0
        double dfScale2 = std::max(1.0, fabs(p0.getX()));
6175
0
        dfScale2 = std::max(dfScale2, fabs(p0.getY()));
6176
        // Not strictly necessary, but helps having 'clean' lines without
6177
        // duplicated points.
6178
0
        constexpr double dfToleranceEps =
6179
0
            OGRCompoundCurve::DEFAULT_TOLERANCE_EPSILON;
6180
0
        if (fabs(poLSNew->getX(poLSNew->getNumPoints() - 1) - p0.getX()) >
6181
0
                dfToleranceEps * dfScale2 ||
6182
0
            fabs(poLSNew->getY(poLSNew->getNumPoints() - 1) - p0.getY()) >
6183
0
                dfToleranceEps * dfScale2)
6184
0
            poLSNew->addPoint(&p0);
6185
0
        if (poLSNew->getNumPoints() >= 2)
6186
0
        {
6187
0
            if (poCC == nullptr)
6188
0
                poCC = new OGRCompoundCurve();
6189
0
            poCC->addCurveDirectly(poLSNew);
6190
0
        }
6191
0
        else
6192
0
            delete poLSNew;
6193
0
        poLSNew = nullptr;
6194
0
    }
6195
6196
0
    if (poCS == nullptr)
6197
0
    {
6198
0
        poCS = new OGRCircularString();
6199
0
        poCS->addPoint(&p0);
6200
0
    }
6201
6202
0
    OGRPoint *poFinalPoint = (j + 2 >= poLS->getNumPoints()) ? &p3 : &p2;
6203
6204
0
    double dfXMid = 0.0;
6205
0
    double dfYMid = 0.0;
6206
0
    double dfZMid = 0.0;
6207
0
    if (bValidAlphaRatio)
6208
0
    {
6209
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6210
        printf("Using alpha ratio...\n"); /*ok*/
6211
#endif
6212
0
        double dfAlphaMid = 0.0;
6213
0
        if (OGRGF_NeedSwithArcOrder(p0.getX(), p0.getY(), poFinalPoint->getX(),
6214
0
                                    poFinalPoint->getY()))
6215
0
        {
6216
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6217
            printf("Switching angles\n"); /*ok*/
6218
#endif
6219
0
            dfAlphaMid = dfLastValidAlpha + nAlphaRatioRef *
6220
0
                                                (alpha0_1 - dfLastValidAlpha) /
6221
0
                                                HIDDEN_ALPHA_SCALE;
6222
0
            dfAlphaMid = OGRGF_FixAngle(alpha0_1, dfLastValidAlpha, dfAlphaMid);
6223
0
        }
6224
0
        else
6225
0
        {
6226
0
            dfAlphaMid = alpha0_1 + nAlphaRatioRef *
6227
0
                                        (dfLastValidAlpha - alpha0_1) /
6228
0
                                        HIDDEN_ALPHA_SCALE;
6229
0
        }
6230
6231
0
        dfXMid = cx_1 + R_1 * cos(dfAlphaMid);
6232
0
        dfYMid = cy_1 + R_1 * sin(dfAlphaMid);
6233
6234
0
        if (poLS->getCoordinateDimension() == 3)
6235
0
        {
6236
0
            double dfLastAlpha = 0.0;
6237
0
            double dfLastZ = 0.0;
6238
0
            int k = i;  // Used after for.
6239
0
            for (; k < j + 2; k++)
6240
0
            {
6241
0
                OGRPoint p;
6242
0
                poLS->getPoint(k, &p);
6243
0
                double dfAlpha = atan2(p.getY() - cy_1, p.getX() - cx_1);
6244
0
                dfAlpha = OGRGF_FixAngle(alpha0_1, dfLastValidAlpha, dfAlpha);
6245
0
                if (k > i &&
6246
0
                    ((dfAlpha < dfLastValidAlpha && dfAlphaMid < dfAlpha) ||
6247
0
                     (dfAlpha > dfLastValidAlpha && dfAlphaMid > dfAlpha)))
6248
0
                {
6249
0
                    const double dfRatio =
6250
0
                        (dfAlphaMid - dfLastAlpha) / (dfAlpha - dfLastAlpha);
6251
0
                    dfZMid = (1 - dfRatio) * dfLastZ + dfRatio * p.getZ();
6252
0
                    break;
6253
0
                }
6254
0
                dfLastAlpha = dfAlpha;
6255
0
                dfLastZ = p.getZ();
6256
0
            }
6257
0
            if (k == j + 2)
6258
0
                dfZMid = dfLastZ;
6259
0
            if (IS_ALMOST_INTEGER(dfZMid))
6260
0
                dfZMid = static_cast<int>(floor(dfZMid + 0.5));
6261
0
        }
6262
6263
        // A few rounding strategies in case the mid point was at "exact"
6264
        // coordinates.
6265
0
        if (R_1 > 1e-5)
6266
0
        {
6267
0
            const bool bStartEndInteger =
6268
0
                IS_ALMOST_INTEGER(p0.getX()) && IS_ALMOST_INTEGER(p0.getY()) &&
6269
0
                IS_ALMOST_INTEGER(poFinalPoint->getX()) &&
6270
0
                IS_ALMOST_INTEGER(poFinalPoint->getY());
6271
0
            if (bStartEndInteger &&
6272
0
                fabs(dfXMid - floor(dfXMid + 0.5)) / dfScale < 1e-4 &&
6273
0
                fabs(dfYMid - floor(dfYMid + 0.5)) / dfScale < 1e-4)
6274
0
            {
6275
0
                dfXMid = static_cast<int>(floor(dfXMid + 0.5));
6276
0
                dfYMid = static_cast<int>(floor(dfYMid + 0.5));
6277
                // Sometimes rounding to closest is not best approach
6278
                // Try neighbouring integers to look for the one that
6279
                // minimize the error w.r.t to the arc center
6280
                // But only do that if the radius is greater than
6281
                // the magnitude of the delta that we will try!
6282
0
                double dfBestRError =
6283
0
                    fabs(R_1 - DISTANCE(dfXMid, dfYMid, cx_1, cy_1));
6284
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6285
                printf("initial_error=%f\n", dfBestRError); /*ok*/
6286
#endif
6287
0
                int iBestX = 0;
6288
0
                int iBestY = 0;
6289
0
                if (dfBestRError > 0.001 && R_1 > 2)
6290
0
                {
6291
0
                    int nSearchRadius = 1;
6292
                    // Extend the search radius if the arc circle radius
6293
                    // is much higher than the coordinate values.
6294
0
                    double dfMaxCoords =
6295
0
                        std::max(fabs(p0.getX()), fabs(p0.getY()));
6296
0
                    dfMaxCoords = std::max(dfMaxCoords, poFinalPoint->getX());
6297
0
                    dfMaxCoords = std::max(dfMaxCoords, poFinalPoint->getY());
6298
0
                    dfMaxCoords = std::max(dfMaxCoords, dfXMid);
6299
0
                    dfMaxCoords = std::max(dfMaxCoords, dfYMid);
6300
0
                    if (R_1 > dfMaxCoords * 1000)
6301
0
                        nSearchRadius = 100;
6302
0
                    else if (R_1 > dfMaxCoords * 10)
6303
0
                        nSearchRadius = 10;
6304
0
                    for (int iY = -nSearchRadius; iY <= nSearchRadius; iY++)
6305
0
                    {
6306
0
                        for (int iX = -nSearchRadius; iX <= nSearchRadius; iX++)
6307
0
                        {
6308
0
                            const double dfCandidateX = dfXMid + iX;
6309
0
                            const double dfCandidateY = dfYMid + iY;
6310
0
                            if (fabs(dfCandidateX - p0.getX()) < 1e-8 &&
6311
0
                                fabs(dfCandidateY - p0.getY()) < 1e-8)
6312
0
                                continue;
6313
0
                            if (fabs(dfCandidateX - poFinalPoint->getX()) <
6314
0
                                    1e-8 &&
6315
0
                                fabs(dfCandidateY - poFinalPoint->getY()) <
6316
0
                                    1e-8)
6317
0
                                continue;
6318
0
                            const double dfRError =
6319
0
                                fabs(R_1 - DISTANCE(dfCandidateX, dfCandidateY,
6320
0
                                                    cx_1, cy_1));
6321
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6322
                            printf("x=%d y=%d error=%f besterror=%f\n", /*ok*/
6323
                                   static_cast<int>(dfXMid + iX),
6324
                                   static_cast<int>(dfYMid + iY), dfRError,
6325
                                   dfBestRError);
6326
#endif
6327
0
                            if (dfRError < dfBestRError)
6328
0
                            {
6329
0
                                iBestX = iX;
6330
0
                                iBestY = iY;
6331
0
                                dfBestRError = dfRError;
6332
0
                            }
6333
0
                        }
6334
0
                    }
6335
0
                }
6336
0
                dfXMid += iBestX;
6337
0
                dfYMid += iBestY;
6338
0
            }
6339
0
            else
6340
0
            {
6341
                // Limit the number of significant figures in decimal
6342
                // representation.
6343
0
                if (fabs(dfXMid) < 100000000.0)
6344
0
                {
6345
0
                    dfXMid =
6346
0
                        static_cast<GIntBig>(floor(dfXMid * 100000000 + 0.5)) /
6347
0
                        100000000.0;
6348
0
                }
6349
0
                if (fabs(dfYMid) < 100000000.0)
6350
0
                {
6351
0
                    dfYMid =
6352
0
                        static_cast<GIntBig>(floor(dfYMid * 100000000 + 0.5)) /
6353
0
                        100000000.0;
6354
0
                }
6355
0
            }
6356
0
        }
6357
6358
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6359
        printf("dfAlphaMid=%f, x_mid = %f, y_mid = %f\n", /*ok*/
6360
               dfLastValidAlpha, dfXMid, dfYMid);
6361
#endif
6362
0
    }
6363
6364
    // If this is a full circle of a non-polygonal zone, we must
6365
    // use a 5-point representation to keep the winding order.
6366
0
    if (p0.Equals(poFinalPoint) &&
6367
0
        !EQUAL(poLS->getGeometryName(), "LINEARRING"))
6368
0
    {
6369
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6370
        printf("Full circle of a non-polygonal zone\n"); /*ok*/
6371
#endif
6372
0
        poLS->getPoint((i + j + 2) / 4, &p1);
6373
0
        poCS->addPoint(&p1);
6374
0
        if (bValidAlphaRatio)
6375
0
        {
6376
0
            p1.setX(dfXMid);
6377
0
            p1.setY(dfYMid);
6378
0
            if (poLS->getCoordinateDimension() == 3)
6379
0
                p1.setZ(dfZMid);
6380
0
        }
6381
0
        else
6382
0
        {
6383
0
            poLS->getPoint((i + j + 1) / 2, &p1);
6384
0
        }
6385
0
        poCS->addPoint(&p1);
6386
0
        poLS->getPoint(3 * (i + j + 2) / 4, &p1);
6387
0
        poCS->addPoint(&p1);
6388
0
    }
6389
6390
0
    else if (bValidAlphaRatio)
6391
0
    {
6392
0
        p1.setX(dfXMid);
6393
0
        p1.setY(dfYMid);
6394
0
        if (poLS->getCoordinateDimension() == 3)
6395
0
            p1.setZ(dfZMid);
6396
0
        poCS->addPoint(&p1);
6397
0
    }
6398
6399
    // If we have found a candidate for a precise intermediate
6400
    // point, use it.
6401
0
    else if (iMidPoint >= 1 && iMidPoint < j)
6402
0
    {
6403
0
        poLS->getPoint(iMidPoint, &p1);
6404
0
        poCS->addPoint(&p1);
6405
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6406
        printf("Using detected midpoint...\n");                   /*ok*/
6407
        printf("x_mid = %f, y_mid = %f\n", p1.getX(), p1.getY()); /*ok*/
6408
#endif
6409
0
    }
6410
    // Otherwise pick up the mid point between both extremities.
6411
0
    else
6412
0
    {
6413
0
        poLS->getPoint((i + j + 1) / 2, &p1);
6414
0
        poCS->addPoint(&p1);
6415
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6416
        printf("Pickup 'random' midpoint at index=%d...\n", /*ok*/
6417
               (i + j + 1) / 2);
6418
        printf("x_mid = %f, y_mid = %f\n", p1.getX(), p1.getY()); /*ok*/
6419
#endif
6420
0
    }
6421
0
    poCS->addPoint(poFinalPoint);
6422
6423
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6424
    printf("----------------------------\n"); /*ok*/
6425
#endif
6426
6427
0
    if (j + 2 >= poLS->getNumPoints())
6428
0
        return -2;
6429
0
    return j + 1;
6430
0
}
6431
6432
/************************************************************************/
6433
/*                        curveFromLineString()                         */
6434
/************************************************************************/
6435
6436
/**
6437
 * \brief Try to convert a linestring approximating curves into a curve.
6438
 *
6439
 * This method can return a COMPOUNDCURVE, a CIRCULARSTRING or a LINESTRING.
6440
 *
6441
 * This method is the reverse of curveFromLineString().
6442
 *
6443
 * @param poLS handle to the geometry to convert.
6444
 * @param papszOptions options as a null-terminated list of strings.
6445
 *                     Unused for now. Must be set to NULL.
6446
 *
6447
 * @return the converted geometry (ownership to caller).
6448
 *
6449
 */
6450
6451
OGRCurve *OGRGeometryFactory::curveFromLineString(
6452
    const OGRLineString *poLS, CPL_UNUSED const char *const *papszOptions)
6453
0
{
6454
0
    OGRCompoundCurve *poCC = nullptr;
6455
0
    OGRCircularString *poCS = nullptr;
6456
0
    OGRLineString *poLSNew = nullptr;
6457
0
    const int nLSNumPoints = poLS->getNumPoints();
6458
0
    const bool bIsClosed = nLSNumPoints >= 4 && poLS->get_IsClosed();
6459
0
    for (int i = 0; i < nLSNumPoints; /* nothing */)
6460
0
    {
6461
0
        const int iNewI = OGRGF_DetectArc(poLS, i, poCC, poCS, poLSNew);
6462
0
        if (iNewI == -2)
6463
0
            break;
6464
0
        if (iNewI >= 0)
6465
0
        {
6466
0
            i = iNewI;
6467
0
            continue;
6468
0
        }
6469
6470
0
        if (poCS != nullptr)
6471
0
        {
6472
0
            if (poCC == nullptr)
6473
0
                poCC = new OGRCompoundCurve();
6474
0
            poCC->addCurveDirectly(poCS);
6475
0
            poCS = nullptr;
6476
0
        }
6477
6478
0
        OGRPoint p;
6479
0
        poLS->getPoint(i, &p);
6480
0
        if (poLSNew == nullptr)
6481
0
        {
6482
0
            poLSNew = new OGRLineString();
6483
0
            poLSNew->addPoint(&p);
6484
0
        }
6485
        // Not strictly necessary, but helps having 'clean' lines without
6486
        // duplicated points.
6487
0
        else
6488
0
        {
6489
0
            double dfScale = std::max(1.0, fabs(p.getX()));
6490
0
            dfScale = std::max(dfScale, fabs(p.getY()));
6491
0
            if (bIsClosed && i == nLSNumPoints - 1)
6492
0
                dfScale = 0;
6493
0
            constexpr double dfToleranceEps =
6494
0
                OGRCompoundCurve::DEFAULT_TOLERANCE_EPSILON;
6495
0
            if (fabs(poLSNew->getX(poLSNew->getNumPoints() - 1) - p.getX()) >
6496
0
                    dfToleranceEps * dfScale ||
6497
0
                fabs(poLSNew->getY(poLSNew->getNumPoints() - 1) - p.getY()) >
6498
0
                    dfToleranceEps * dfScale)
6499
0
            {
6500
0
                poLSNew->addPoint(&p);
6501
0
            }
6502
0
        }
6503
6504
0
        i++;
6505
0
    }
6506
6507
0
    OGRCurve *poRet = nullptr;
6508
6509
0
    if (poLSNew != nullptr && poLSNew->getNumPoints() < 2)
6510
0
    {
6511
0
        delete poLSNew;
6512
0
        poLSNew = nullptr;
6513
0
        if (poCC != nullptr)
6514
0
        {
6515
0
            if (poCC->getNumCurves() == 1)
6516
0
            {
6517
0
                poRet = poCC->stealCurve(0);
6518
0
                delete poCC;
6519
0
                poCC = nullptr;
6520
0
            }
6521
0
            else
6522
0
                poRet = poCC;
6523
0
        }
6524
0
        else
6525
0
            poRet = poLS->clone();
6526
0
    }
6527
0
    else if (poCC != nullptr)
6528
0
    {
6529
0
        if (poLSNew)
6530
0
            poCC->addCurveDirectly(poLSNew);
6531
0
        else
6532
0
            poCC->addCurveDirectly(poCS);
6533
0
        poRet = poCC;
6534
0
    }
6535
0
    else if (poLSNew != nullptr)
6536
0
        poRet = poLSNew;
6537
0
    else if (poCS != nullptr)
6538
0
        poRet = poCS;
6539
0
    else
6540
0
        poRet = poLS->clone();
6541
6542
0
    poRet->assignSpatialReference(poLS->getSpatialReference());
6543
6544
0
    return poRet;
6545
0
}
6546
6547
/************************************************************************/
6548
/*                   createFromGeoJson( const char* )                   */
6549
/************************************************************************/
6550
6551
/**
6552
 * @brief Create geometry from GeoJson fragment.
6553
 * @param pszJsonString The GeoJSON fragment for the geometry.
6554
 * @param nSize (new in GDAL 3.4) Optional length of the string
6555
 *              if it is not null-terminated
6556
 * @return a geometry on success, or NULL on error.
6557
 */
6558
OGRGeometry *OGRGeometryFactory::createFromGeoJson(const char *pszJsonString,
6559
                                                   int nSize)
6560
0
{
6561
0
    CPLJSONDocument oDocument;
6562
0
    if (!oDocument.LoadMemory(reinterpret_cast<const GByte *>(pszJsonString),
6563
0
                              nSize))
6564
0
    {
6565
0
        return nullptr;
6566
0
    }
6567
6568
0
    return createFromGeoJson(oDocument.GetRoot());
6569
0
}
6570
6571
/************************************************************************/
6572
/*              createFromGeoJson( const CPLJSONObject& )               */
6573
/************************************************************************/
6574
6575
/**
6576
 * @brief Create geometry from GeoJson fragment.
6577
 * @param oJsonObject The JSONObject class describes the GeoJSON geometry.
6578
 * @return a geometry on success, or NULL on error.
6579
 */
6580
OGRGeometry *
6581
OGRGeometryFactory::createFromGeoJson(const CPLJSONObject &oJsonObject)
6582
0
{
6583
0
    if (!oJsonObject.IsValid())
6584
0
    {
6585
0
        return nullptr;
6586
0
    }
6587
6588
    // TODO: Move from GeoJSON driver functions create geometry here, and
6589
    // replace json-c specific json_object to CPLJSONObject
6590
0
    return OGRGeoJSONReadGeometry(
6591
0
               static_cast<json_object *>(oJsonObject.GetInternalHandle()),
6592
0
               /* bHasM = */ false, /* OGRSpatialReference* = */ nullptr)
6593
0
        .release();
6594
0
}