Coverage Report

Created: 2025-12-31 06:48

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