Coverage Report

Created: 2025-06-13 06:18

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