Coverage Report

Created: 2025-08-28 06:57

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