• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 16254026072

13 Jul 2025 10:18PM UTC coverage: 71.084% (-0.01%) from 71.094%
16254026072

Pull #12754

github

web-flow
Merge caa46bb71 into 7b610c036
Pull Request #12754: gdal vector info: add --check-valid-geometry and --check-valid-coverage

123 of 163 new or added lines in 3 files covered. (75.46%)

115 existing lines in 48 files now uncovered.

575490 of 809596 relevant lines covered (71.08%)

260116.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.48
/ogr/ogrgeometryfactory.cpp
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,
59,044✔
90
                                         const OGRSpatialReference *poSR,
91
                                         OGRGeometry **ppoReturn, size_t nBytes,
92
                                         OGRwkbVariant eWkbVariant)
93

94
{
95
    size_t nBytesConsumedOutIgnored = 0;
59,044✔
96
    return createFromWkb(pabyData, poSR, ppoReturn, nBytes, eWkbVariant,
59,044✔
97
                         nBytesConsumedOutIgnored);
118,088✔
98
}
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,
98,067✔
135
                                         const OGRSpatialReference *poSR,
136
                                         OGRGeometry **ppoReturn, size_t nBytes,
137
                                         OGRwkbVariant eWkbVariant,
138
                                         size_t &nBytesConsumedOut)
139

140
{
141
    const GByte *l_pabyData = static_cast<const GByte *>(pabyData);
98,067✔
142
    nBytesConsumedOut = 0;
98,067✔
143
    *ppoReturn = nullptr;
98,067✔
144

145
    if (nBytes < 9 && nBytes != static_cast<size_t>(-1))
98,067✔
146
        return OGRERR_NOT_ENOUGH_DATA;
1,394✔
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
    const int nByteOrder = DB2_V72_FIX_BYTE_ORDER(*l_pabyData);
96,673✔
153
    if (nByteOrder != wkbXDR && nByteOrder != wkbNDR)
96,673✔
154
    {
155
        CPLDebug("OGR",
293✔
156
                 "OGRGeometryFactory::createFromWkb() - got corrupt data.\n"
157
                 "%02X%02X%02X%02X%02X%02X%02X%02X%02X",
158
                 l_pabyData[0], l_pabyData[1], l_pabyData[2], l_pabyData[3],
293✔
159
                 l_pabyData[4], l_pabyData[5], l_pabyData[6], l_pabyData[7],
293✔
160
                 l_pabyData[8]);
293✔
161
        return OGRERR_CORRUPT_DATA;
293✔
162
    }
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
    OGRwkbGeometryType eGeometryType = wkbUnknown;
96,380✔
171
    const OGRErr err =
172
        OGRReadWKBGeometryType(l_pabyData, eWkbVariant, &eGeometryType);
96,380✔
173

174
    if (err != OGRERR_NONE)
96,378✔
175
        return err;
563✔
176

177
    /* -------------------------------------------------------------------- */
178
    /*      Instantiate a geometry of the appropriate type, and             */
179
    /*      initialize from the input stream.                               */
180
    /* -------------------------------------------------------------------- */
181
    OGRGeometry *poGeom = createGeometry(eGeometryType);
95,815✔
182

183
    if (poGeom == nullptr)
95,815✔
184
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
×
185

186
    /* -------------------------------------------------------------------- */
187
    /*      Import from binary.                                             */
188
    /* -------------------------------------------------------------------- */
189
    const OGRErr eErr = poGeom->importFromWkb(l_pabyData, nBytes, eWkbVariant,
191,632✔
190
                                              nBytesConsumedOut);
95,815✔
191
    if (eErr != OGRERR_NONE)
95,817✔
192
    {
193
        delete poGeom;
7,315✔
194
        return eErr;
7,315✔
195
    }
196

197
    /* -------------------------------------------------------------------- */
198
    /*      Assign spatial reference system.                                */
199
    /* -------------------------------------------------------------------- */
200

201
    if (poGeom->hasCurveGeometry() &&
92,063✔
202
        CPLTestBool(CPLGetConfigOption("OGR_STROKE_CURVE", "FALSE")))
3,561✔
203
    {
204
        OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
5✔
205
        delete poGeom;
5✔
206
        poGeom = poNewGeom;
5✔
207
    }
208
    poGeom->assignSpatialReference(poSR);
88,501✔
209
    *ppoReturn = poGeom;
88,500✔
210

211
    return OGRERR_NONE;
88,500✔
212
}
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,
2✔
247
                                   OGRSpatialReferenceH hSRS,
248
                                   OGRGeometryH *phGeometry, int nBytes)
249

250
{
251
    return OGRGeometryFactory::createFromWkb(
2✔
252
        pabyData, OGRSpatialReference::FromHandle(hSRS),
2✔
253
        reinterpret_cast<OGRGeometry **>(phGeometry), nBytes);
2✔
254
}
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,
31,028✔
290
                                     OGRSpatialReferenceH hSRS,
291
                                     OGRGeometryH *phGeometry, size_t nBytes)
292

293
{
294
    return OGRGeometryFactory::createFromWkb(
31,028✔
295
        pabyData, OGRSpatialReference::FromHandle(hSRS),
31,028✔
296
        reinterpret_cast<OGRGeometry **>(phGeometry), nBytes);
31,028✔
297
}
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,
123,545✔
340
                                         const OGRSpatialReference *poSR,
341
                                         OGRGeometry **ppoReturn)
342

343
{
344
    const char *pszInput = *ppszData;
123,545✔
345
    *ppoReturn = nullptr;
123,545✔
346

347
    /* -------------------------------------------------------------------- */
348
    /*      Get the first token, which should be the geometry type.         */
349
    /* -------------------------------------------------------------------- */
350
    char szToken[OGR_WKT_TOKEN_MAX] = {};
123,545✔
351
    if (OGRWktReadToken(pszInput, szToken) == nullptr)
123,545✔
352
        return OGRERR_CORRUPT_DATA;
×
353

354
    /* -------------------------------------------------------------------- */
355
    /*      Instantiate a geometry of the appropriate type.                 */
356
    /* -------------------------------------------------------------------- */
357
    OGRGeometry *poGeom = nullptr;
123,545✔
358
    if (STARTS_WITH_CI(szToken, "POINT"))
123,545✔
359
    {
360
        poGeom = new OGRPoint();
97,232✔
361
    }
362
    else if (STARTS_WITH_CI(szToken, "LINESTRING"))
26,313✔
363
    {
364
        poGeom = new OGRLineString();
1,654✔
365
    }
366
    else if (STARTS_WITH_CI(szToken, "POLYGON"))
24,659✔
367
    {
368
        poGeom = new OGRPolygon();
16,245✔
369
    }
370
    else if (STARTS_WITH_CI(szToken, "TRIANGLE"))
8,414✔
371
    {
372
        poGeom = new OGRTriangle();
62✔
373
    }
374
    else if (STARTS_WITH_CI(szToken, "GEOMETRYCOLLECTION"))
8,352✔
375
    {
376
        poGeom = new OGRGeometryCollection();
518✔
377
    }
378
    else if (STARTS_WITH_CI(szToken, "MULTIPOLYGON"))
7,834✔
379
    {
380
        poGeom = new OGRMultiPolygon();
914✔
381
    }
382
    else if (STARTS_WITH_CI(szToken, "MULTIPOINT"))
6,920✔
383
    {
384
        poGeom = new OGRMultiPoint();
583✔
385
    }
386
    else if (STARTS_WITH_CI(szToken, "MULTILINESTRING"))
6,337✔
387
    {
388
        poGeom = new OGRMultiLineString();
628✔
389
    }
390
    else if (STARTS_WITH_CI(szToken, "CIRCULARSTRING"))
5,709✔
391
    {
392
        poGeom = new OGRCircularString();
3,519✔
393
    }
394
    else if (STARTS_WITH_CI(szToken, "COMPOUNDCURVE"))
2,190✔
395
    {
396
        poGeom = new OGRCompoundCurve();
298✔
397
    }
398
    else if (STARTS_WITH_CI(szToken, "CURVEPOLYGON"))
1,892✔
399
    {
400
        poGeom = new OGRCurvePolygon();
322✔
401
    }
402
    else if (STARTS_WITH_CI(szToken, "MULTICURVE"))
1,570✔
403
    {
404
        poGeom = new OGRMultiCurve();
141✔
405
    }
406
    else if (STARTS_WITH_CI(szToken, "MULTISURFACE"))
1,429✔
407
    {
408
        poGeom = new OGRMultiSurface();
157✔
409
    }
410

411
    else if (STARTS_WITH_CI(szToken, "POLYHEDRALSURFACE"))
1,272✔
412
    {
413
        poGeom = new OGRPolyhedralSurface();
69✔
414
    }
415

416
    else if (STARTS_WITH_CI(szToken, "TIN"))
1,203✔
417
    {
418
        poGeom = new OGRTriangulatedSurface();
122✔
419
    }
420

421
    else
422
    {
423
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
1,081✔
424
    }
425

426
    /* -------------------------------------------------------------------- */
427
    /*      Do the import.                                                  */
428
    /* -------------------------------------------------------------------- */
429
    const OGRErr eErr = poGeom->importFromWkt(&pszInput);
122,464✔
430

431
    /* -------------------------------------------------------------------- */
432
    /*      Assign spatial reference system.                                */
433
    /* -------------------------------------------------------------------- */
434
    if (eErr == OGRERR_NONE)
122,464✔
435
    {
436
        if (poGeom->hasCurveGeometry() &&
126,634✔
437
            CPLTestBool(CPLGetConfigOption("OGR_STROKE_CURVE", "FALSE")))
4,409✔
438
        {
439
            OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
9✔
440
            delete poGeom;
9✔
441
            poGeom = poNewGeom;
9✔
442
        }
443
        poGeom->assignSpatialReference(poSR);
122,225✔
444
        *ppoReturn = poGeom;
122,225✔
445
        *ppszData = pszInput;
122,225✔
446
    }
447
    else
448
    {
449
        delete poGeom;
239✔
450
    }
451

452
    return eErr;
122,464✔
453
}
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,
2,060✔
477
                                         const OGRSpatialReference *poSR,
478
                                         OGRGeometry **ppoReturn)
479

480
{
481
    return createFromWkt(&pszData, poSR, ppoReturn);
2,060✔
482
}
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,
3,756✔
504
                                  const OGRSpatialReference *poSR)
505

506
{
507
    std::unique_ptr<OGRGeometry> poGeom;
3,756✔
508
    OGRGeometry *poTmpGeom;
509
    auto err = createFromWkt(&pszData, poSR, &poTmpGeom);
3,756✔
510
    poGeom.reset(poTmpGeom);
3,756✔
511

512
    return {std::move(poGeom), err};
7,512✔
513
}
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,
116,061✔
541
                                   OGRGeometryH *phGeometry)
542

543
{
544
    return OGRGeometryFactory::createFromWkt(
116,061✔
545
        const_cast<const char **>(ppszData),
546
        OGRSpatialReference::FromHandle(hSRS),
116,061✔
547
        reinterpret_cast<OGRGeometry **>(phGeometry));
116,061✔
548
}
549

550
/************************************************************************/
551
/*                    OGR_G_CreateFromEnvelope()                        */
552
/************************************************************************/
553
/**
554
 * \brief Create a Polygon geometry from an envelope
555
 *
556
 *
557
 * @param dfMinX minimum X coordinate
558
 * @param dfMinY minimum Y coordinate
559
 * @param dfMaxX maximum X coordinate
560
 * @param dfMaxY maximum Y coordinate
561
 * @param hSRS handle to the spatial reference to be assigned to the
562
 *             created geometry object. This may be NULL.
563
 *
564
 * @return the newly created geometry. Should be freed with
565
 *          OGR_G_DestroyGeometry() after use.
566
 * @since 3.12
567
 */
568

569
OGRGeometryH CPL_DLL OGR_G_CreateFromEnvelope(double dfMinX, double dfMinY,
1✔
570
                                              double dfMaxX, double dfMaxY,
571
                                              OGRSpatialReferenceH hSRS)
572

573
{
574
    auto poPolygon =
575
        std::make_unique<OGRPolygon>(dfMinX, dfMinY, dfMaxX, dfMaxY);
2✔
576

577
    if (hSRS)
1✔
578
    {
579
        poPolygon->assignSpatialReference(
2✔
580
            OGRSpatialReference::FromHandle(hSRS));
1✔
581
    }
582

583
    return OGRGeometry::ToHandle(poPolygon.release());
2✔
584
}
585

586
/************************************************************************/
587
/*                           createGeometry()                           */
588
/************************************************************************/
589

590
/**
591
 * \brief Create an empty geometry of desired type.
592
 *
593
 * This is equivalent to allocating the desired geometry with new, but
594
 * the allocation is guaranteed to take place in the context of the
595
 * GDAL/OGR heap.
596
 *
597
 * This method is the same as the C function OGR_G_CreateGeometry().
598
 *
599
 * @param eGeometryType the type code of the geometry class to be instantiated.
600
 *
601
 * @return the newly create geometry or NULL on failure. Should be freed with
602
 *          OGRGeometryFactory::destroyGeometry() after use.
603
 */
604

605
OGRGeometry *
606
OGRGeometryFactory::createGeometry(OGRwkbGeometryType eGeometryType)
264,696✔
607

608
{
609
    OGRGeometry *poGeom = nullptr;
264,696✔
610
    switch (wkbFlatten(eGeometryType))
264,696✔
611
    {
612
        case wkbPoint:
182,995✔
613
            poGeom = new (std::nothrow) OGRPoint();
365,990✔
614
            break;
182,995✔
615

616
        case wkbLineString:
11,768✔
617
            poGeom = new (std::nothrow) OGRLineString();
23,535✔
618
            break;
11,767✔
619

620
        case wkbPolygon:
28,905✔
621
            poGeom = new (std::nothrow) OGRPolygon();
57,810✔
622
            break;
28,905✔
623

624
        case wkbGeometryCollection:
1,981✔
625
            poGeom = new (std::nothrow) OGRGeometryCollection();
3,962✔
626
            break;
1,981✔
627

628
        case wkbMultiPolygon:
3,138✔
629
            poGeom = new (std::nothrow) OGRMultiPolygon();
6,276✔
630
            break;
3,138✔
631

632
        case wkbMultiPoint:
1,401✔
633
            poGeom = new (std::nothrow) OGRMultiPoint();
2,802✔
634
            break;
1,401✔
635

636
        case wkbMultiLineString:
1,922✔
637
            poGeom = new (std::nothrow) OGRMultiLineString();
3,844✔
638
            break;
1,922✔
639

640
        case wkbLinearRing:
60✔
641
            poGeom = new (std::nothrow) OGRLinearRing();
120✔
642
            break;
60✔
643

644
        case wkbCircularString:
69✔
645
            poGeom = new (std::nothrow) OGRCircularString();
138✔
646
            break;
69✔
647

648
        case wkbCompoundCurve:
1,982✔
649
            poGeom = new (std::nothrow) OGRCompoundCurve();
3,964✔
650
            break;
1,982✔
651

652
        case wkbCurvePolygon:
46✔
653
            poGeom = new (std::nothrow) OGRCurvePolygon();
92✔
654
            break;
46✔
655

656
        case wkbMultiCurve:
1,121✔
657
            poGeom = new (std::nothrow) OGRMultiCurve();
2,242✔
658
            break;
1,121✔
659

660
        case wkbMultiSurface:
1,183✔
661
            poGeom = new (std::nothrow) OGRMultiSurface();
2,366✔
662
            break;
1,183✔
663

664
        case wkbTriangle:
14,502✔
665
            poGeom = new (std::nothrow) OGRTriangle();
29,004✔
666
            break;
14,502✔
667

668
        case wkbPolyhedralSurface:
7,379✔
669
            poGeom = new (std::nothrow) OGRPolyhedralSurface();
14,758✔
670
            break;
7,379✔
671

672
        case wkbTIN:
6,243✔
673
            poGeom = new (std::nothrow) OGRTriangulatedSurface();
12,486✔
674
            break;
6,243✔
675

676
        case wkbUnknown:
1✔
677
            break;
1✔
678

679
        default:
×
680
            CPLAssert(false);
×
681
            break;
682
    }
683
    if (poGeom)
264,695✔
684
    {
685
        if (OGR_GT_HasZ(eGeometryType))
264,695✔
686
            poGeom->set3D(true);
64,745✔
687
        if (OGR_GT_HasM(eGeometryType))
264,694✔
688
            poGeom->setMeasured(true);
59,822✔
689
    }
690
    return poGeom;
264,695✔
691
}
692

693
/************************************************************************/
694
/*                        OGR_G_CreateGeometry()                        */
695
/************************************************************************/
696
/**
697
 * \brief Create an empty geometry of desired type.
698
 *
699
 * This is equivalent to allocating the desired geometry with new, but
700
 * the allocation is guaranteed to take place in the context of the
701
 * GDAL/OGR heap.
702
 *
703
 * This function is the same as the CPP method
704
 * OGRGeometryFactory::createGeometry.
705
 *
706
 * @param eGeometryType the type code of the geometry to be created.
707
 *
708
 * @return handle to the newly create geometry or NULL on failure. Should be
709
 *         freed with OGR_G_DestroyGeometry() after use.
710
 */
711

712
OGRGeometryH OGR_G_CreateGeometry(OGRwkbGeometryType eGeometryType)
165,612✔
713

714
{
715
    return OGRGeometry::ToHandle(
165,612✔
716
        OGRGeometryFactory::createGeometry(eGeometryType));
165,612✔
717
}
718

719
/************************************************************************/
720
/*                          destroyGeometry()                           */
721
/************************************************************************/
722

723
/**
724
 * \brief Destroy geometry object.
725
 *
726
 * Equivalent to invoking delete on a geometry, but it guaranteed to take
727
 * place within the context of the GDAL/OGR heap.
728
 *
729
 * This method is the same as the C function OGR_G_DestroyGeometry().
730
 *
731
 * @param poGeom the geometry to deallocate.
732
 */
733

734
void OGRGeometryFactory::destroyGeometry(OGRGeometry *poGeom)
2✔
735

736
{
737
    delete poGeom;
2✔
738
}
2✔
739

740
/************************************************************************/
741
/*                        OGR_G_DestroyGeometry()                       */
742
/************************************************************************/
743
/**
744
 * \brief Destroy geometry object.
745
 *
746
 * Equivalent to invoking delete on a geometry, but it guaranteed to take
747
 * place within the context of the GDAL/OGR heap.
748
 *
749
 * This function is the same as the CPP method
750
 * OGRGeometryFactory::destroyGeometry.
751
 *
752
 * @param hGeom handle to the geometry to delete.
753
 */
754

755
void OGR_G_DestroyGeometry(OGRGeometryH hGeom)
290,141✔
756

757
{
758
    delete OGRGeometry::FromHandle(hGeom);
290,141✔
759
}
290,141✔
760

761
/************************************************************************/
762
/*                           forceToPolygon()                           */
763
/************************************************************************/
764

765
/**
766
 * \brief Convert to polygon.
767
 *
768
 * Tries to force the provided geometry to be a polygon. This effects a change
769
 * on multipolygons.
770
 * Starting with GDAL 2.0, curve polygons or closed curves will be changed to
771
 * polygons.  The passed in geometry is consumed and a new one returned (or
772
 * potentially the same one).
773
 *
774
 * Note: the resulting polygon may break the Simple Features rules for polygons,
775
 * for example when converting from a multi-part multipolygon.
776
 *
777
 * @param poGeom the input geometry - ownership is passed to the method.
778
 * @return new geometry.
779
 */
780

781
OGRGeometry *OGRGeometryFactory::forceToPolygon(OGRGeometry *poGeom)
148✔
782

783
{
784
    if (poGeom == nullptr)
148✔
785
        return nullptr;
×
786

787
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
148✔
788

789
    if (eGeomType == wkbCurvePolygon)
148✔
790
    {
791
        OGRCurvePolygon *poCurve = poGeom->toCurvePolygon();
34✔
792

793
        if (!poGeom->hasCurveGeometry(TRUE))
34✔
794
            return OGRSurface::CastToPolygon(poCurve);
14✔
795

796
        OGRPolygon *poPoly = poCurve->CurvePolyToPoly();
20✔
797
        delete poGeom;
20✔
798
        return poPoly;
20✔
799
    }
800

801
    // base polygon or triangle
802
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolygon))
114✔
803
    {
804
        return OGRSurface::CastToPolygon(poGeom->toSurface());
7✔
805
    }
806

807
    if (OGR_GT_IsCurve(eGeomType))
107✔
808
    {
809
        OGRCurve *poCurve = poGeom->toCurve();
60✔
810
        if (poCurve->getNumPoints() >= 3 && poCurve->get_IsClosed())
60✔
811
        {
812
            OGRPolygon *poPolygon = new OGRPolygon();
40✔
813
            poPolygon->assignSpatialReference(poGeom->getSpatialReference());
40✔
814

815
            if (!poGeom->hasCurveGeometry(TRUE))
40✔
816
            {
817
                poPolygon->addRingDirectly(OGRCurve::CastToLinearRing(poCurve));
26✔
818
            }
819
            else
820
            {
821
                OGRLineString *poLS = poCurve->CurveToLine();
14✔
822
                poPolygon->addRingDirectly(OGRCurve::CastToLinearRing(poLS));
14✔
823
                delete poGeom;
14✔
824
            }
825
            return poPolygon;
40✔
826
        }
827
    }
828

829
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
67✔
830
    {
831
        OGRPolyhedralSurface *poPS = poGeom->toPolyhedralSurface();
6✔
832
        if (poPS->getNumGeometries() == 1)
6✔
833
        {
834
            poGeom = OGRSurface::CastToPolygon(
5✔
835
                poPS->getGeometryRef(0)->clone()->toSurface());
5✔
836
            delete poPS;
5✔
837
            return poGeom;
5✔
838
        }
839
    }
840

841
    if (eGeomType != wkbGeometryCollection && eGeomType != wkbMultiPolygon &&
62✔
842
        eGeomType != wkbMultiSurface)
843
        return poGeom;
38✔
844

845
    // Build an aggregated polygon from all the polygon rings in the container.
846
    OGRPolygon *poPolygon = new OGRPolygon();
24✔
847
    OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
24✔
848
    if (poGeom->hasCurveGeometry())
24✔
849
    {
850
        OGRGeometryCollection *poNewGC =
851
            poGC->getLinearGeometry()->toGeometryCollection();
5✔
852
        delete poGC;
5✔
853
        poGeom = poNewGC;
5✔
854
        poGC = poNewGC;
5✔
855
    }
856

857
    poPolygon->assignSpatialReference(poGeom->getSpatialReference());
24✔
858

859
    for (int iGeom = 0; iGeom < poGC->getNumGeometries(); iGeom++)
53✔
860
    {
861
        if (wkbFlatten(poGC->getGeometryRef(iGeom)->getGeometryType()) !=
29✔
862
            wkbPolygon)
863
            continue;
12✔
864

865
        OGRPolygon *poOldPoly = poGC->getGeometryRef(iGeom)->toPolygon();
17✔
866

867
        if (poOldPoly->getExteriorRing() == nullptr)
17✔
868
            continue;
3✔
869

870
        poPolygon->addRingDirectly(poOldPoly->stealExteriorRing());
14✔
871

872
        for (int iRing = 0; iRing < poOldPoly->getNumInteriorRings(); iRing++)
22✔
873
            poPolygon->addRingDirectly(poOldPoly->stealInteriorRing(iRing));
8✔
874
    }
875

876
    delete poGC;
24✔
877

878
    return poPolygon;
24✔
879
}
880

881
/************************************************************************/
882
/*                        OGR_G_ForceToPolygon()                        */
883
/************************************************************************/
884

885
/**
886
 * \brief Convert to polygon.
887
 *
888
 * This function is the same as the C++ method
889
 * OGRGeometryFactory::forceToPolygon().
890
 *
891
 * @param hGeom handle to the geometry to convert (ownership surrendered).
892
 * @return the converted geometry (ownership to caller).
893
 *
894
 * @since GDAL/OGR 1.8.0
895
 */
896

897
OGRGeometryH OGR_G_ForceToPolygon(OGRGeometryH hGeom)
46✔
898

899
{
900
    return OGRGeometry::ToHandle(
46✔
901
        OGRGeometryFactory::forceToPolygon(OGRGeometry::FromHandle(hGeom)));
46✔
902
}
903

904
/************************************************************************/
905
/*                        forceToMultiPolygon()                         */
906
/************************************************************************/
907

908
/**
909
 * \brief Convert to multipolygon.
910
 *
911
 * Tries to force the provided geometry to be a multipolygon.  Currently
912
 * this just effects a change on polygons.  The passed in geometry is
913
 * consumed and a new one returned (or potentially the same one).
914
 *
915
 * @return new geometry.
916
 */
917

918
OGRGeometry *OGRGeometryFactory::forceToMultiPolygon(OGRGeometry *poGeom)
3,728✔
919

920
{
921
    if (poGeom == nullptr)
3,728✔
922
        return nullptr;
×
923

924
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
3,728✔
925

926
    /* -------------------------------------------------------------------- */
927
    /*      If this is already a MultiPolygon, nothing to do                */
928
    /* -------------------------------------------------------------------- */
929
    if (eGeomType == wkbMultiPolygon)
3,728✔
930
    {
931
        return poGeom;
40✔
932
    }
933

934
    /* -------------------------------------------------------------------- */
935
    /*      If this is already a MultiSurface with compatible content,      */
936
    /*      just cast                                                       */
937
    /* -------------------------------------------------------------------- */
938
    if (eGeomType == wkbMultiSurface)
3,688✔
939
    {
940
        OGRMultiSurface *poMS = poGeom->toMultiSurface();
9✔
941
        if (!poMS->hasCurveGeometry(TRUE))
9✔
942
        {
943
            return OGRMultiSurface::CastToMultiPolygon(poMS);
4✔
944
        }
945
    }
946

947
    /* -------------------------------------------------------------------- */
948
    /*      Check for the case of a geometrycollection that can be          */
949
    /*      promoted to MultiPolygon.                                       */
950
    /* -------------------------------------------------------------------- */
951
    if (eGeomType == wkbGeometryCollection || eGeomType == wkbMultiSurface)
3,684✔
952
    {
953
        bool bAllPoly = true;
73✔
954
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
73✔
955
        if (poGeom->hasCurveGeometry())
73✔
956
        {
957
            OGRGeometryCollection *poNewGC =
958
                poGC->getLinearGeometry()->toGeometryCollection();
6✔
959
            delete poGC;
6✔
960
            poGeom = poNewGC;
6✔
961
            poGC = poNewGC;
6✔
962
        }
963

964
        bool bCanConvertToMultiPoly = true;
73✔
965
        for (int iGeom = 0; iGeom < poGC->getNumGeometries(); iGeom++)
294✔
966
        {
967
            OGRwkbGeometryType eSubGeomType =
968
                wkbFlatten(poGC->getGeometryRef(iGeom)->getGeometryType());
221✔
969
            if (eSubGeomType != wkbPolygon)
221✔
970
                bAllPoly = false;
172✔
971
            if (eSubGeomType != wkbMultiPolygon && eSubGeomType != wkbPolygon &&
221✔
972
                eSubGeomType != wkbPolyhedralSurface && eSubGeomType != wkbTIN)
134✔
973
            {
974
                bCanConvertToMultiPoly = false;
16✔
975
            }
976
        }
977

978
        if (!bCanConvertToMultiPoly)
73✔
979
            return poGeom;
12✔
980

981
        OGRMultiPolygon *poMP = new OGRMultiPolygon();
61✔
982
        poMP->assignSpatialReference(poGeom->getSpatialReference());
61✔
983

984
        while (poGC->getNumGeometries() > 0)
264✔
985
        {
986
            OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
203✔
987
            poGC->removeGeometry(0, FALSE);
203✔
988
            if (bAllPoly)
203✔
989
            {
990
                poMP->addGeometryDirectly(poSubGeom);
47✔
991
            }
992
            else
993
            {
994
                poSubGeom = forceToMultiPolygon(poSubGeom);
156✔
995
                OGRMultiPolygon *poSubMP = poSubGeom->toMultiPolygon();
156✔
996
                while (poSubMP != nullptr && poSubMP->getNumGeometries() > 0)
414✔
997
                {
998
                    poMP->addGeometryDirectly(poSubMP->getGeometryRef(0));
258✔
999
                    poSubMP->removeGeometry(0, FALSE);
258✔
1000
                }
1001
                delete poSubMP;
156✔
1002
            }
1003
        }
1004

1005
        delete poGC;
61✔
1006

1007
        return poMP;
61✔
1008
    }
1009

1010
    if (eGeomType == wkbCurvePolygon)
3,611✔
1011
    {
1012
        OGRPolygon *poPoly = poGeom->toCurvePolygon()->CurvePolyToPoly();
5✔
1013
        OGRMultiPolygon *poMP = new OGRMultiPolygon();
5✔
1014
        poMP->assignSpatialReference(poGeom->getSpatialReference());
5✔
1015
        poMP->addGeometryDirectly(poPoly);
5✔
1016
        delete poGeom;
5✔
1017
        return poMP;
5✔
1018
    }
1019

1020
    /* -------------------------------------------------------------------- */
1021
    /*      If it is PolyhedralSurface or TIN, then pretend it is a         */
1022
    /*      multipolygon.                                                   */
1023
    /* -------------------------------------------------------------------- */
1024
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
3,606✔
1025
    {
1026
        return OGRPolyhedralSurface::CastToMultiPolygon(
992✔
1027
            poGeom->toPolyhedralSurface());
992✔
1028
    }
1029

1030
    if (eGeomType == wkbTriangle)
2,614✔
1031
    {
1032
        return forceToMultiPolygon(forceToPolygon(poGeom));
2✔
1033
    }
1034

1035
    /* -------------------------------------------------------------------- */
1036
    /*      Eventually we should try to split the polygon into component    */
1037
    /*      island polygons.  But that is a lot of work and can be put off. */
1038
    /* -------------------------------------------------------------------- */
1039
    if (eGeomType != wkbPolygon)
2,612✔
1040
        return poGeom;
30✔
1041

1042
    OGRMultiPolygon *poMP = new OGRMultiPolygon();
2,582✔
1043
    poMP->assignSpatialReference(poGeom->getSpatialReference());
2,582✔
1044
    poMP->addGeometryDirectly(poGeom);
2,582✔
1045

1046
    return poMP;
2,582✔
1047
}
1048

1049
/************************************************************************/
1050
/*                     OGR_G_ForceToMultiPolygon()                      */
1051
/************************************************************************/
1052

1053
/**
1054
 * \brief Convert to multipolygon.
1055
 *
1056
 * This function is the same as the C++ method
1057
 * OGRGeometryFactory::forceToMultiPolygon().
1058
 *
1059
 * @param hGeom handle to the geometry to convert (ownership surrendered).
1060
 * @return the converted geometry (ownership to caller).
1061
 *
1062
 * @since GDAL/OGR 1.8.0
1063
 */
1064

1065
OGRGeometryH OGR_G_ForceToMultiPolygon(OGRGeometryH hGeom)
47✔
1066

1067
{
1068
    return OGRGeometry::ToHandle(OGRGeometryFactory::forceToMultiPolygon(
47✔
1069
        OGRGeometry::FromHandle(hGeom)));
47✔
1070
}
1071

1072
/************************************************************************/
1073
/*                        forceToMultiPoint()                           */
1074
/************************************************************************/
1075

1076
/**
1077
 * \brief Convert to multipoint.
1078
 *
1079
 * Tries to force the provided geometry to be a multipoint.  Currently
1080
 * this just effects a change on points or collection of points.
1081
 * The passed in geometry is
1082
 * consumed and a new one returned (or potentially the same one).
1083
 *
1084
 * @return new geometry.
1085
 */
1086

1087
OGRGeometry *OGRGeometryFactory::forceToMultiPoint(OGRGeometry *poGeom)
67✔
1088

1089
{
1090
    if (poGeom == nullptr)
67✔
1091
        return nullptr;
×
1092

1093
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
67✔
1094

1095
    /* -------------------------------------------------------------------- */
1096
    /*      If this is already a MultiPoint, nothing to do                  */
1097
    /* -------------------------------------------------------------------- */
1098
    if (eGeomType == wkbMultiPoint)
67✔
1099
    {
1100
        return poGeom;
2✔
1101
    }
1102

1103
    /* -------------------------------------------------------------------- */
1104
    /*      Check for the case of a geometrycollection that can be          */
1105
    /*      promoted to MultiPoint.                                         */
1106
    /* -------------------------------------------------------------------- */
1107
    if (eGeomType == wkbGeometryCollection)
65✔
1108
    {
1109
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
14✔
1110
        for (const auto &poMember : poGC)
18✔
1111
        {
1112
            if (wkbFlatten(poMember->getGeometryType()) != wkbPoint)
14✔
1113
                return poGeom;
10✔
1114
        }
1115

1116
        OGRMultiPoint *poMP = new OGRMultiPoint();
4✔
1117
        poMP->assignSpatialReference(poGeom->getSpatialReference());
4✔
1118

1119
        while (poGC->getNumGeometries() > 0)
8✔
1120
        {
1121
            poMP->addGeometryDirectly(poGC->getGeometryRef(0));
4✔
1122
            poGC->removeGeometry(0, FALSE);
4✔
1123
        }
1124

1125
        delete poGC;
4✔
1126

1127
        return poMP;
4✔
1128
    }
1129

1130
    if (eGeomType != wkbPoint)
51✔
1131
        return poGeom;
44✔
1132

1133
    OGRMultiPoint *poMP = new OGRMultiPoint();
7✔
1134
    poMP->assignSpatialReference(poGeom->getSpatialReference());
7✔
1135
    poMP->addGeometryDirectly(poGeom);
7✔
1136

1137
    return poMP;
7✔
1138
}
1139

1140
/************************************************************************/
1141
/*                      OGR_G_ForceToMultiPoint()                       */
1142
/************************************************************************/
1143

1144
/**
1145
 * \brief Convert to multipoint.
1146
 *
1147
 * This function is the same as the C++ method
1148
 * OGRGeometryFactory::forceToMultiPoint().
1149
 *
1150
 * @param hGeom handle to the geometry to convert (ownership surrendered).
1151
 * @return the converted geometry (ownership to caller).
1152
 *
1153
 * @since GDAL/OGR 1.8.0
1154
 */
1155

1156
OGRGeometryH OGR_G_ForceToMultiPoint(OGRGeometryH hGeom)
41✔
1157

1158
{
1159
    return OGRGeometry::ToHandle(
41✔
1160
        OGRGeometryFactory::forceToMultiPoint(OGRGeometry::FromHandle(hGeom)));
41✔
1161
}
1162

1163
/************************************************************************/
1164
/*                        forceToMultiLinestring()                      */
1165
/************************************************************************/
1166

1167
/**
1168
 * \brief Convert to multilinestring.
1169
 *
1170
 * Tries to force the provided geometry to be a multilinestring.
1171
 *
1172
 * - linestrings are placed in a multilinestring.
1173
 * - circularstrings and compoundcurves will be approximated and placed in a
1174
 * multilinestring.
1175
 * - geometry collections will be converted to multilinestring if they only
1176
 * contain linestrings.
1177
 * - polygons will be changed to a collection of linestrings (one per ring).
1178
 * - curvepolygons will be approximated and changed to a collection of
1179
 ( linestrings (one per ring).
1180
 *
1181
 * The passed in geometry is
1182
 * consumed and a new one returned (or potentially the same one).
1183
 *
1184
 * @return new geometry.
1185
 */
1186

1187
OGRGeometry *OGRGeometryFactory::forceToMultiLineString(OGRGeometry *poGeom)
2,134✔
1188

1189
{
1190
    if (poGeom == nullptr)
2,134✔
1191
        return nullptr;
×
1192

1193
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
2,134✔
1194

1195
    /* -------------------------------------------------------------------- */
1196
    /*      If this is already a MultiLineString, nothing to do             */
1197
    /* -------------------------------------------------------------------- */
1198
    if (eGeomType == wkbMultiLineString)
2,134✔
1199
    {
1200
        return poGeom;
2✔
1201
    }
1202

1203
    /* -------------------------------------------------------------------- */
1204
    /*      Check for the case of a geometrycollection that can be          */
1205
    /*      promoted to MultiLineString.                                    */
1206
    /* -------------------------------------------------------------------- */
1207
    if (eGeomType == wkbGeometryCollection)
2,132✔
1208
    {
1209
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
16✔
1210
        if (poGeom->hasCurveGeometry())
16✔
1211
        {
1212
            OGRGeometryCollection *poNewGC =
1213
                poGC->getLinearGeometry()->toGeometryCollection();
1✔
1214
            delete poGC;
1✔
1215
            poGeom = poNewGC;
1✔
1216
            poGC = poNewGC;
1✔
1217
        }
1218

1219
        for (auto &&poMember : poGC)
24✔
1220
        {
1221
            if (wkbFlatten(poMember->getGeometryType()) != wkbLineString)
18✔
1222
            {
1223
                return poGeom;
10✔
1224
            }
1225
        }
1226

1227
        OGRMultiLineString *poMP = new OGRMultiLineString();
6✔
1228
        poMP->assignSpatialReference(poGeom->getSpatialReference());
6✔
1229

1230
        while (poGC->getNumGeometries() > 0)
14✔
1231
        {
1232
            poMP->addGeometryDirectly(poGC->getGeometryRef(0));
8✔
1233
            poGC->removeGeometry(0, FALSE);
8✔
1234
        }
1235

1236
        delete poGC;
6✔
1237

1238
        return poMP;
6✔
1239
    }
1240

1241
    /* -------------------------------------------------------------------- */
1242
    /*      Turn a linestring into a multilinestring.                       */
1243
    /* -------------------------------------------------------------------- */
1244
    if (eGeomType == wkbLineString)
2,116✔
1245
    {
1246
        OGRMultiLineString *poMP = new OGRMultiLineString();
2,030✔
1247
        poMP->assignSpatialReference(poGeom->getSpatialReference());
2,030✔
1248
        poMP->addGeometryDirectly(poGeom);
2,030✔
1249
        return poMP;
2,030✔
1250
    }
1251

1252
    /* -------------------------------------------------------------------- */
1253
    /*      Convert polygons into a multilinestring.                        */
1254
    /* -------------------------------------------------------------------- */
1255
    if (OGR_GT_IsSubClassOf(eGeomType, wkbCurvePolygon))
86✔
1256
    {
1257
        OGRMultiLineString *poMLS = new OGRMultiLineString();
28✔
1258
        poMLS->assignSpatialReference(poGeom->getSpatialReference());
28✔
1259

1260
        const auto AddRingFromSrcPoly = [poMLS](const OGRPolygon *poPoly)
57✔
1261
        {
1262
            for (int iRing = 0; iRing < poPoly->getNumInteriorRings() + 1;
61✔
1263
                 iRing++)
1264
            {
1265
                const OGRLineString *poLR;
1266

1267
                if (iRing == 0)
35✔
1268
                {
1269
                    poLR = poPoly->getExteriorRing();
28✔
1270
                    if (poLR == nullptr)
28✔
1271
                        break;
2✔
1272
                }
1273
                else
1274
                    poLR = poPoly->getInteriorRing(iRing - 1);
7✔
1275

1276
                if (poLR == nullptr || poLR->getNumPoints() == 0)
33✔
1277
                    continue;
4✔
1278

1279
                auto poNewLS = new OGRLineString();
29✔
1280
                poNewLS->addSubLineString(poLR);
29✔
1281
                poMLS->addGeometryDirectly(poNewLS);
29✔
1282
            }
1283
        };
28✔
1284

1285
        if (OGR_GT_IsSubClassOf(eGeomType, wkbPolygon))
28✔
1286
        {
1287
            AddRingFromSrcPoly(poGeom->toPolygon());
24✔
1288
        }
1289
        else
1290
        {
1291
            auto poTmpPoly = std::unique_ptr<OGRPolygon>(
1292
                poGeom->toCurvePolygon()->CurvePolyToPoly());
8✔
1293
            AddRingFromSrcPoly(poTmpPoly.get());
4✔
1294
        }
1295

1296
        delete poGeom;
28✔
1297

1298
        return poMLS;
28✔
1299
    }
1300

1301
    /* -------------------------------------------------------------------- */
1302
    /*      If it is PolyhedralSurface or TIN, then pretend it is a         */
1303
    /*      multipolygon.                                                   */
1304
    /* -------------------------------------------------------------------- */
1305
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
58✔
1306
    {
1307
        poGeom = CPLAssertNotNull(forceToMultiPolygon(poGeom));
×
1308
        eGeomType = wkbMultiPolygon;
×
1309
    }
1310

1311
    /* -------------------------------------------------------------------- */
1312
    /*      Convert multi-polygons into a multilinestring.                  */
1313
    /* -------------------------------------------------------------------- */
1314
    if (eGeomType == wkbMultiPolygon || eGeomType == wkbMultiSurface)
58✔
1315
    {
1316
        OGRMultiLineString *poMLS = new OGRMultiLineString();
9✔
1317
        poMLS->assignSpatialReference(poGeom->getSpatialReference());
9✔
1318

1319
        const auto AddRingFromSrcMP = [poMLS](const OGRMultiPolygon *poSrcMP)
22✔
1320
        {
1321
            for (auto &&poPoly : poSrcMP)
21✔
1322
            {
1323
                for (auto &&poLR : poPoly)
27✔
1324
                {
1325
                    if (poLR->IsEmpty())
15✔
1326
                        continue;
2✔
1327

1328
                    OGRLineString *poNewLS = new OGRLineString();
13✔
1329
                    poNewLS->addSubLineString(poLR);
13✔
1330
                    poMLS->addGeometryDirectly(poNewLS);
13✔
1331
                }
1332
            }
1333
        };
9✔
1334

1335
        if (eGeomType == wkbMultiPolygon)
9✔
1336
        {
1337
            AddRingFromSrcMP(poGeom->toMultiPolygon());
6✔
1338
        }
1339
        else
1340
        {
1341
            auto poTmpMPoly = std::unique_ptr<OGRMultiPolygon>(
1342
                poGeom->getLinearGeometry()->toMultiPolygon());
6✔
1343
            AddRingFromSrcMP(poTmpMPoly.get());
3✔
1344
        }
1345

1346
        delete poGeom;
9✔
1347
        return poMLS;
9✔
1348
    }
1349

1350
    /* -------------------------------------------------------------------- */
1351
    /*      If it is a curve line, approximate it and wrap in a multilinestring
1352
     */
1353
    /* -------------------------------------------------------------------- */
1354
    if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
49✔
1355
    {
1356
        OGRMultiLineString *poMP = new OGRMultiLineString();
20✔
1357
        poMP->assignSpatialReference(poGeom->getSpatialReference());
20✔
1358
        poMP->addGeometryDirectly(poGeom->toCurve()->CurveToLine());
20✔
1359
        delete poGeom;
20✔
1360
        return poMP;
20✔
1361
    }
1362

1363
    /* -------------------------------------------------------------------- */
1364
    /*      If this is already a MultiCurve with compatible content,        */
1365
    /*      just cast                                                       */
1366
    /* -------------------------------------------------------------------- */
1367
    if (eGeomType == wkbMultiCurve &&
38✔
1368
        !poGeom->toMultiCurve()->hasCurveGeometry(TRUE))
9✔
1369
    {
1370
        return OGRMultiCurve::CastToMultiLineString(poGeom->toMultiCurve());
3✔
1371
    }
1372

1373
    /* -------------------------------------------------------------------- */
1374
    /*      If it is a multicurve, call getLinearGeometry()                */
1375
    /* -------------------------------------------------------------------- */
1376
    if (eGeomType == wkbMultiCurve)
26✔
1377
    {
1378
        OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
6✔
1379
        CPLAssert(wkbFlatten(poNewGeom->getGeometryType()) ==
6✔
1380
                  wkbMultiLineString);
1381
        delete poGeom;
6✔
1382
        return poNewGeom->toMultiLineString();
6✔
1383
    }
1384

1385
    return poGeom;
20✔
1386
}
1387

1388
/************************************************************************/
1389
/*                    OGR_G_ForceToMultiLineString()                    */
1390
/************************************************************************/
1391

1392
/**
1393
 * \brief Convert to multilinestring.
1394
 *
1395
 * This function is the same as the C++ method
1396
 * OGRGeometryFactory::forceToMultiLineString().
1397
 *
1398
 * @param hGeom handle to the geometry to convert (ownership surrendered).
1399
 * @return the converted geometry (ownership to caller).
1400
 *
1401
 * @since GDAL/OGR 1.8.0
1402
 */
1403

1404
OGRGeometryH OGR_G_ForceToMultiLineString(OGRGeometryH hGeom)
50✔
1405

1406
{
1407
    return OGRGeometry::ToHandle(OGRGeometryFactory::forceToMultiLineString(
50✔
1408
        OGRGeometry::FromHandle(hGeom)));
50✔
1409
}
1410

1411
/************************************************************************/
1412
/*                      removeLowerDimensionSubGeoms()                  */
1413
/************************************************************************/
1414

1415
/** \brief Remove sub-geometries from a geometry collection that do not have
1416
 *         the maximum topological dimensionality of the collection.
1417
 *
1418
 * This is typically to be used as a cleanup phase after running
1419
 * OGRGeometry::MakeValid()
1420
 *
1421
 * For example, MakeValid() on a polygon can return a geometry collection of
1422
 * polygons and linestrings. Calling this method will return either a polygon
1423
 * or multipolygon by dropping those linestrings.
1424
 *
1425
 * On a non-geometry collection, this will return a clone of the passed
1426
 * geometry.
1427
 *
1428
 * @param poGeom input geometry
1429
 * @return a new geometry.
1430
 *
1431
 * @since GDAL 3.1.0
1432
 */
1433
OGRGeometry *
1434
OGRGeometryFactory::removeLowerDimensionSubGeoms(const OGRGeometry *poGeom)
32✔
1435
{
1436
    if (poGeom == nullptr)
32✔
1437
        return nullptr;
×
1438
    if (wkbFlatten(poGeom->getGeometryType()) != wkbGeometryCollection ||
47✔
1439
        poGeom->IsEmpty())
15✔
1440
    {
1441
        return poGeom->clone();
18✔
1442
    }
1443
    const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
14✔
1444
    int nMaxDim = 0;
14✔
1445
    OGRBoolean bHasCurve = FALSE;
14✔
1446
    for (const auto poSubGeom : *poGC)
39✔
1447
    {
1448
        nMaxDim = std::max(nMaxDim, poSubGeom->getDimension());
25✔
1449
        bHasCurve |= poSubGeom->hasCurveGeometry();
25✔
1450
    }
1451
    int nCountAtMaxDim = 0;
14✔
1452
    const OGRGeometry *poGeomAtMaxDim = nullptr;
14✔
1453
    for (const auto poSubGeom : *poGC)
39✔
1454
    {
1455
        if (poSubGeom->getDimension() == nMaxDim)
25✔
1456
        {
1457
            poGeomAtMaxDim = poSubGeom;
19✔
1458
            nCountAtMaxDim++;
19✔
1459
        }
1460
    }
1461
    if (nCountAtMaxDim == 1 && poGeomAtMaxDim != nullptr)
14✔
1462
    {
1463
        return poGeomAtMaxDim->clone();
9✔
1464
    }
1465
    OGRGeometryCollection *poRet =
1466
        (nMaxDim == 0)
5✔
1467
            ? static_cast<OGRGeometryCollection *>(new OGRMultiPoint())
10✔
1468
        : (nMaxDim == 1)
5✔
1469
            ? (!bHasCurve
10✔
1470
                   ? static_cast<OGRGeometryCollection *>(
4✔
1471
                         new OGRMultiLineString())
1✔
1472
                   : static_cast<OGRGeometryCollection *>(new OGRMultiCurve()))
1✔
1473
        : (nMaxDim == 2 && !bHasCurve)
3✔
1474
            ? static_cast<OGRGeometryCollection *>(new OGRMultiPolygon())
6✔
1475
            : static_cast<OGRGeometryCollection *>(new OGRMultiSurface());
1✔
1476
    for (const auto poSubGeom : *poGC)
15✔
1477
    {
1478
        if (poSubGeom->getDimension() == nMaxDim)
10✔
1479
        {
1480
            if (OGR_GT_IsSubClassOf(poSubGeom->getGeometryType(),
10✔
1481
                                    wkbGeometryCollection))
10✔
1482
            {
1483
                const OGRGeometryCollection *poSubGeomAsGC =
1484
                    poSubGeom->toGeometryCollection();
1✔
1485
                for (const auto poSubSubGeom : *poSubGeomAsGC)
2✔
1486
                {
1487
                    if (poSubSubGeom->getDimension() == nMaxDim)
1✔
1488
                    {
1489
                        poRet->addGeometryDirectly(poSubSubGeom->clone());
1✔
1490
                    }
1491
                }
1492
            }
1493
            else
1494
            {
1495
                poRet->addGeometryDirectly(poSubGeom->clone());
9✔
1496
            }
1497
        }
1498
    }
1499
    return poRet;
5✔
1500
}
1501

1502
/************************************************************************/
1503
/*                  OGR_G_RemoveLowerDimensionSubGeoms()                */
1504
/************************************************************************/
1505

1506
/** \brief Remove sub-geometries from a geometry collection that do not have
1507
 *         the maximum topological dimensionality of the collection.
1508
 *
1509
 * This function is the same as the C++ method
1510
 * OGRGeometryFactory::removeLowerDimensionSubGeoms().
1511
 *
1512
 * @param hGeom handle to the geometry to convert
1513
 * @return a new geometry.
1514
 *
1515
 * @since GDAL 3.1.0
1516
 */
1517

1518
OGRGeometryH OGR_G_RemoveLowerDimensionSubGeoms(const OGRGeometryH hGeom)
18✔
1519

1520
{
1521
    return OGRGeometry::ToHandle(
18✔
1522
        OGRGeometryFactory::removeLowerDimensionSubGeoms(
1523
            OGRGeometry::FromHandle(hGeom)));
36✔
1524
}
1525

1526
/************************************************************************/
1527
/*                          organizePolygons()                          */
1528
/************************************************************************/
1529

1530
struct sPolyExtended
85,332✔
1531
{
1532
    CPL_DISALLOW_COPY_ASSIGN(sPolyExtended)
1533
    sPolyExtended() = default;
60,266✔
1534
    sPolyExtended(sPolyExtended &&) = default;
112,052✔
1535
    sPolyExtended &operator=(sPolyExtended &&) = default;
1536

1537
    OGRGeometry *poGeometry = nullptr;
1538
    OGRCurvePolygon *poPolygon = nullptr;
1539
    OGREnvelope sEnvelope{};
1540
    OGRCurve *poExteriorRing = nullptr;
1541
    OGRPoint poAPoint{};
1542
    int nInitialIndex = 0;
1543
    OGRCurvePolygon *poEnclosingPolygon = nullptr;
1544
    double dfArea = 0.0;
1545
    bool bIsTopLevel = false;
1546
    bool bIsCW = false;
1547
    bool bIsPolygon = false;
1548
};
1549

1550
static bool OGRGeometryFactoryCompareArea(const sPolyExtended &sPoly1,
4,973✔
1551
                                          const sPolyExtended &sPoly2)
1552
{
1553
    return sPoly2.dfArea < sPoly1.dfArea;
4,973✔
1554
}
1555

1556
static bool OGRGeometryFactoryCompareByIndex(const sPolyExtended &sPoly1,
518,784✔
1557
                                             const sPolyExtended &sPoly2)
1558
{
1559
    return sPoly1.nInitialIndex < sPoly2.nInitialIndex;
518,784✔
1560
}
1561

1562
constexpr int N_CRITICAL_PART_NUMBER = 100;
1563

1564
enum OrganizePolygonMethod
1565
{
1566
    METHOD_NORMAL,
1567
    METHOD_SKIP,
1568
    METHOD_ONLY_CCW,
1569
    METHOD_CCW_INNER_JUST_AFTER_CW_OUTER
1570
};
1571

1572
/**
1573
 * \brief Organize polygons based on geometries.
1574
 *
1575
 * Analyse a set of rings (passed as simple polygons), and based on a
1576
 * geometric analysis convert them into a polygon with inner rings,
1577
 * (or a MultiPolygon if dealing with more than one polygon) that follow the
1578
 * OGC Simple Feature specification.
1579
 *
1580
 * All the input geometries must be OGRPolygon/OGRCurvePolygon with only a valid
1581
 * exterior ring (at least 4 points) and no interior rings.
1582
 *
1583
 * The passed in geometries become the responsibility of the method, but the
1584
 * papoPolygons "pointer array" remains owned by the caller.
1585
 *
1586
 * For faster computation, a polygon is considered to be inside
1587
 * another one if a single point of its external ring is included into the other
1588
 * one. (unless 'OGR_DEBUG_ORGANIZE_POLYGONS' configuration option is set to
1589
 * TRUE. In that case, a slower algorithm that tests exact topological
1590
 * relationships is used if GEOS is available.)
1591
 *
1592
 * In cases where a big number of polygons is passed to this function, the
1593
 * default processing may be really slow. You can skip the processing by adding
1594
 * METHOD=SKIP to the option list (the result of the function will be a
1595
 * multi-polygon with all polygons as toplevel polygons) or only make it analyze
1596
 * counterclockwise polygons by adding METHOD=ONLY_CCW to the option list if you
1597
 * can assume that the outline of holes is counterclockwise defined (this is the
1598
 * convention for example in shapefiles, Personal Geodatabases or File
1599
 * Geodatabases).
1600
 *
1601
 * For FileGDB, in most cases, but not always, a faster method than ONLY_CCW can
1602
 * be used. It is CCW_INNER_JUST_AFTER_CW_OUTER. When using it, inner rings are
1603
 * assumed to be counterclockwise oriented, and following immediately the outer
1604
 * ring (clockwise oriented) that they belong to. If that assumption is not met,
1605
 * an inner ring could be attached to the wrong outer ring, so this method must
1606
 * be used with care.
1607
 *
1608
 * If the OGR_ORGANIZE_POLYGONS configuration option is defined, its value will
1609
 * override the value of the METHOD option of papszOptions (useful to modify the
1610
 * behavior of the shapefile driver)
1611
 *
1612
 * @param papoPolygons array of geometry pointers - should all be OGRPolygons
1613
 * or OGRCurvePolygons. Ownership of the geometries is passed, but not of the
1614
 * array itself.
1615
 * @param nPolygonCount number of items in papoPolygons
1616
 * @param pbIsValidGeometry value may be set to FALSE if an invalid result is
1617
 * detected. Validity checks vary according to the method used and are are limited
1618
 * to what is needed to link inner rings to outer rings, so a result of TRUE
1619
 * does not mean that OGRGeometry::IsValid() returns TRUE.
1620
 * @param papszOptions a list of strings for passing options
1621
 *
1622
 * @return a single resulting geometry (either OGRPolygon, OGRCurvePolygon,
1623
 * OGRMultiPolygon, OGRMultiSurface or OGRGeometryCollection). Returns a
1624
 * POLYGON EMPTY in the case of nPolygonCount being 0.
1625
 */
1626

1627
OGRGeometry *OGRGeometryFactory::organizePolygons(OGRGeometry **papoPolygons,
48,253✔
1628
                                                  int nPolygonCount,
1629
                                                  int *pbIsValidGeometry,
1630
                                                  const char **papszOptions)
1631
{
1632
    if (nPolygonCount == 0)
48,253✔
1633
    {
1634
        if (pbIsValidGeometry)
4✔
1635
            *pbIsValidGeometry = TRUE;
×
1636

1637
        return new OGRPolygon();
4✔
1638
    }
1639

1640
    OGRGeometry *geom = nullptr;
48,249✔
1641
    OrganizePolygonMethod method = METHOD_NORMAL;
48,249✔
1642
    bool bHasCurves = false;
48,249✔
1643

1644
    /* -------------------------------------------------------------------- */
1645
    /*      Trivial case of a single polygon.                               */
1646
    /* -------------------------------------------------------------------- */
1647
    if (nPolygonCount == 1)
48,249✔
1648
    {
1649
        OGRwkbGeometryType eType =
1650
            wkbFlatten(papoPolygons[0]->getGeometryType());
33,438✔
1651

1652
        bool bIsValid = true;
33,438✔
1653

1654
        if (eType != wkbPolygon && eType != wkbCurvePolygon)
33,438✔
1655
        {
1656
            CPLError(CE_Warning, CPLE_AppDefined,
3✔
1657
                     "organizePolygons() received a non-Polygon geometry.");
1658
            bIsValid = false;
3✔
1659
            delete papoPolygons[0];
3✔
1660
            geom = new OGRPolygon();
3✔
1661
        }
1662
        else
1663
        {
1664
            geom = papoPolygons[0];
33,435✔
1665
        }
1666

1667
        papoPolygons[0] = nullptr;
33,438✔
1668

1669
        if (pbIsValidGeometry)
33,438✔
1670
            *pbIsValidGeometry = bIsValid;
33,426✔
1671

1672
        return geom;
33,438✔
1673
    }
1674

1675
    bool bUseFastVersion = true;
14,811✔
1676
    if (CPLTestBool(CPLGetConfigOption("OGR_DEBUG_ORGANIZE_POLYGONS", "NO")))
14,811✔
1677
    {
1678
        /* ------------------------------------------------------------------ */
1679
        /*      A wee bit of a warning.                                       */
1680
        /* ------------------------------------------------------------------ */
1681
        static int firstTime = 1;
1682
        // cppcheck-suppress knownConditionTrueFalse
1683
        if (!haveGEOS() && firstTime)
×
1684
        {
1685
            CPLDebug(
×
1686
                "OGR",
1687
                "In OGR_DEBUG_ORGANIZE_POLYGONS mode, GDAL should be built "
1688
                "with GEOS support enabled in order "
1689
                "OGRGeometryFactory::organizePolygons to provide reliable "
1690
                "results on complex polygons.");
1691
            firstTime = 0;
×
1692
        }
1693
        // cppcheck-suppress knownConditionTrueFalse
1694
        bUseFastVersion = !haveGEOS();
×
1695
    }
1696

1697
    /* -------------------------------------------------------------------- */
1698
    /*      Setup per polygon envelope and area information.                */
1699
    /* -------------------------------------------------------------------- */
1700
    std::vector<sPolyExtended> asPolyEx;
29,622✔
1701
    asPolyEx.reserve(nPolygonCount);
14,811✔
1702

1703
    bool bValidTopology = true;
14,811✔
1704
    bool bMixedUpGeometries = false;
14,811✔
1705
    bool bFoundCCW = false;
14,811✔
1706

1707
    const char *pszMethodValue = CSLFetchNameValue(papszOptions, "METHOD");
14,811✔
1708
    const char *pszMethodValueOption =
1709
        CPLGetConfigOption("OGR_ORGANIZE_POLYGONS", nullptr);
14,811✔
1710
    if (pszMethodValueOption != nullptr && pszMethodValueOption[0] != '\0')
14,811✔
1711
        pszMethodValue = pszMethodValueOption;
13,944✔
1712

1713
    if (pszMethodValue != nullptr)
14,811✔
1714
    {
1715
        if (EQUAL(pszMethodValue, "SKIP"))
14,316✔
1716
        {
1717
            method = METHOD_SKIP;
13,948✔
1718
            bMixedUpGeometries = true;
13,948✔
1719
        }
1720
        else if (EQUAL(pszMethodValue, "ONLY_CCW"))
368✔
1721
        {
1722
            method = METHOD_ONLY_CCW;
296✔
1723
        }
1724
        else if (EQUAL(pszMethodValue, "CCW_INNER_JUST_AFTER_CW_OUTER"))
72✔
1725
        {
1726
            method = METHOD_CCW_INNER_JUST_AFTER_CW_OUTER;
×
1727
        }
1728
        else if (!EQUAL(pszMethodValue, "DEFAULT"))
72✔
1729
        {
1730
            CPLError(CE_Warning, CPLE_AppDefined,
×
1731
                     "Unrecognized value for METHOD option : %s",
1732
                     pszMethodValue);
1733
        }
1734
    }
1735

1736
    int nCountCWPolygon = 0;
14,811✔
1737
    int indexOfCWPolygon = -1;
14,811✔
1738

1739
    for (int i = 0; i < nPolygonCount; i++)
75,080✔
1740
    {
1741
        OGRwkbGeometryType eType =
1742
            wkbFlatten(papoPolygons[i]->getGeometryType());
60,269✔
1743

1744
        if (eType != wkbPolygon && eType != wkbCurvePolygon)
60,269✔
1745
        {
1746
            // Ignore any points or lines that find their way in here.
1747
            CPLError(CE_Warning, CPLE_AppDefined,
3✔
1748
                     "organizePolygons() received a non-Polygon geometry.");
1749
            delete papoPolygons[i];
3✔
1750
            continue;
3✔
1751
        }
1752

1753
        sPolyExtended sPolyEx;
120,532✔
1754

1755
        sPolyEx.nInitialIndex = i;
60,266✔
1756
        sPolyEx.poGeometry = papoPolygons[i];
60,266✔
1757
        sPolyEx.poPolygon = papoPolygons[i]->toCurvePolygon();
60,266✔
1758

1759
        papoPolygons[i]->getEnvelope(&sPolyEx.sEnvelope);
60,266✔
1760

1761
        if (eType == wkbCurvePolygon)
60,266✔
1762
            bHasCurves = true;
33✔
1763
        if (!sPolyEx.poPolygon->IsEmpty() &&
60,266✔
1764
            sPolyEx.poPolygon->getNumInteriorRings() == 0 &&
120,532✔
1765
            sPolyEx.poPolygon->getExteriorRingCurve()->getNumPoints() >= 4)
60,266✔
1766
        {
1767
            if (method != METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
60,264✔
1768
                sPolyEx.dfArea = sPolyEx.poPolygon->get_Area();
60,264✔
1769
            sPolyEx.poExteriorRing = sPolyEx.poPolygon->getExteriorRingCurve();
60,264✔
1770
            sPolyEx.poExteriorRing->StartPoint(&sPolyEx.poAPoint);
60,264✔
1771
            if (eType == wkbPolygon)
60,264✔
1772
            {
1773
                sPolyEx.bIsCW = CPL_TO_BOOL(
60,231✔
1774
                    sPolyEx.poExteriorRing->toLinearRing()->isClockwise());
60,231✔
1775
                sPolyEx.bIsPolygon = true;
60,231✔
1776
            }
1777
            else
1778
            {
1779
                OGRLineString *poLS = sPolyEx.poExteriorRing->CurveToLine();
33✔
1780
                OGRLinearRing oLR;
66✔
1781
                oLR.addSubLineString(poLS);
33✔
1782
                sPolyEx.bIsCW = CPL_TO_BOOL(oLR.isClockwise());
33✔
1783
                sPolyEx.bIsPolygon = false;
33✔
1784
                delete poLS;
33✔
1785
            }
1786
            if (sPolyEx.bIsCW)
60,264✔
1787
            {
1788
                indexOfCWPolygon = i;
17,168✔
1789
                nCountCWPolygon++;
17,168✔
1790
            }
1791
            if (!bFoundCCW)
60,264✔
1792
                bFoundCCW = !(sPolyEx.bIsCW);
29,646✔
1793
        }
1794
        else
1795
        {
1796
            if (!bMixedUpGeometries)
2✔
1797
            {
1798
                CPLError(CE_Warning, CPLE_AppDefined,
×
1799
                         "organizePolygons() received an unexpected geometry.  "
1800
                         "Either a polygon with interior rings, or a polygon "
1801
                         "with less than 4 points, or a non-Polygon geometry.  "
1802
                         "Return arguments as a collection.");
1803
                bMixedUpGeometries = true;
×
1804
            }
1805
        }
1806

1807
        asPolyEx.push_back(std::move(sPolyEx));
60,266✔
1808
    }
1809

1810
    // If we are in ONLY_CCW mode and that we have found that there is only one
1811
    // outer ring, then it is pretty easy : we can assume that all other rings
1812
    // are inside.
1813
    if ((method == METHOD_ONLY_CCW ||
14,811✔
1814
         method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER) &&
296✔
1815
        nCountCWPolygon == 1 && bUseFastVersion)
115✔
1816
    {
1817
        OGRCurvePolygon *poCP = asPolyEx[indexOfCWPolygon].poPolygon;
115✔
1818
        for (int i = 0; i < static_cast<int>(asPolyEx.size()); i++)
391✔
1819
        {
1820
            if (i != indexOfCWPolygon)
276✔
1821
            {
1822
                poCP->addRingDirectly(
161✔
1823
                    asPolyEx[i].poPolygon->stealExteriorRingCurve());
161✔
1824
                delete asPolyEx[i].poPolygon;
161✔
1825
            }
1826
        }
1827

1828
        if (pbIsValidGeometry)
115✔
1829
            *pbIsValidGeometry = TRUE;
113✔
1830
        return poCP;
115✔
1831
    }
1832

1833
    if (method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER && asPolyEx[0].bIsCW)
14,696✔
1834
    {
1835
        // Inner rings are CCW oriented and follow immediately the outer
1836
        // ring (that is CW oriented) in which they are included.
1837
        OGRMultiSurface *poMulti = nullptr;
×
1838
        OGRCurvePolygon *poCur = asPolyEx[0].poPolygon;
×
1839
        OGRGeometry *poRet = poCur;
×
1840
        // We have already checked that the first ring is CW.
1841
        OGREnvelope *psEnvelope = &(asPolyEx[0].sEnvelope);
×
1842
        for (std::size_t i = 1; i < asPolyEx.size(); i++)
×
1843
        {
1844
            if (asPolyEx[i].bIsCW)
×
1845
            {
1846
                if (poMulti == nullptr)
×
1847
                {
1848
                    if (bHasCurves)
×
1849
                        poMulti = new OGRMultiSurface();
×
1850
                    else
1851
                        poMulti = new OGRMultiPolygon();
×
1852
                    poRet = poMulti;
×
1853
                    poMulti->addGeometryDirectly(poCur);
×
1854
                }
1855
                poCur = asPolyEx[i].poPolygon;
×
1856
                poMulti->addGeometryDirectly(poCur);
×
1857
                psEnvelope = &(asPolyEx[i].sEnvelope);
×
1858
            }
1859
            else
1860
            {
1861
                poCur->addRingDirectly(
×
1862
                    asPolyEx[i].poPolygon->stealExteriorRingCurve());
×
1863
                if (!(asPolyEx[i].poAPoint.getX() >= psEnvelope->MinX &&
×
1864
                      asPolyEx[i].poAPoint.getX() <= psEnvelope->MaxX &&
×
1865
                      asPolyEx[i].poAPoint.getY() >= psEnvelope->MinY &&
×
1866
                      asPolyEx[i].poAPoint.getY() <= psEnvelope->MaxY))
×
1867
                {
1868
                    CPLError(CE_Warning, CPLE_AppDefined,
×
1869
                             "Part %d does not respect "
1870
                             "CCW_INNER_JUST_AFTER_CW_OUTER rule",
1871
                             static_cast<int>(i));
1872
                }
1873
                delete asPolyEx[i].poPolygon;
×
1874
            }
1875
        }
1876

1877
        if (pbIsValidGeometry)
×
1878
            *pbIsValidGeometry = TRUE;
×
1879
        return poRet;
×
1880
    }
1881
    else if (method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
14,696✔
1882
    {
1883
        method = METHOD_ONLY_CCW;
×
1884
        for (std::size_t i = 0; i < asPolyEx.size(); i++)
×
1885
            asPolyEx[i].dfArea = asPolyEx[i].poPolygon->get_Area();
×
1886
    }
1887

1888
    // Emits a warning if the number of parts is sufficiently big to anticipate
1889
    // for very long computation time, and the user didn't specify an explicit
1890
    // method.
1891
    if (nPolygonCount > N_CRITICAL_PART_NUMBER && method == METHOD_NORMAL &&
14,696✔
1892
        pszMethodValue == nullptr)
1893
    {
1894
        static int firstTime = 1;
1895
        if (firstTime)
×
1896
        {
1897
            if (bFoundCCW)
×
1898
            {
1899
                CPLError(
×
1900
                    CE_Warning, CPLE_AppDefined,
1901
                    "organizePolygons() received a polygon with more than %d "
1902
                    "parts. The processing may be really slow.  "
1903
                    "You can skip the processing by setting METHOD=SKIP, "
1904
                    "or only make it analyze counter-clock wise parts by "
1905
                    "setting METHOD=ONLY_CCW if you can assume that the "
1906
                    "outline of holes is counter-clock wise defined",
1907
                    N_CRITICAL_PART_NUMBER);
1908
            }
1909
            else
1910
            {
1911
                CPLError(
×
1912
                    CE_Warning, CPLE_AppDefined,
1913
                    "organizePolygons() received a polygon with more than %d "
1914
                    "parts.  The processing may be really slow.  "
1915
                    "You can skip the processing by setting METHOD=SKIP.",
1916
                    N_CRITICAL_PART_NUMBER);
1917
            }
1918
            firstTime = 0;
×
1919
        }
1920
    }
1921

1922
    /* This a nulti-step algorithm :
1923
       1) Sort polygons by descending areas
1924
       2) For each polygon of rank i, find its smallest enclosing polygon
1925
          among the polygons of rank [i-1 ... 0]. If there are no such polygon,
1926
          this is a top-level polygon. Otherwise, depending on if the enclosing
1927
          polygon is top-level or not, we can decide if we are top-level or not
1928
       3) Re-sort the polygons to retrieve their initial order (nicer for
1929
          some applications)
1930
       4) For each non top-level polygon (= inner ring), add it to its
1931
          outer ring
1932
       5) Add the top-level polygons to the multipolygon
1933

1934
       Complexity : O(nPolygonCount^2)
1935
    */
1936

1937
    /* Compute how each polygon relate to the other ones
1938
       To save a bit of computation we always begin the computation by a test
1939
       on the envelope. We also take into account the areas to avoid some
1940
       useless tests.  (A contains B implies envelop(A) contains envelop(B)
1941
       and area(A) > area(B)) In practice, we can hope that few full geometry
1942
       intersection of inclusion test is done:
1943
       * if the polygons are well separated geographically (a set of islands
1944
       for example), no full geometry intersection or inclusion test is done.
1945
       (the envelopes don't intersect each other)
1946

1947
       * if the polygons are 'lake inside an island inside a lake inside an
1948
       area' and that each polygon is much smaller than its enclosing one,
1949
       their bounding boxes are strictly contained into each other, and thus,
1950
       no full geometry intersection or inclusion test is done
1951
    */
1952

1953
    if (!bMixedUpGeometries)
14,696✔
1954
    {
1955
        // STEP 1: Sort polygons by descending area.
1956
        std::sort(asPolyEx.begin(), asPolyEx.end(),
748✔
1957
                  OGRGeometryFactoryCompareArea);
1958
    }
1959
    papoPolygons = nullptr;  // Just to use to avoid it afterwards.
14,696✔
1960

1961
    /* -------------------------------------------------------------------- */
1962
    /*      Compute relationships, if things seem well structured.          */
1963
    /* -------------------------------------------------------------------- */
1964

1965
    // The first (largest) polygon is necessarily top-level.
1966
    asPolyEx[0].bIsTopLevel = true;
14,696✔
1967
    asPolyEx[0].poEnclosingPolygon = nullptr;
14,696✔
1968

1969
    int nCountTopLevel = 1;
14,696✔
1970

1971
    // STEP 2.
1972
    for (int i = 1; !bMixedUpGeometries && bValidTopology &&
18,708✔
1973
                    i < static_cast<int>(asPolyEx.size());
2,380✔
1974
         i++)
1975
    {
1976
        if (method == METHOD_ONLY_CCW && asPolyEx[i].bIsCW)
1,632✔
1977
        {
1978
            nCountTopLevel++;
322✔
1979
            asPolyEx[i].bIsTopLevel = true;
322✔
1980
            asPolyEx[i].poEnclosingPolygon = nullptr;
322✔
1981
            continue;
322✔
1982
        }
1983

1984
        int j = i - 1;  // Used after for.
1,310✔
1985
        for (; bValidTopology && j >= 0; j--)
4,280✔
1986
        {
1987
            bool b_i_inside_j = false;
3,799✔
1988

1989
            if (method == METHOD_ONLY_CCW && asPolyEx[j].bIsCW == false)
3,799✔
1990
            {
1991
                // In that mode, i which is CCW if we reach here can only be
1992
                // included in a CW polygon.
1993
                continue;
810✔
1994
            }
1995

1996
            if (asPolyEx[j].sEnvelope.Contains(asPolyEx[i].sEnvelope))
2,989✔
1997
            {
1998
                if (bUseFastVersion)
835✔
1999
                {
2000
                    if (method == METHOD_ONLY_CCW && j == 0)
835✔
2001
                    {
2002
                        // We are testing if a CCW ring is in the biggest CW
2003
                        // ring It *must* be inside as this is the last
2004
                        // candidate, otherwise the winding order rules is
2005
                        // broken.
2006
                        b_i_inside_j = true;
231✔
2007
                    }
2008
                    else if (asPolyEx[i].bIsPolygon && asPolyEx[j].bIsPolygon &&
1,208✔
2009
                             asPolyEx[j]
604✔
2010
                                 .poExteriorRing->toLinearRing()
604✔
2011
                                 ->isPointOnRingBoundary(&asPolyEx[i].poAPoint,
604✔
2012
                                                         FALSE))
2013
                    {
2014
                        OGRLinearRing *poLR_i =
2015
                            asPolyEx[i].poExteriorRing->toLinearRing();
16✔
2016
                        OGRLinearRing *poLR_j =
2017
                            asPolyEx[j].poExteriorRing->toLinearRing();
16✔
2018

2019
                        // If the point of i is on the boundary of j, we will
2020
                        // iterate over the other points of i.
2021
                        const int nPoints = poLR_i->getNumPoints();
16✔
2022
                        int k = 1;  // Used after for.
16✔
2023
                        OGRPoint previousPoint = asPolyEx[i].poAPoint;
32✔
2024
                        for (; k < nPoints; k++)
31✔
2025
                        {
2026
                            OGRPoint point;
30✔
2027
                            poLR_i->getPoint(k, &point);
30✔
2028
                            if (point.getX() == previousPoint.getX() &&
32✔
2029
                                point.getY() == previousPoint.getY())
2✔
2030
                            {
2031
                                continue;
×
2032
                            }
2033
                            if (poLR_j->isPointOnRingBoundary(&point, FALSE))
30✔
2034
                            {
2035
                                // If it is on the boundary of j, iterate again.
2036
                            }
2037
                            else if (poLR_j->isPointInRing(&point, FALSE))
15✔
2038
                            {
2039
                                // If then point is strictly included in j, then
2040
                                // i is considered inside j.
2041
                                b_i_inside_j = true;
13✔
2042
                                break;
13✔
2043
                            }
2044
                            else
2045
                            {
2046
                                // If it is outside, then i cannot be inside j.
2047
                                break;
2✔
2048
                            }
2049
                            previousPoint = std::move(point);
15✔
2050
                        }
2051
                        if (!b_i_inside_j && k == nPoints && nPoints > 2)
16✔
2052
                        {
2053
                            // All points of i are on the boundary of j.
2054
                            // Take a point in the middle of a segment of i and
2055
                            // test it against j.
2056
                            poLR_i->getPoint(0, &previousPoint);
1✔
2057
                            for (k = 1; k < nPoints; k++)
2✔
2058
                            {
2059
                                OGRPoint point;
2✔
2060
                                poLR_i->getPoint(k, &point);
2✔
2061
                                if (point.getX() == previousPoint.getX() &&
2✔
2062
                                    point.getY() == previousPoint.getY())
×
2063
                                {
2064
                                    continue;
×
2065
                                }
2066
                                OGRPoint pointMiddle;
2✔
2067
                                pointMiddle.setX(
2✔
2068
                                    (point.getX() + previousPoint.getX()) / 2);
2✔
2069
                                pointMiddle.setY(
2✔
2070
                                    (point.getY() + previousPoint.getY()) / 2);
2✔
2071
                                if (poLR_j->isPointOnRingBoundary(&pointMiddle,
2✔
2072
                                                                  FALSE))
2✔
2073
                                {
2074
                                    // If it is on the boundary of j, iterate
2075
                                    // again.
2076
                                }
2077
                                else if (poLR_j->isPointInRing(&pointMiddle,
1✔
2078
                                                               FALSE))
1✔
2079
                                {
2080
                                    // If then point is strictly included in j,
2081
                                    // then i is considered inside j.
2082
                                    b_i_inside_j = true;
1✔
2083
                                    break;
1✔
2084
                                }
2085
                                else
2086
                                {
2087
                                    // If it is outside, then i cannot be inside
2088
                                    // j.
2089
                                    break;
×
2090
                                }
2091
                                previousPoint = std::move(point);
1✔
2092
                            }
2093
                        }
2094
                    }
2095
                    // Note that isPointInRing only test strict inclusion in the
2096
                    // ring.
2097
                    else if (asPolyEx[i].bIsPolygon && asPolyEx[j].bIsPolygon &&
1,176✔
2098
                             asPolyEx[j]
588✔
2099
                                 .poExteriorRing->toLinearRing()
588✔
2100
                                 ->isPointInRing(&asPolyEx[i].poAPoint, FALSE))
588✔
2101
                    {
2102
                        b_i_inside_j = true;
584✔
2103
                    }
2104
                }
2105
                else if (asPolyEx[j].poPolygon->Contains(asPolyEx[i].poPolygon))
×
2106
                {
2107
                    b_i_inside_j = true;
×
2108
                }
2109
            }
2110

2111
            if (b_i_inside_j)
2,989✔
2112
            {
2113
                if (asPolyEx[j].bIsTopLevel)
829✔
2114
                {
2115
                    // We are a lake.
2116
                    asPolyEx[i].bIsTopLevel = false;
828✔
2117
                    asPolyEx[i].poEnclosingPolygon = asPolyEx[j].poPolygon;
828✔
2118
                }
2119
                else
2120
                {
2121
                    // We are included in a something not toplevel (a lake),
2122
                    // so in OGCSF we are considered as toplevel too.
2123
                    nCountTopLevel++;
1✔
2124
                    asPolyEx[i].bIsTopLevel = true;
1✔
2125
                    asPolyEx[i].poEnclosingPolygon = nullptr;
1✔
2126
                }
2127
                break;
829✔
2128
            }
2129
            // Use Overlaps instead of Intersects to be more
2130
            // tolerant about touching polygons.
2131
            else if (bUseFastVersion ||
×
2132
                     !asPolyEx[i].sEnvelope.Intersects(asPolyEx[j].sEnvelope) ||
2,160✔
2133
                     !asPolyEx[i].poPolygon->Overlaps(asPolyEx[j].poPolygon))
×
2134
            {
2135
            }
2136
            else
2137
            {
2138
                // Bad... The polygons are intersecting but no one is
2139
                // contained inside the other one. This is a really broken
2140
                // case. We just make a multipolygon with the whole set of
2141
                // polygons.
2142
                bValidTopology = false;
×
2143
#ifdef DEBUG
2144
                char *wkt1 = nullptr;
×
2145
                char *wkt2 = nullptr;
×
2146
                asPolyEx[i].poPolygon->exportToWkt(&wkt1);
×
2147
                asPolyEx[j].poPolygon->exportToWkt(&wkt2);
×
2148
                CPLDebug("OGR",
×
2149
                         "Bad intersection for polygons %d and %d\n"
2150
                         "geom %d: %s\n"
2151
                         "geom %d: %s",
2152
                         static_cast<int>(i), j, static_cast<int>(i), wkt1, j,
2153
                         wkt2);
2154
                CPLFree(wkt1);
×
2155
                CPLFree(wkt2);
×
2156
#endif
2157
            }
2158
        }
2159

2160
        if (j < 0)
1,310✔
2161
        {
2162
            // We come here because we are not included in anything.
2163
            // We are toplevel.
2164
            nCountTopLevel++;
481✔
2165
            asPolyEx[i].bIsTopLevel = true;
481✔
2166
            asPolyEx[i].poEnclosingPolygon = nullptr;
481✔
2167
        }
2168
    }
2169

2170
    if (pbIsValidGeometry)
14,696✔
2171
        *pbIsValidGeometry = bValidTopology && !bMixedUpGeometries;
14,195✔
2172

2173
    /* --------------------------------------------------------------------- */
2174
    /*      Things broke down - just mark everything as top-level so it gets */
2175
    /*      turned into a multipolygon.                                      */
2176
    /* --------------------------------------------------------------------- */
2177
    if (!bValidTopology || bMixedUpGeometries)
14,696✔
2178
    {
2179
        for (auto &sPolyEx : asPolyEx)
71,558✔
2180
        {
2181
            sPolyEx.bIsTopLevel = true;
57,610✔
2182
        }
2183
        nCountTopLevel = static_cast<int>(asPolyEx.size());
13,948✔
2184
    }
2185

2186
    /* -------------------------------------------------------------------- */
2187
    /*      Try to turn into one or more polygons based on the ring         */
2188
    /*      relationships.                                                  */
2189
    /* -------------------------------------------------------------------- */
2190
    // STEP 3: Sort again in initial order.
2191
    std::sort(asPolyEx.begin(), asPolyEx.end(),
14,696✔
2192
              OGRGeometryFactoryCompareByIndex);
2193

2194
    // STEP 4: Add holes as rings of their enclosing polygon.
2195
    for (auto &sPolyEx : asPolyEx)
74,686✔
2196
    {
2197
        if (sPolyEx.bIsTopLevel == false)
59,990✔
2198
        {
2199
            sPolyEx.poEnclosingPolygon->addRingDirectly(
828✔
2200
                sPolyEx.poPolygon->stealExteriorRingCurve());
828✔
2201
            delete sPolyEx.poPolygon;
828✔
2202
        }
2203
        else if (nCountTopLevel == 1)
59,162✔
2204
        {
2205
            geom = sPolyEx.poPolygon;
91✔
2206
        }
2207
    }
2208

2209
    // STEP 5: Add toplevel polygons.
2210
    if (nCountTopLevel > 1)
14,696✔
2211
    {
2212
        OGRGeometryCollection *poGC =
2213
            bHasCurves ? new OGRMultiSurface() : new OGRMultiPolygon();
14,605✔
2214
        for (auto &sPolyEx : asPolyEx)
74,386✔
2215
        {
2216
            if (sPolyEx.bIsTopLevel)
59,781✔
2217
            {
2218
                poGC->addGeometryDirectly(sPolyEx.poPolygon);
59,071✔
2219
            }
2220
        }
2221
        geom = poGC;
14,605✔
2222
    }
2223

2224
    return geom;
14,696✔
2225
}
2226

2227
/************************************************************************/
2228
/*                           createFromGML()                            */
2229
/************************************************************************/
2230

2231
/**
2232
 * \brief Create geometry from GML.
2233
 *
2234
 * This method translates a fragment of GML containing only the geometry
2235
 * portion into a corresponding OGRGeometry.  There are many limitations
2236
 * on the forms of GML geometries supported by this parser, but they are
2237
 * too numerous to list here.
2238
 *
2239
 * The following GML2 elements are parsed : Point, LineString, Polygon,
2240
 * MultiPoint, MultiLineString, MultiPolygon, MultiGeometry.
2241
 *
2242
 * The following GML3 elements are parsed : Surface,
2243
 * MultiSurface, PolygonPatch, Triangle, Rectangle, Curve, MultiCurve,
2244
 * LineStringSegment, Arc, Circle, CompositeSurface, OrientableSurface, Solid,
2245
 * Tin, TriangulatedSurface.
2246
 *
2247
 * Arc and Circle elements are returned as curves by default. Stroking to
2248
 * linestrings can be done with
2249
 * OGR_G_ForceTo(hGeom, OGR_GT_GetLinear(OGR_G_GetGeometryType(hGeom)), NULL).
2250
 * A 4 degrees step is used by default, unless the user
2251
 * has overridden the value with the OGR_ARC_STEPSIZE configuration variable.
2252
 *
2253
 * The C function OGR_G_CreateFromGML() is the same as this method.
2254
 *
2255
 * @param pszData The GML fragment for the geometry.
2256
 *
2257
 * @return a geometry on success, or NULL on error.
2258
 *
2259
 * @see OGR_G_ForceTo()
2260
 * @see OGR_GT_GetLinear()
2261
 * @see OGR_G_GetGeometryType()
2262
 */
2263

2264
OGRGeometry *OGRGeometryFactory::createFromGML(const char *pszData)
×
2265

2266
{
2267
    OGRGeometryH hGeom;
2268

2269
    hGeom = OGR_G_CreateFromGML(pszData);
×
2270

2271
    return OGRGeometry::FromHandle(hGeom);
×
2272
}
2273

2274
/************************************************************************/
2275
/*                           createFromGEOS()                           */
2276
/************************************************************************/
2277

2278
/** Builds a OGRGeometry* from a GEOSGeom.
2279
 * @param hGEOSCtxt GEOS context
2280
 * @param geosGeom GEOS geometry
2281
 * @return a OGRGeometry*
2282
 */
2283
OGRGeometry *OGRGeometryFactory::createFromGEOS(
3,685✔
2284
    UNUSED_IF_NO_GEOS GEOSContextHandle_t hGEOSCtxt,
2285
    UNUSED_IF_NO_GEOS GEOSGeom geosGeom)
2286

2287
{
2288
#ifndef HAVE_GEOS
2289

2290
    CPLError(CE_Failure, CPLE_NotSupported, "GEOS support not enabled.");
2291
    return nullptr;
2292

2293
#else
2294

2295
    size_t nSize = 0;
3,685✔
2296
    unsigned char *pabyBuf = nullptr;
3,685✔
2297
    OGRGeometry *poGeometry = nullptr;
3,685✔
2298

2299
    // Special case as POINT EMPTY cannot be translated to WKB.
2300
    if (GEOSGeomTypeId_r(hGEOSCtxt, geosGeom) == GEOS_POINT &&
3,770✔
2301
        GEOSisEmpty_r(hGEOSCtxt, geosGeom))
85✔
2302
        return new OGRPoint();
14✔
2303

2304
    const int nCoordDim =
2305
        GEOSGeom_getCoordinateDimension_r(hGEOSCtxt, geosGeom);
3,670✔
2306
    GEOSWKBWriter *wkbwriter = GEOSWKBWriter_create_r(hGEOSCtxt);
3,671✔
2307
    GEOSWKBWriter_setOutputDimension_r(hGEOSCtxt, wkbwriter, nCoordDim);
3,670✔
2308
    pabyBuf = GEOSWKBWriter_write_r(hGEOSCtxt, wkbwriter, geosGeom, &nSize);
3,670✔
2309
    GEOSWKBWriter_destroy_r(hGEOSCtxt, wkbwriter);
3,669✔
2310

2311
    if (pabyBuf == nullptr || nSize == 0)
3,669✔
2312
    {
UNCOV
2313
        return nullptr;
×
2314
    }
2315

2316
    if (OGRGeometryFactory::createFromWkb(pabyBuf, nullptr, &poGeometry,
3,669✔
2317
                                          static_cast<int>(nSize)) !=
3,670✔
2318
        OGRERR_NONE)
2319
    {
2320
        poGeometry = nullptr;
×
2321
    }
2322

2323
    GEOSFree_r(hGEOSCtxt, pabyBuf);
3,670✔
2324

2325
    return poGeometry;
3,670✔
2326

2327
#endif  // HAVE_GEOS
2328
}
2329

2330
/************************************************************************/
2331
/*                              haveGEOS()                              */
2332
/************************************************************************/
2333

2334
/**
2335
 * \brief Test if GEOS enabled.
2336
 *
2337
 * This static method returns TRUE if GEOS support is built into OGR,
2338
 * otherwise it returns FALSE.
2339
 *
2340
 * @return TRUE if available, otherwise FALSE.
2341
 */
2342

2343
bool OGRGeometryFactory::haveGEOS()
33,239✔
2344

2345
{
2346
#ifndef HAVE_GEOS
2347
    return false;
2348
#else
2349
    return true;
33,239✔
2350
#endif
2351
}
2352

2353
/************************************************************************/
2354
/*                           createFromFgf()                            */
2355
/************************************************************************/
2356

2357
/**
2358
 * \brief Create a geometry object of the appropriate type from its FGF (FDO
2359
 * Geometry Format) binary representation.
2360
 *
2361
 * Also note that this is a static method, and that there
2362
 * is no need to instantiate an OGRGeometryFactory object.
2363
 *
2364
 * The C function OGR_G_CreateFromFgf() is the same as this method.
2365
 *
2366
 * @param pabyData pointer to the input BLOB data.
2367
 * @param poSR pointer to the spatial reference to be assigned to the
2368
 *             created geometry object.  This may be NULL.
2369
 * @param ppoReturn the newly created geometry object will be assigned to the
2370
 *                  indicated pointer on return.  This will be NULL in case
2371
 *                  of failure, but NULL might be a valid return for a NULL
2372
 * shape.
2373
 * @param nBytes the number of bytes available in pabyData.
2374
 * @param pnBytesConsumed if not NULL, it will be set to the number of bytes
2375
 * consumed (at most nBytes).
2376
 *
2377
 * @return OGRERR_NONE if all goes well, otherwise any of
2378
 * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
2379
 * OGRERR_CORRUPT_DATA may be returned.
2380
 */
2381

2382
OGRErr OGRGeometryFactory::createFromFgf(const void *pabyData,
291✔
2383
                                         OGRSpatialReference *poSR,
2384
                                         OGRGeometry **ppoReturn, int nBytes,
2385
                                         int *pnBytesConsumed)
2386

2387
{
2388
    return createFromFgfInternal(static_cast<const GByte *>(pabyData), poSR,
291✔
2389
                                 ppoReturn, nBytes, pnBytesConsumed, 0);
291✔
2390
}
2391

2392
/************************************************************************/
2393
/*                       createFromFgfInternal()                        */
2394
/************************************************************************/
2395

2396
OGRErr OGRGeometryFactory::createFromFgfInternal(
294✔
2397
    const unsigned char *pabyData, OGRSpatialReference *poSR,
2398
    OGRGeometry **ppoReturn, int nBytes, int *pnBytesConsumed, int nRecLevel)
2399
{
2400
    // Arbitrary value, but certainly large enough for reasonable usages.
2401
    if (nRecLevel == 32)
294✔
2402
    {
2403
        CPLError(CE_Failure, CPLE_AppDefined,
×
2404
                 "Too many recursion levels (%d) while parsing FGF geometry.",
2405
                 nRecLevel);
2406
        return OGRERR_CORRUPT_DATA;
×
2407
    }
2408

2409
    *ppoReturn = nullptr;
294✔
2410

2411
    if (nBytes < 4)
294✔
2412
        return OGRERR_NOT_ENOUGH_DATA;
109✔
2413

2414
    /* -------------------------------------------------------------------- */
2415
    /*      Decode the geometry type.                                       */
2416
    /* -------------------------------------------------------------------- */
2417
    GInt32 nGType = 0;
185✔
2418
    memcpy(&nGType, pabyData + 0, 4);
185✔
2419
    CPL_LSBPTR32(&nGType);
185✔
2420

2421
    if (nGType < 0 || nGType > 13)
185✔
2422
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
171✔
2423

2424
    /* -------------------------------------------------------------------- */
2425
    /*      Decode the dimensionality if appropriate.                       */
2426
    /* -------------------------------------------------------------------- */
2427
    int nTupleSize = 0;
14✔
2428
    GInt32 nGDim = 0;
14✔
2429

2430
    // TODO: Why is this a switch?
2431
    switch (nGType)
14✔
2432
    {
2433
        case 1:  // Point
9✔
2434
        case 2:  // LineString
2435
        case 3:  // Polygon
2436
            if (nBytes < 8)
9✔
2437
                return OGRERR_NOT_ENOUGH_DATA;
×
2438

2439
            memcpy(&nGDim, pabyData + 4, 4);
9✔
2440
            CPL_LSBPTR32(&nGDim);
9✔
2441

2442
            if (nGDim < 0 || nGDim > 3)
9✔
2443
                return OGRERR_CORRUPT_DATA;
×
2444

2445
            nTupleSize = 2;
9✔
2446
            if (nGDim & 0x01)  // Z
9✔
2447
                nTupleSize++;
1✔
2448
            if (nGDim & 0x02)  // M
9✔
2449
                nTupleSize++;
×
2450

2451
            break;
9✔
2452

2453
        default:
5✔
2454
            break;
5✔
2455
    }
2456

2457
    OGRGeometry *poGeom = nullptr;
14✔
2458

2459
    /* -------------------------------------------------------------------- */
2460
    /*      None                                                            */
2461
    /* -------------------------------------------------------------------- */
2462
    if (nGType == 0)
14✔
2463
    {
2464
        if (pnBytesConsumed)
×
2465
            *pnBytesConsumed = 4;
×
2466
    }
2467

2468
    /* -------------------------------------------------------------------- */
2469
    /*      Point                                                           */
2470
    /* -------------------------------------------------------------------- */
2471
    else if (nGType == 1)
14✔
2472
    {
2473
        if (nBytes < nTupleSize * 8 + 8)
3✔
2474
            return OGRERR_NOT_ENOUGH_DATA;
×
2475

2476
        double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
3✔
2477
        memcpy(adfTuple, pabyData + 8, nTupleSize * 8);
3✔
2478
#ifdef CPL_MSB
2479
        for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2480
            CPL_SWAP64PTR(adfTuple + iOrdinal);
2481
#endif
2482
        if (nTupleSize > 2)
3✔
2483
            poGeom = new OGRPoint(adfTuple[0], adfTuple[1], adfTuple[2]);
1✔
2484
        else
2485
            poGeom = new OGRPoint(adfTuple[0], adfTuple[1]);
2✔
2486

2487
        if (pnBytesConsumed)
3✔
2488
            *pnBytesConsumed = 8 + nTupleSize * 8;
1✔
2489
    }
2490

2491
    /* -------------------------------------------------------------------- */
2492
    /*      LineString                                                      */
2493
    /* -------------------------------------------------------------------- */
2494
    else if (nGType == 2)
11✔
2495
    {
2496
        if (nBytes < 12)
2✔
2497
            return OGRERR_NOT_ENOUGH_DATA;
×
2498

2499
        GInt32 nPointCount = 0;
2✔
2500
        memcpy(&nPointCount, pabyData + 8, 4);
2✔
2501
        CPL_LSBPTR32(&nPointCount);
2✔
2502

2503
        if (nPointCount < 0 || nPointCount > INT_MAX / (nTupleSize * 8))
2✔
2504
            return OGRERR_CORRUPT_DATA;
×
2505

2506
        if (nBytes - 12 < nTupleSize * 8 * nPointCount)
2✔
2507
            return OGRERR_NOT_ENOUGH_DATA;
×
2508

2509
        OGRLineString *poLS = new OGRLineString();
2✔
2510
        poGeom = poLS;
2✔
2511
        poLS->setNumPoints(nPointCount);
2✔
2512

2513
        for (int iPoint = 0; iPoint < nPointCount; iPoint++)
4✔
2514
        {
2515
            double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
2✔
2516
            memcpy(adfTuple, pabyData + 12 + 8 * nTupleSize * iPoint,
2✔
2517
                   nTupleSize * 8);
2✔
2518
#ifdef CPL_MSB
2519
            for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2520
                CPL_SWAP64PTR(adfTuple + iOrdinal);
2521
#endif
2522
            if (nTupleSize > 2)
2✔
2523
                poLS->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
×
2524
            else
2525
                poLS->setPoint(iPoint, adfTuple[0], adfTuple[1]);
2✔
2526
        }
2527

2528
        if (pnBytesConsumed)
2✔
2529
            *pnBytesConsumed = 12 + nTupleSize * 8 * nPointCount;
×
2530
    }
2531

2532
    /* -------------------------------------------------------------------- */
2533
    /*      Polygon                                                         */
2534
    /* -------------------------------------------------------------------- */
2535
    else if (nGType == 3)
9✔
2536
    {
2537
        if (nBytes < 12)
4✔
2538
            return OGRERR_NOT_ENOUGH_DATA;
×
2539

2540
        GInt32 nRingCount = 0;
4✔
2541
        memcpy(&nRingCount, pabyData + 8, 4);
4✔
2542
        CPL_LSBPTR32(&nRingCount);
4✔
2543

2544
        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
4✔
2545
            return OGRERR_CORRUPT_DATA;
×
2546

2547
        // Each ring takes at least 4 bytes.
2548
        if (nBytes - 12 < nRingCount * 4)
4✔
2549
            return OGRERR_NOT_ENOUGH_DATA;
×
2550

2551
        int nNextByte = 12;
4✔
2552

2553
        OGRPolygon *poPoly = new OGRPolygon();
4✔
2554
        poGeom = poPoly;
4✔
2555

2556
        for (int iRing = 0; iRing < nRingCount; iRing++)
10✔
2557
        {
2558
            if (nBytes - nNextByte < 4)
6✔
2559
            {
2560
                delete poGeom;
×
2561
                return OGRERR_NOT_ENOUGH_DATA;
×
2562
            }
2563

2564
            GInt32 nPointCount = 0;
6✔
2565
            memcpy(&nPointCount, pabyData + nNextByte, 4);
6✔
2566
            CPL_LSBPTR32(&nPointCount);
6✔
2567

2568
            if (nPointCount < 0 || nPointCount > INT_MAX / (nTupleSize * 8))
6✔
2569
            {
2570
                delete poGeom;
×
2571
                return OGRERR_CORRUPT_DATA;
×
2572
            }
2573

2574
            nNextByte += 4;
6✔
2575

2576
            if (nBytes - nNextByte < nTupleSize * 8 * nPointCount)
6✔
2577
            {
2578
                delete poGeom;
×
2579
                return OGRERR_NOT_ENOUGH_DATA;
×
2580
            }
2581

2582
            OGRLinearRing *poLR = new OGRLinearRing();
6✔
2583
            poLR->setNumPoints(nPointCount);
6✔
2584

2585
            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
12✔
2586
            {
2587
                double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
6✔
2588
                memcpy(adfTuple, pabyData + nNextByte, nTupleSize * 8);
6✔
2589
                nNextByte += nTupleSize * 8;
6✔
2590

2591
#ifdef CPL_MSB
2592
                for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2593
                    CPL_SWAP64PTR(adfTuple + iOrdinal);
2594
#endif
2595
                if (nTupleSize > 2)
6✔
2596
                    poLR->setPoint(iPoint, adfTuple[0], adfTuple[1],
×
2597
                                   adfTuple[2]);
2598
                else
2599
                    poLR->setPoint(iPoint, adfTuple[0], adfTuple[1]);
6✔
2600
            }
2601

2602
            poPoly->addRingDirectly(poLR);
6✔
2603
        }
2604

2605
        if (pnBytesConsumed)
4✔
2606
            *pnBytesConsumed = nNextByte;
2✔
2607
    }
2608

2609
    /* -------------------------------------------------------------------- */
2610
    /*      GeometryCollections of various kinds.                           */
2611
    /* -------------------------------------------------------------------- */
2612
    else if (nGType == 4      // MultiPoint
5✔
2613
             || nGType == 5   // MultiLineString
5✔
2614
             || nGType == 6   // MultiPolygon
5✔
2615
             || nGType == 7)  // MultiGeometry
3✔
2616
    {
2617
        if (nBytes < 8)
5✔
2618
            return OGRERR_NOT_ENOUGH_DATA;
3✔
2619

2620
        GInt32 nGeomCount = 0;
5✔
2621
        memcpy(&nGeomCount, pabyData + 4, 4);
5✔
2622
        CPL_LSBPTR32(&nGeomCount);
5✔
2623

2624
        if (nGeomCount < 0 || nGeomCount > INT_MAX / 4)
5✔
2625
            return OGRERR_CORRUPT_DATA;
×
2626

2627
        // Each geometry takes at least 4 bytes.
2628
        if (nBytes - 8 < 4 * nGeomCount)
5✔
2629
            return OGRERR_NOT_ENOUGH_DATA;
2✔
2630

2631
        OGRGeometryCollection *poGC = nullptr;
3✔
2632
        if (nGType == 4)
3✔
2633
            poGC = new OGRMultiPoint();
×
2634
        else if (nGType == 5)
3✔
2635
            poGC = new OGRMultiLineString();
×
2636
        else if (nGType == 6)
3✔
2637
            poGC = new OGRMultiPolygon();
1✔
2638
        else if (nGType == 7)
2✔
2639
            poGC = new OGRGeometryCollection();
2✔
2640

2641
        int nBytesUsed = 8;
3✔
2642

2643
        for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
5✔
2644
        {
2645
            int nThisGeomSize = 0;
3✔
2646
            OGRGeometry *poThisGeom = nullptr;
3✔
2647

2648
            const OGRErr eErr = createFromFgfInternal(
6✔
2649
                pabyData + nBytesUsed, poSR, &poThisGeom, nBytes - nBytesUsed,
3✔
2650
                &nThisGeomSize, nRecLevel + 1);
2651
            if (eErr != OGRERR_NONE)
3✔
2652
            {
2653
                delete poGC;
×
2654
                return eErr;
1✔
2655
            }
2656

2657
            nBytesUsed += nThisGeomSize;
3✔
2658
            if (poThisGeom != nullptr)
3✔
2659
            {
2660
                const OGRErr eErr2 = poGC->addGeometryDirectly(poThisGeom);
3✔
2661
                if (eErr2 != OGRERR_NONE)
3✔
2662
                {
2663
                    delete poGC;
1✔
2664
                    delete poThisGeom;
1✔
2665
                    return eErr2;
1✔
2666
                }
2667
            }
2668
        }
2669

2670
        poGeom = poGC;
2✔
2671
        if (pnBytesConsumed)
2✔
2672
            *pnBytesConsumed = nBytesUsed;
2✔
2673
    }
2674

2675
    /* -------------------------------------------------------------------- */
2676
    /*      Currently unsupported geometry.                                 */
2677
    /*                                                                      */
2678
    /*      We need to add 10/11/12/13 curve types in some fashion.         */
2679
    /* -------------------------------------------------------------------- */
2680
    else
2681
    {
2682
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
×
2683
    }
2684

2685
    /* -------------------------------------------------------------------- */
2686
    /*      Assign spatial reference system.                                */
2687
    /* -------------------------------------------------------------------- */
2688
    if (poGeom != nullptr && poSR)
11✔
2689
        poGeom->assignSpatialReference(poSR);
×
2690
    *ppoReturn = poGeom;
11✔
2691

2692
    return OGRERR_NONE;
11✔
2693
}
2694

2695
/************************************************************************/
2696
/*                        OGR_G_CreateFromFgf()                         */
2697
/************************************************************************/
2698

2699
/**
2700
 * \brief Create a geometry object of the appropriate type from its FGF
2701
 * (FDO Geometry Format) binary representation.
2702
 *
2703
 * See OGRGeometryFactory::createFromFgf() */
2704
OGRErr CPL_DLL OGR_G_CreateFromFgf(const void *pabyData,
×
2705
                                   OGRSpatialReferenceH hSRS,
2706
                                   OGRGeometryH *phGeometry, int nBytes,
2707
                                   int *pnBytesConsumed)
2708

2709
{
2710
    return OGRGeometryFactory::createFromFgf(
×
2711
        pabyData, OGRSpatialReference::FromHandle(hSRS),
2712
        reinterpret_cast<OGRGeometry **>(phGeometry), nBytes, pnBytesConsumed);
×
2713
}
2714

2715
/************************************************************************/
2716
/*                SplitLineStringAtDateline()                           */
2717
/************************************************************************/
2718

2719
static void SplitLineStringAtDateline(OGRGeometryCollection *poMulti,
8✔
2720
                                      const OGRLineString *poLS,
2721
                                      double dfDateLineOffset, double dfXOffset)
2722
{
2723
    const double dfLeftBorderX = 180 - dfDateLineOffset;
8✔
2724
    const double dfRightBorderX = -180 + dfDateLineOffset;
8✔
2725
    const double dfDiffSpace = 360 - dfDateLineOffset;
8✔
2726

2727
    const bool bIs3D = poLS->getCoordinateDimension() == 3;
8✔
2728
    OGRLineString *poNewLS = new OGRLineString();
8✔
2729
    poMulti->addGeometryDirectly(poNewLS);
8✔
2730
    for (int i = 0; i < poLS->getNumPoints(); i++)
35✔
2731
    {
2732
        const double dfX = poLS->getX(i) + dfXOffset;
27✔
2733
        if (i > 0 && fabs(dfX - (poLS->getX(i - 1) + dfXOffset)) > dfDiffSpace)
27✔
2734
        {
2735
            double dfX1 = poLS->getX(i - 1) + dfXOffset;
9✔
2736
            double dfY1 = poLS->getY(i - 1);
9✔
2737
            double dfZ1 = poLS->getY(i - 1);
9✔
2738
            double dfX2 = poLS->getX(i) + dfXOffset;
9✔
2739
            double dfY2 = poLS->getY(i);
9✔
2740
            double dfZ2 = poLS->getY(i);
9✔
2741

2742
            if (dfX1 > -180 && dfX1 < dfRightBorderX && dfX2 == 180 &&
8✔
2743
                i + 1 < poLS->getNumPoints() &&
×
2744
                poLS->getX(i + 1) + dfXOffset > -180 &&
17✔
2745
                poLS->getX(i + 1) + dfXOffset < dfRightBorderX)
×
2746
            {
2747
                if (bIs3D)
×
2748
                    poNewLS->addPoint(-180, poLS->getY(i), poLS->getZ(i));
×
2749
                else
2750
                    poNewLS->addPoint(-180, poLS->getY(i));
×
2751

2752
                i++;
×
2753

2754
                if (bIs3D)
×
2755
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i),
×
2756
                                      poLS->getZ(i));
2757
                else
2758
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i));
×
2759
                continue;
×
2760
            }
2761
            else if (dfX1 > dfLeftBorderX && dfX1 < 180 && dfX2 == -180 &&
4✔
2762
                     i + 1 < poLS->getNumPoints() &&
×
2763
                     poLS->getX(i + 1) + dfXOffset > dfLeftBorderX &&
13✔
2764
                     poLS->getX(i + 1) + dfXOffset < 180)
×
2765
            {
2766
                if (bIs3D)
×
2767
                    poNewLS->addPoint(180, poLS->getY(i), poLS->getZ(i));
×
2768
                else
2769
                    poNewLS->addPoint(180, poLS->getY(i));
×
2770

2771
                i++;
×
2772

2773
                if (bIs3D)
×
2774
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i),
×
2775
                                      poLS->getZ(i));
2776
                else
2777
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i));
×
2778
                continue;
×
2779
            }
2780

2781
            if (dfX1 < dfRightBorderX && dfX2 > dfLeftBorderX)
9✔
2782
            {
2783
                std::swap(dfX1, dfX2);
5✔
2784
                std::swap(dfY1, dfY2);
5✔
2785
                std::swap(dfZ1, dfZ2);
5✔
2786
            }
2787
            if (dfX1 > dfLeftBorderX && dfX2 < dfRightBorderX)
9✔
2788
                dfX2 += 360;
9✔
2789

2790
            if (dfX1 <= 180 && dfX2 >= 180 && dfX1 < dfX2)
9✔
2791
            {
2792
                const double dfRatio = (180 - dfX1) / (dfX2 - dfX1);
9✔
2793
                const double dfY = dfRatio * dfY2 + (1 - dfRatio) * dfY1;
9✔
2794
                const double dfZ = dfRatio * dfZ2 + (1 - dfRatio) * dfZ1;
9✔
2795
                double dfNewX =
2796
                    poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? 180 : -180;
9✔
2797
                if (poNewLS->getNumPoints() == 0 ||
18✔
2798
                    poNewLS->getX(poNewLS->getNumPoints() - 1) != dfNewX ||
18✔
2799
                    poNewLS->getY(poNewLS->getNumPoints() - 1) != dfY)
2✔
2800
                {
2801
                    if (bIs3D)
7✔
2802
                        poNewLS->addPoint(dfNewX, dfY, dfZ);
×
2803
                    else
2804
                        poNewLS->addPoint(dfNewX, dfY);
7✔
2805
                }
2806
                poNewLS = new OGRLineString();
9✔
2807
                if (bIs3D)
9✔
2808
                    poNewLS->addPoint(
×
2809
                        poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? -180
×
2810
                                                                      : 180,
2811
                        dfY, dfZ);
2812
                else
2813
                    poNewLS->addPoint(
9✔
2814
                        poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? -180
9✔
2815
                                                                      : 180,
2816
                        dfY);
2817
                poMulti->addGeometryDirectly(poNewLS);
9✔
2818
            }
2819
            else
2820
            {
2821
                poNewLS = new OGRLineString();
×
2822
                poMulti->addGeometryDirectly(poNewLS);
×
2823
            }
2824
        }
2825
        if (bIs3D)
27✔
2826
            poNewLS->addPoint(dfX, poLS->getY(i), poLS->getZ(i));
×
2827
        else
2828
            poNewLS->addPoint(dfX, poLS->getY(i));
27✔
2829
    }
2830
}
8✔
2831

2832
/************************************************************************/
2833
/*               FixPolygonCoordinatesAtDateLine()                      */
2834
/************************************************************************/
2835

2836
#ifdef HAVE_GEOS
2837
static void FixPolygonCoordinatesAtDateLine(OGRPolygon *poPoly,
5✔
2838
                                            double dfDateLineOffset)
2839
{
2840
    const double dfLeftBorderX = 180 - dfDateLineOffset;
5✔
2841
    const double dfRightBorderX = -180 + dfDateLineOffset;
5✔
2842
    const double dfDiffSpace = 360 - dfDateLineOffset;
5✔
2843

2844
    for (int iPart = 0; iPart < 1 + poPoly->getNumInteriorRings(); iPart++)
10✔
2845
    {
2846
        OGRLineString *poLS = (iPart == 0) ? poPoly->getExteriorRing()
5✔
2847
                                           : poPoly->getInteriorRing(iPart - 1);
5✔
2848
        bool bGoEast = false;
5✔
2849
        const bool bIs3D = poLS->getCoordinateDimension() == 3;
5✔
2850
        for (int i = 1; i < poLS->getNumPoints(); i++)
41✔
2851
        {
2852
            double dfX = poLS->getX(i);
36✔
2853
            const double dfPrevX = poLS->getX(i - 1);
36✔
2854
            const double dfDiffLong = fabs(dfX - dfPrevX);
36✔
2855
            if (dfDiffLong > dfDiffSpace)
36✔
2856
            {
2857
                if ((dfPrevX > dfLeftBorderX && dfX < dfRightBorderX) ||
21✔
2858
                    (dfX < 0 && bGoEast))
6✔
2859
                {
2860
                    dfX += 360;
18✔
2861
                    bGoEast = true;
18✔
2862
                    if (bIs3D)
18✔
2863
                        poLS->setPoint(i, dfX, poLS->getY(i), poLS->getZ(i));
×
2864
                    else
2865
                        poLS->setPoint(i, dfX, poLS->getY(i));
18✔
2866
                }
2867
                else if (dfPrevX < dfRightBorderX && dfX > dfLeftBorderX)
3✔
2868
                {
2869
                    for (int j = i - 1; j >= 0; j--)
10✔
2870
                    {
2871
                        dfX = poLS->getX(j);
7✔
2872
                        if (dfX < 0)
7✔
2873
                        {
2874
                            if (bIs3D)
7✔
2875
                                poLS->setPoint(j, dfX + 360, poLS->getY(j),
×
2876
                                               poLS->getZ(j));
2877
                            else
2878
                                poLS->setPoint(j, dfX + 360, poLS->getY(j));
7✔
2879
                        }
2880
                    }
2881
                    bGoEast = false;
3✔
2882
                }
2883
                else
2884
                {
2885
                    bGoEast = false;
×
2886
                }
2887
            }
2888
        }
2889
    }
2890
}
5✔
2891
#endif
2892

2893
/************************************************************************/
2894
/*                            AddOffsetToLon()                          */
2895
/************************************************************************/
2896

2897
static void AddOffsetToLon(OGRGeometry *poGeom, double dfOffset)
17✔
2898
{
2899
    switch (wkbFlatten(poGeom->getGeometryType()))
17✔
2900
    {
2901
        case wkbPolygon:
7✔
2902
        {
2903
            for (auto poSubGeom : *(poGeom->toPolygon()))
14✔
2904
            {
2905
                AddOffsetToLon(poSubGeom, dfOffset);
7✔
2906
            }
2907

2908
            break;
7✔
2909
        }
2910

2911
        case wkbMultiLineString:
×
2912
        case wkbMultiPolygon:
2913
        case wkbGeometryCollection:
2914
        {
2915
            for (auto poSubGeom : *(poGeom->toGeometryCollection()))
×
2916
            {
2917
                AddOffsetToLon(poSubGeom, dfOffset);
×
2918
            }
2919

2920
            break;
×
2921
        }
2922

2923
        case wkbLineString:
10✔
2924
        {
2925
            OGRLineString *poLineString = poGeom->toLineString();
10✔
2926
            const int nPointCount = poLineString->getNumPoints();
10✔
2927
            const int nCoordDim = poLineString->getCoordinateDimension();
10✔
2928
            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
63✔
2929
            {
2930
                if (nCoordDim == 2)
53✔
2931
                    poLineString->setPoint(
106✔
2932
                        iPoint, poLineString->getX(iPoint) + dfOffset,
53✔
2933
                        poLineString->getY(iPoint));
2934
                else
2935
                    poLineString->setPoint(
×
2936
                        iPoint, poLineString->getX(iPoint) + dfOffset,
×
2937
                        poLineString->getY(iPoint), poLineString->getZ(iPoint));
2938
            }
2939
            break;
10✔
2940
        }
2941

2942
        default:
×
2943
            break;
×
2944
    }
2945
}
17✔
2946

2947
/************************************************************************/
2948
/*                        AddSimpleGeomToMulti()                        */
2949
/************************************************************************/
2950

2951
#ifdef HAVE_GEOS
2952
static void AddSimpleGeomToMulti(OGRGeometryCollection *poMulti,
12✔
2953
                                 const OGRGeometry *poGeom)
2954
{
2955
    switch (wkbFlatten(poGeom->getGeometryType()))
12✔
2956
    {
2957
        case wkbPolygon:
12✔
2958
        case wkbLineString:
2959
            poMulti->addGeometry(poGeom);
12✔
2960
            break;
12✔
2961

2962
        case wkbMultiLineString:
×
2963
        case wkbMultiPolygon:
2964
        case wkbGeometryCollection:
2965
        {
2966
            for (const auto poSubGeom : *(poGeom->toGeometryCollection()))
×
2967
            {
2968
                AddSimpleGeomToMulti(poMulti, poSubGeom);
×
2969
            }
2970
            break;
×
2971
        }
2972

2973
        default:
×
2974
            break;
×
2975
    }
2976
}
12✔
2977
#endif  // #ifdef HAVE_GEOS
2978

2979
/************************************************************************/
2980
/*                       WrapPointDateLine()                            */
2981
/************************************************************************/
2982

2983
static void WrapPointDateLine(OGRPoint *poPoint)
14✔
2984
{
2985
    if (poPoint->getX() > 180)
14✔
2986
    {
2987
        poPoint->setX(fmod(poPoint->getX() + 180, 360) - 180);
2✔
2988
    }
2989
    else if (poPoint->getX() < -180)
12✔
2990
    {
2991
        poPoint->setX(-(fmod(-poPoint->getX() + 180, 360) - 180));
3✔
2992
    }
2993
}
14✔
2994

2995
/************************************************************************/
2996
/*                 CutGeometryOnDateLineAndAddToMulti()                 */
2997
/************************************************************************/
2998

2999
static void CutGeometryOnDateLineAndAddToMulti(OGRGeometryCollection *poMulti,
69✔
3000
                                               const OGRGeometry *poGeom,
3001
                                               double dfDateLineOffset)
3002
{
3003
    const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
69✔
3004
    switch (eGeomType)
69✔
3005
    {
3006
        case wkbPoint:
1✔
3007
        {
3008
            auto poPoint = poGeom->toPoint()->clone();
1✔
3009
            WrapPointDateLine(poPoint);
1✔
3010
            poMulti->addGeometryDirectly(poPoint);
1✔
3011
            break;
1✔
3012
        }
3013

3014
        case wkbPolygon:
54✔
3015
        case wkbLineString:
3016
        {
3017
            bool bSplitLineStringAtDateline = false;
54✔
3018
            OGREnvelope oEnvelope;
54✔
3019

3020
            poGeom->getEnvelope(&oEnvelope);
54✔
3021
            const bool bAroundMinus180 = (oEnvelope.MinX < -180.0);
54✔
3022

3023
            // Naive heuristics... Place to improve.
3024
#ifdef HAVE_GEOS
3025
            std::unique_ptr<OGRGeometry> poDupGeom;
54✔
3026
            bool bWrapDateline = false;
54✔
3027
#endif
3028

3029
            const double dfLeftBorderX = 180 - dfDateLineOffset;
54✔
3030
            const double dfRightBorderX = -180 + dfDateLineOffset;
54✔
3031
            const double dfDiffSpace = 360 - dfDateLineOffset;
54✔
3032

3033
            const double dfXOffset = (bAroundMinus180) ? 360.0 : 0.0;
54✔
3034
            if (oEnvelope.MinX < -180 || oEnvelope.MaxX > 180 ||
54✔
3035
                (oEnvelope.MinX + dfXOffset > dfLeftBorderX &&
52✔
3036
                 oEnvelope.MaxX + dfXOffset > 180))
12✔
3037
            {
3038
#ifndef HAVE_GEOS
3039
                CPLError(CE_Failure, CPLE_NotSupported,
3040
                         "GEOS support not enabled.");
3041
#else
3042
                bWrapDateline = true;
2✔
3043
#endif
3044
            }
3045
            else
3046
            {
3047
                auto poLS = eGeomType == wkbPolygon
3048
                                ? poGeom->toPolygon()->getExteriorRing()
52✔
3049
                                : poGeom->toLineString();
14✔
3050
                if (poLS)
52✔
3051
                {
3052
                    double dfMaxSmallDiffLong = 0;
52✔
3053
                    bool bHasBigDiff = false;
52✔
3054
                    // Detect big gaps in longitude.
3055
                    for (int i = 1; i < poLS->getNumPoints(); i++)
294✔
3056
                    {
3057
                        const double dfPrevX = poLS->getX(i - 1) + dfXOffset;
242✔
3058
                        const double dfX = poLS->getX(i) + dfXOffset;
242✔
3059
                        const double dfDiffLong = fabs(dfX - dfPrevX);
242✔
3060

3061
                        if (dfDiffLong > dfDiffSpace &&
242✔
3062
                            ((dfX > dfLeftBorderX &&
11✔
3063
                              dfPrevX < dfRightBorderX) ||
10✔
3064
                             (dfPrevX > dfLeftBorderX && dfX < dfRightBorderX)))
10✔
3065
                            bHasBigDiff = true;
21✔
3066
                        else if (dfDiffLong > dfMaxSmallDiffLong)
221✔
3067
                            dfMaxSmallDiffLong = dfDiffLong;
55✔
3068
                    }
3069
                    if (bHasBigDiff && dfMaxSmallDiffLong < dfDateLineOffset)
52✔
3070
                    {
3071
                        if (eGeomType == wkbLineString)
13✔
3072
                            bSplitLineStringAtDateline = true;
8✔
3073
                        else
3074
                        {
3075
#ifndef HAVE_GEOS
3076
                            CPLError(CE_Failure, CPLE_NotSupported,
3077
                                     "GEOS support not enabled.");
3078
#else
3079
                            poDupGeom.reset(poGeom->clone());
5✔
3080
                            FixPolygonCoordinatesAtDateLine(
5✔
3081
                                poDupGeom->toPolygon(), dfDateLineOffset);
3082

3083
                            OGREnvelope sEnvelope;
5✔
3084
                            poDupGeom->getEnvelope(&sEnvelope);
5✔
3085
                            bWrapDateline = sEnvelope.MinX != sEnvelope.MaxX;
5✔
3086
#endif
3087
                        }
3088
                    }
3089
                }
3090
            }
3091

3092
            if (bSplitLineStringAtDateline)
54✔
3093
            {
3094
                SplitLineStringAtDateline(poMulti, poGeom->toLineString(),
8✔
3095
                                          dfDateLineOffset,
3096
                                          (bAroundMinus180) ? 360.0 : 0.0);
3097
            }
3098
#ifdef HAVE_GEOS
3099
            else if (bWrapDateline)
46✔
3100
            {
3101
                const OGRGeometry *poWorkGeom =
3102
                    poDupGeom ? poDupGeom.get() : poGeom;
6✔
3103
                OGRGeometry *poRectangle1 = nullptr;
6✔
3104
                OGRGeometry *poRectangle2 = nullptr;
6✔
3105
                const char *pszWKT1 =
6✔
3106
                    !bAroundMinus180
3107
                        ? "POLYGON((-180 90,180 90,180 -90,-180 -90,-180 90))"
6✔
3108
                        : "POLYGON((180 90,-180 90,-180 -90,180 -90,180 90))";
3109
                const char *pszWKT2 =
6✔
3110
                    !bAroundMinus180
3111
                        ? "POLYGON((180 90,360 90,360 -90,180 -90,180 90))"
6✔
3112
                        : "POLYGON((-180 90,-360 90,-360 -90,-180 -90,-180 "
3113
                          "90))";
3114
                OGRGeometryFactory::createFromWkt(pszWKT1, nullptr,
6✔
3115
                                                  &poRectangle1);
3116
                OGRGeometryFactory::createFromWkt(pszWKT2, nullptr,
6✔
3117
                                                  &poRectangle2);
3118
                auto poGeom1 = std::unique_ptr<OGRGeometry>(
3119
                    poWorkGeom->Intersection(poRectangle1));
12✔
3120
                auto poGeom2 = std::unique_ptr<OGRGeometry>(
3121
                    poWorkGeom->Intersection(poRectangle2));
12✔
3122
                delete poRectangle1;
6✔
3123
                delete poRectangle2;
6✔
3124

3125
                if (poGeom1 != nullptr && poGeom2 != nullptr)
6✔
3126
                {
3127
                    AddSimpleGeomToMulti(poMulti, poGeom1.get());
6✔
3128
                    AddOffsetToLon(poGeom2.get(),
6✔
3129
                                   !bAroundMinus180 ? -360.0 : 360.0);
3130
                    AddSimpleGeomToMulti(poMulti, poGeom2.get());
6✔
3131
                }
3132
                else
3133
                {
3134
                    AddSimpleGeomToMulti(poMulti, poGeom);
×
3135
                }
3136
            }
3137
#endif
3138
            else
3139
            {
3140
                poMulti->addGeometry(poGeom);
40✔
3141
            }
3142
            break;
54✔
3143
        }
3144

3145
        case wkbMultiLineString:
14✔
3146
        case wkbMultiPolygon:
3147
        case wkbGeometryCollection:
3148
        {
3149
            for (const auto poSubGeom : *(poGeom->toGeometryCollection()))
45✔
3150
            {
3151
                CutGeometryOnDateLineAndAddToMulti(poMulti, poSubGeom,
31✔
3152
                                                   dfDateLineOffset);
3153
            }
3154
            break;
14✔
3155
        }
3156

3157
        default:
×
3158
            break;
×
3159
    }
3160
}
69✔
3161

3162
#ifdef HAVE_GEOS
3163

3164
/************************************************************************/
3165
/*                             RemovePoint()                            */
3166
/************************************************************************/
3167

3168
static void RemovePoint(OGRGeometry *poGeom, OGRPoint *poPoint)
9✔
3169
{
3170
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
9✔
3171
    switch (eType)
9✔
3172
    {
3173
        case wkbLineString:
4✔
3174
        {
3175
            OGRLineString *poLS = poGeom->toLineString();
4✔
3176
            const bool bIs3D = (poLS->getCoordinateDimension() == 3);
4✔
3177
            int j = 0;
4✔
3178
            for (int i = 0; i < poLS->getNumPoints(); i++)
32✔
3179
            {
3180
                if (poLS->getX(i) != poPoint->getX() ||
30✔
3181
                    poLS->getY(i) != poPoint->getY())
2✔
3182
                {
3183
                    if (i > j)
26✔
3184
                    {
3185
                        if (bIs3D)
4✔
3186
                        {
3187
                            poLS->setPoint(j, poLS->getX(i), poLS->getY(i),
×
3188
                                           poLS->getZ(i));
3189
                        }
3190
                        else
3191
                        {
3192
                            poLS->setPoint(j, poLS->getX(i), poLS->getY(i));
4✔
3193
                        }
3194
                    }
3195
                    j++;
26✔
3196
                }
3197
            }
3198
            poLS->setNumPoints(j);
4✔
3199
            break;
4✔
3200
        }
3201

3202
        case wkbPolygon:
4✔
3203
        {
3204
            OGRPolygon *poPoly = poGeom->toPolygon();
4✔
3205
            if (poPoly->getExteriorRing() != nullptr)
4✔
3206
            {
3207
                RemovePoint(poPoly->getExteriorRing(), poPoint);
4✔
3208
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
4✔
3209
                {
3210
                    RemovePoint(poPoly->getInteriorRing(i), poPoint);
×
3211
                }
3212
            }
3213
            break;
4✔
3214
        }
3215

3216
        case wkbMultiLineString:
1✔
3217
        case wkbMultiPolygon:
3218
        case wkbGeometryCollection:
3219
        {
3220
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
1✔
3221
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
3✔
3222
            {
3223
                RemovePoint(poGC->getGeometryRef(i), poPoint);
2✔
3224
            }
3225
            break;
1✔
3226
        }
3227

3228
        default:
×
3229
            break;
×
3230
    }
3231
}
9✔
3232

3233
/************************************************************************/
3234
/*                              GetDist()                               */
3235
/************************************************************************/
3236

3237
static double GetDist(double dfDeltaX, double dfDeltaY)
51✔
3238
{
3239
    return sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY);
51✔
3240
}
3241

3242
/************************************************************************/
3243
/*                             AlterPole()                              */
3244
/*                                                                      */
3245
/* Replace and point at the pole by points really close to the pole,    */
3246
/* but on the previous and later segments.                              */
3247
/************************************************************************/
3248

3249
static void AlterPole(OGRGeometry *poGeom, OGRPoint *poPole,
5✔
3250
                      bool bIsRing = false)
3251
{
3252
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
5✔
3253
    switch (eType)
5✔
3254
    {
3255
        case wkbLineString:
2✔
3256
        {
3257
            if (!bIsRing)
2✔
3258
                return;
×
3259
            OGRLineString *poLS = poGeom->toLineString();
2✔
3260
            const int nNumPoints = poLS->getNumPoints();
2✔
3261
            if (nNumPoints >= 4)
2✔
3262
            {
3263
                const bool bIs3D = (poLS->getCoordinateDimension() == 3);
2✔
3264
                std::vector<OGRRawPoint> aoPoints;
4✔
3265
                std::vector<double> adfZ;
4✔
3266
                bool bMustClose = false;
2✔
3267
                for (int i = 0; i < nNumPoints; i++)
10✔
3268
                {
3269
                    const double dfX = poLS->getX(i);
8✔
3270
                    const double dfY = poLS->getY(i);
8✔
3271
                    if (dfX == poPole->getX() && dfY == poPole->getY())
8✔
3272
                    {
3273
                        // Replace the pole by points really close to it
3274
                        if (i == 0)
2✔
3275
                            bMustClose = true;
×
3276
                        if (i == nNumPoints - 1)
2✔
3277
                            continue;
×
3278
                        const int iBefore = i > 0 ? i - 1 : nNumPoints - 2;
2✔
3279
                        double dfXBefore = poLS->getX(iBefore);
2✔
3280
                        double dfYBefore = poLS->getY(iBefore);
2✔
3281
                        double dfNorm =
3282
                            GetDist(dfXBefore - dfX, dfYBefore - dfY);
2✔
3283
                        double dfXInterp =
2✔
3284
                            dfX + (dfXBefore - dfX) / dfNorm * 1.0e-7;
2✔
3285
                        double dfYInterp =
2✔
3286
                            dfY + (dfYBefore - dfY) / dfNorm * 1.0e-7;
2✔
3287
                        OGRRawPoint oPoint;
2✔
3288
                        oPoint.x = dfXInterp;
2✔
3289
                        oPoint.y = dfYInterp;
2✔
3290
                        aoPoints.push_back(oPoint);
2✔
3291
                        adfZ.push_back(poLS->getZ(i));
2✔
3292

3293
                        const int iAfter = i + 1;
2✔
3294
                        double dfXAfter = poLS->getX(iAfter);
2✔
3295
                        double dfYAfter = poLS->getY(iAfter);
2✔
3296
                        dfNorm = GetDist(dfXAfter - dfX, dfYAfter - dfY);
2✔
3297
                        dfXInterp = dfX + (dfXAfter - dfX) / dfNorm * 1e-7;
2✔
3298
                        dfYInterp = dfY + (dfYAfter - dfY) / dfNorm * 1e-7;
2✔
3299
                        oPoint.x = dfXInterp;
2✔
3300
                        oPoint.y = dfYInterp;
2✔
3301
                        aoPoints.push_back(oPoint);
2✔
3302
                        adfZ.push_back(poLS->getZ(i));
2✔
3303
                    }
3304
                    else
3305
                    {
3306
                        OGRRawPoint oPoint;
6✔
3307
                        oPoint.x = dfX;
6✔
3308
                        oPoint.y = dfY;
6✔
3309
                        aoPoints.push_back(oPoint);
6✔
3310
                        adfZ.push_back(poLS->getZ(i));
6✔
3311
                    }
3312
                }
3313
                if (bMustClose)
2✔
3314
                {
3315
                    aoPoints.push_back(aoPoints[0]);
×
3316
                    adfZ.push_back(adfZ[0]);
×
3317
                }
3318

3319
                poLS->setPoints(static_cast<int>(aoPoints.size()),
4✔
3320
                                &(aoPoints[0]), bIs3D ? &adfZ[0] : nullptr);
2✔
3321
            }
3322
            break;
2✔
3323
        }
3324

3325
        case wkbPolygon:
2✔
3326
        {
3327
            OGRPolygon *poPoly = poGeom->toPolygon();
2✔
3328
            if (poPoly->getExteriorRing() != nullptr)
2✔
3329
            {
3330
                AlterPole(poPoly->getExteriorRing(), poPole, true);
2✔
3331
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
2✔
3332
                {
3333
                    AlterPole(poPoly->getInteriorRing(i), poPole, true);
×
3334
                }
3335
            }
3336
            break;
2✔
3337
        }
3338

3339
        case wkbMultiLineString:
1✔
3340
        case wkbMultiPolygon:
3341
        case wkbGeometryCollection:
3342
        {
3343
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
1✔
3344
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
2✔
3345
            {
3346
                AlterPole(poGC->getGeometryRef(i), poPole);
1✔
3347
            }
3348
            break;
1✔
3349
        }
3350

3351
        default:
×
3352
            break;
×
3353
    }
3354
}
3355

3356
/************************************************************************/
3357
/*                        IsPolarToGeographic()                         */
3358
/*                                                                      */
3359
/* Returns true if poCT transforms from a projection that includes one  */
3360
/* of the pole in a continuous way.                                     */
3361
/************************************************************************/
3362

3363
static bool IsPolarToGeographic(OGRCoordinateTransformation *poCT,
20✔
3364
                                OGRCoordinateTransformation *poRevCT,
3365
                                bool &bIsNorthPolarOut)
3366
{
3367
    bool bIsNorthPolar = false;
20✔
3368
    bool bIsSouthPolar = false;
20✔
3369
    double x = 0.0;
20✔
3370
    double y = 90.0;
20✔
3371

3372
    const bool bBackupEmitErrors = poCT->GetEmitErrors();
20✔
3373
    poRevCT->SetEmitErrors(false);
20✔
3374
    poCT->SetEmitErrors(false);
20✔
3375

3376
    if (poRevCT->Transform(1, &x, &y) &&
20✔
3377
        // Surprisingly, pole south projects correctly back &
3378
        // forth for antarctic polar stereographic.  Therefore, check that
3379
        // the projected value is not too big.
3380
        fabs(x) < 1e10 && fabs(y) < 1e10)
20✔
3381
    {
3382
        double x_tab[] = {x, x - 1e5, x + 1e5};
18✔
3383
        double y_tab[] = {y, y - 1e5, y + 1e5};
18✔
3384
        if (poCT->Transform(3, x_tab, y_tab) &&
18✔
3385
            fabs(y_tab[0] - (90.0)) < 1e-10 &&
18✔
3386
            fabs(x_tab[2] - x_tab[1]) > 170 &&
53✔
3387
            fabs(y_tab[2] - y_tab[1]) < 1e-10)
17✔
3388
        {
3389
            bIsNorthPolar = true;
17✔
3390
        }
3391
    }
3392

3393
    x = 0.0;
20✔
3394
    y = -90.0;
20✔
3395
    if (poRevCT->Transform(1, &x, &y) && fabs(x) < 1e10 && fabs(y) < 1e10)
20✔
3396
    {
3397
        double x_tab[] = {x, x - 1e5, x + 1e5};
15✔
3398
        double y_tab[] = {y, y - 1e5, y + 1e5};
15✔
3399
        if (poCT->Transform(3, x_tab, y_tab) &&
15✔
3400
            fabs(y_tab[0] - (-90.0)) < 1e-10 &&
15✔
3401
            fabs(x_tab[2] - x_tab[1]) > 170 &&
44✔
3402
            fabs(y_tab[2] - y_tab[1]) < 1e-10)
14✔
3403
        {
3404
            bIsSouthPolar = true;
14✔
3405
        }
3406
    }
3407

3408
    poCT->SetEmitErrors(bBackupEmitErrors);
20✔
3409

3410
    if (bIsNorthPolar && bIsSouthPolar)
20✔
3411
    {
3412
        bIsNorthPolar = false;
13✔
3413
        bIsSouthPolar = false;
13✔
3414
    }
3415

3416
    bIsNorthPolarOut = bIsNorthPolar;
20✔
3417
    return bIsNorthPolar || bIsSouthPolar;
20✔
3418
}
3419

3420
/************************************************************************/
3421
/*                 TransformBeforePolarToGeographic()                   */
3422
/*                                                                      */
3423
/* Transform the geometry (by intersection), so as to cut each geometry */
3424
/* that crosses the pole, in 2 parts. Do also tricks for geometries     */
3425
/* that just touch the pole.                                            */
3426
/************************************************************************/
3427

3428
static std::unique_ptr<OGRGeometry> TransformBeforePolarToGeographic(
6✔
3429
    OGRCoordinateTransformation *poRevCT, bool bIsNorthPolar,
3430
    std::unique_ptr<OGRGeometry> poDstGeom, bool &bNeedPostCorrectionOut)
3431
{
3432
    const int nSign = (bIsNorthPolar) ? 1 : -1;
6✔
3433

3434
    // Does the geometry fully contains the pole ? */
3435
    double dfXPole = 0.0;
6✔
3436
    double dfYPole = nSign * 90.0;
6✔
3437
    poRevCT->Transform(1, &dfXPole, &dfYPole);
6✔
3438
    OGRPoint oPole(dfXPole, dfYPole);
12✔
3439
    const bool bContainsPole = CPL_TO_BOOL(poDstGeom->Contains(&oPole));
6✔
3440

3441
    const double EPS = 1e-9;
6✔
3442

3443
    // Does the geometry touches the pole and intersects the antimeridian ?
3444
    double dfNearPoleAntiMeridianX = 180.0;
6✔
3445
    double dfNearPoleAntiMeridianY = nSign * (90.0 - EPS);
6✔
3446
    poRevCT->Transform(1, &dfNearPoleAntiMeridianX, &dfNearPoleAntiMeridianY);
6✔
3447
    OGRPoint oNearPoleAntimeridian(dfNearPoleAntiMeridianX,
3448
                                   dfNearPoleAntiMeridianY);
12✔
3449
    const bool bContainsNearPoleAntimeridian =
3450
        CPL_TO_BOOL(poDstGeom->Contains(&oNearPoleAntimeridian));
6✔
3451

3452
    // Does the geometry touches the pole (but not intersect the antimeridian) ?
3453
    const bool bRegularTouchesPole = !bContainsPole &&
10✔
3454
                                     !bContainsNearPoleAntimeridian &&
9✔
3455
                                     CPL_TO_BOOL(poDstGeom->Touches(&oPole));
3✔
3456

3457
    // Create a polygon of nearly a full hemisphere, but excluding the anti
3458
    // meridian and the pole.
3459
    OGRPolygon oCutter;
12✔
3460
    OGRLinearRing *poRing = new OGRLinearRing();
6✔
3461
    poRing->addPoint(180.0 - EPS, 0);
6✔
3462
    poRing->addPoint(180.0 - EPS, nSign * (90.0 - EPS));
6✔
3463
    // If the geometry doesn't contain the pole, then we add it to the cutter
3464
    // geometry, but will later remove it completely (geometry touching the
3465
    // pole but intersecting the antimeridian), or will replace it by 2
3466
    // close points (geometry touching the pole without intersecting the
3467
    // antimeridian)
3468
    if (!bContainsPole)
6✔
3469
        poRing->addPoint(180.0, nSign * 90);
4✔
3470
    poRing->addPoint(-180.0 + EPS, nSign * (90.0 - EPS));
6✔
3471
    poRing->addPoint(-180.0 + EPS, 0);
6✔
3472
    poRing->addPoint(180.0 - EPS, 0);
6✔
3473
    oCutter.addRingDirectly(poRing);
6✔
3474

3475
    if (oCutter.transform(poRevCT) == OGRERR_NONE &&
6✔
3476
        // Check that longitudes +/- 180 are continuous
3477
        // in the polar projection
3478
        fabs(poRing->getX(0) - poRing->getX(poRing->getNumPoints() - 2)) < 1 &&
10✔
3479
        (bContainsPole || bContainsNearPoleAntimeridian || bRegularTouchesPole))
4✔
3480
    {
3481
        if (bContainsPole || bContainsNearPoleAntimeridian)
5✔
3482
        {
3483
            auto poNewGeom =
3484
                std::unique_ptr<OGRGeometry>(poDstGeom->Difference(&oCutter));
6✔
3485
            if (poNewGeom)
3✔
3486
            {
3487
                if (bContainsNearPoleAntimeridian)
3✔
3488
                    RemovePoint(poNewGeom.get(), &oPole);
3✔
3489
                poDstGeom = std::move(poNewGeom);
3✔
3490
            }
3491
        }
3492

3493
        if (bRegularTouchesPole)
5✔
3494
        {
3495
            AlterPole(poDstGeom.get(), &oPole);
2✔
3496
        }
3497

3498
        bNeedPostCorrectionOut = true;
5✔
3499
    }
3500
    return poDstGeom;
12✔
3501
}
3502

3503
/************************************************************************/
3504
/*                   IsAntimeridianProjToGeographic()                   */
3505
/*                                                                      */
3506
/* Returns true if poCT transforms from a projection that includes the  */
3507
/* antimeridian in a continuous way.                                    */
3508
/************************************************************************/
3509

3510
static bool IsAntimeridianProjToGeographic(OGRCoordinateTransformation *poCT,
17✔
3511
                                           OGRCoordinateTransformation *poRevCT,
3512
                                           OGRGeometry *poDstGeometry)
3513
{
3514
    const bool bBackupEmitErrors = poCT->GetEmitErrors();
17✔
3515
    poRevCT->SetEmitErrors(false);
17✔
3516
    poCT->SetEmitErrors(false);
17✔
3517

3518
    // Find a reasonable latitude for the geometry
3519
    OGREnvelope sEnvelope;
17✔
3520
    poDstGeometry->getEnvelope(&sEnvelope);
17✔
3521
    OGRPoint pMean(sEnvelope.MinX, (sEnvelope.MinY + sEnvelope.MaxY) / 2);
34✔
3522
    if (pMean.transform(poCT) != OGRERR_NONE)
17✔
3523
    {
3524
        poCT->SetEmitErrors(bBackupEmitErrors);
×
3525
        return false;
×
3526
    }
3527
    const double dfMeanLat = pMean.getY();
17✔
3528

3529
    // Check that close points on each side of the antimeridian in (long, lat)
3530
    // project to close points in the source projection, and check that they
3531
    // roundtrip correctly.
3532
    const double EPS = 1.0e-8;
17✔
3533
    double x1 = 180 - EPS;
17✔
3534
    double y1 = dfMeanLat;
17✔
3535
    double x2 = -180 + EPS;
17✔
3536
    double y2 = dfMeanLat;
17✔
3537
    if (!poRevCT->Transform(1, &x1, &y1) || !poRevCT->Transform(1, &x2, &y2) ||
51✔
3538
        GetDist(x2 - x1, y2 - y1) > 1 || !poCT->Transform(1, &x1, &y1) ||
32✔
3539
        !poCT->Transform(1, &x2, &y2) ||
30✔
3540
        GetDist(x1 - (180 - EPS), y1 - dfMeanLat) > 2 * EPS ||
49✔
3541
        GetDist(x2 - (-180 + EPS), y2 - dfMeanLat) > 2 * EPS)
15✔
3542
    {
3543
        poCT->SetEmitErrors(bBackupEmitErrors);
2✔
3544
        return false;
2✔
3545
    }
3546

3547
    poCT->SetEmitErrors(bBackupEmitErrors);
15✔
3548

3549
    return true;
15✔
3550
}
3551

3552
/************************************************************************/
3553
/*                      CollectPointsOnAntimeridian()                   */
3554
/*                                                                      */
3555
/* Collect points that are the intersection of the lines of the geometry*/
3556
/* with the antimeridian.                                               */
3557
/************************************************************************/
3558

3559
static void CollectPointsOnAntimeridian(OGRGeometry *poGeom,
21✔
3560
                                        OGRCoordinateTransformation *poCT,
3561
                                        OGRCoordinateTransformation *poRevCT,
3562
                                        std::vector<OGRRawPoint> &aoPoints)
3563
{
3564
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
21✔
3565
    switch (eType)
21✔
3566
    {
3567
        case wkbLineString:
11✔
3568
        {
3569
            OGRLineString *poLS = poGeom->toLineString();
11✔
3570
            const int nNumPoints = poLS->getNumPoints();
11✔
3571
            for (int i = 0; i < nNumPoints - 1; i++)
44✔
3572
            {
3573
                const double dfX = poLS->getX(i);
33✔
3574
                const double dfY = poLS->getY(i);
33✔
3575
                const double dfX2 = poLS->getX(i + 1);
33✔
3576
                const double dfY2 = poLS->getY(i + 1);
33✔
3577
                double dfXTrans = dfX;
33✔
3578
                double dfYTrans = dfY;
33✔
3579
                double dfX2Trans = dfX2;
33✔
3580
                double dfY2Trans = dfY2;
33✔
3581
                poCT->Transform(1, &dfXTrans, &dfYTrans);
33✔
3582
                poCT->Transform(1, &dfX2Trans, &dfY2Trans);
33✔
3583
                // Are we crossing the antimeridian ? (detecting by inversion of
3584
                // sign of X)
3585
                if ((dfX2 - dfX) * (dfX2Trans - dfXTrans) < 0 ||
33✔
3586
                    (dfX == dfX2 && dfX2Trans * dfXTrans < 0 &&
14✔
3587
                     fabs(fabs(dfXTrans) - 180) < 10 &&
1✔
3588
                     fabs(fabs(dfX2Trans) - 180) < 10))
1✔
3589
                {
3590
                    double dfXStart = dfX;
17✔
3591
                    double dfYStart = dfY;
17✔
3592
                    double dfXEnd = dfX2;
17✔
3593
                    double dfYEnd = dfY2;
17✔
3594
                    double dfXStartTrans = dfXTrans;
17✔
3595
                    double dfXEndTrans = dfX2Trans;
17✔
3596
                    int iIter = 0;
17✔
3597
                    const double EPS = 1e-8;
17✔
3598
                    // Find point of the segment intersecting the antimeridian
3599
                    // by dichotomy
3600
                    for (;
453✔
3601
                         iIter < 50 && (fabs(fabs(dfXStartTrans) - 180) > EPS ||
470✔
3602
                                        fabs(fabs(dfXEndTrans) - 180) > EPS);
25✔
3603
                         ++iIter)
3604
                    {
3605
                        double dfXMid = (dfXStart + dfXEnd) / 2;
453✔
3606
                        double dfYMid = (dfYStart + dfYEnd) / 2;
453✔
3607
                        double dfXMidTrans = dfXMid;
453✔
3608
                        double dfYMidTrans = dfYMid;
453✔
3609
                        poCT->Transform(1, &dfXMidTrans, &dfYMidTrans);
453✔
3610
                        if ((dfXMid - dfXStart) *
453✔
3611
                                    (dfXMidTrans - dfXStartTrans) <
453✔
3612
                                0 ||
247✔
3613
                            (dfXMid == dfXStart &&
22✔
3614
                             dfXMidTrans * dfXStartTrans < 0))
22✔
3615
                        {
3616
                            dfXEnd = dfXMid;
214✔
3617
                            dfYEnd = dfYMid;
214✔
3618
                            dfXEndTrans = dfXMidTrans;
214✔
3619
                        }
3620
                        else
3621
                        {
3622
                            dfXStart = dfXMid;
239✔
3623
                            dfYStart = dfYMid;
239✔
3624
                            dfXStartTrans = dfXMidTrans;
239✔
3625
                        }
3626
                    }
3627
                    if (iIter < 50)
17✔
3628
                    {
3629
                        OGRRawPoint oPoint;
17✔
3630
                        oPoint.x = (dfXStart + dfXEnd) / 2;
17✔
3631
                        oPoint.y = (dfYStart + dfYEnd) / 2;
17✔
3632
                        poCT->Transform(1, &(oPoint.x), &(oPoint.y));
17✔
3633
                        oPoint.x = 180.0;
17✔
3634
                        aoPoints.push_back(oPoint);
17✔
3635
                    }
3636
                }
3637
            }
3638
            break;
11✔
3639
        }
3640

3641
        case wkbPolygon:
6✔
3642
        {
3643
            OGRPolygon *poPoly = poGeom->toPolygon();
6✔
3644
            if (poPoly->getExteriorRing() != nullptr)
6✔
3645
            {
3646
                CollectPointsOnAntimeridian(poPoly->getExteriorRing(), poCT,
6✔
3647
                                            poRevCT, aoPoints);
3648
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
6✔
3649
                {
3650
                    CollectPointsOnAntimeridian(poPoly->getInteriorRing(i),
×
3651
                                                poCT, poRevCT, aoPoints);
3652
                }
3653
            }
3654
            break;
6✔
3655
        }
3656

3657
        case wkbMultiLineString:
4✔
3658
        case wkbMultiPolygon:
3659
        case wkbGeometryCollection:
3660
        {
3661
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
4✔
3662
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
8✔
3663
            {
3664
                CollectPointsOnAntimeridian(poGC->getGeometryRef(i), poCT,
4✔
3665
                                            poRevCT, aoPoints);
3666
            }
3667
            break;
4✔
3668
        }
3669

3670
        default:
×
3671
            break;
×
3672
    }
3673
}
21✔
3674

3675
/************************************************************************/
3676
/*                         SortPointsByAscendingY()                     */
3677
/************************************************************************/
3678

3679
struct SortPointsByAscendingY
3680
{
3681
    bool operator()(const OGRRawPoint &a, const OGRRawPoint &b)
8✔
3682
    {
3683
        return a.y < b.y;
8✔
3684
    }
3685
};
3686

3687
/************************************************************************/
3688
/*              TransformBeforeAntimeridianToGeographic()               */
3689
/*                                                                      */
3690
/* Transform the geometry (by intersection), so as to cut each geometry */
3691
/* that crosses the antimeridian, in 2 parts.                           */
3692
/************************************************************************/
3693

3694
static std::unique_ptr<OGRGeometry> TransformBeforeAntimeridianToGeographic(
15✔
3695
    OGRCoordinateTransformation *poCT, OGRCoordinateTransformation *poRevCT,
3696
    std::unique_ptr<OGRGeometry> poDstGeom, bool &bNeedPostCorrectionOut)
3697
{
3698
    OGREnvelope sEnvelope;
15✔
3699
    poDstGeom->getEnvelope(&sEnvelope);
15✔
3700
    OGRPoint pMean(sEnvelope.MinX, (sEnvelope.MinY + sEnvelope.MaxY) / 2);
30✔
3701
    pMean.transform(poCT);
15✔
3702
    const double dfMeanLat = pMean.getY();
15✔
3703
    pMean.setX(180.0);
15✔
3704
    pMean.setY(dfMeanLat);
15✔
3705
    pMean.transform(poRevCT);
15✔
3706
    // Check if the antimeridian crosses the bbox of our geometry
3707
    if (!(pMean.getX() >= sEnvelope.MinX && pMean.getY() >= sEnvelope.MinY &&
28✔
3708
          pMean.getX() <= sEnvelope.MaxX && pMean.getY() <= sEnvelope.MaxY))
13✔
3709
    {
3710
        return poDstGeom;
4✔
3711
    }
3712

3713
    // Collect points that are the intersection of the lines of the geometry
3714
    // with the antimeridian
3715
    std::vector<OGRRawPoint> aoPoints;
22✔
3716
    CollectPointsOnAntimeridian(poDstGeom.get(), poCT, poRevCT, aoPoints);
11✔
3717
    if (aoPoints.empty())
11✔
3718
        return poDstGeom;
×
3719

3720
    SortPointsByAscendingY sortFunc;
3721
    std::sort(aoPoints.begin(), aoPoints.end(), sortFunc);
11✔
3722

3723
    const double EPS = 1e-9;
11✔
3724

3725
    // Build a very thin polygon cutting the antimeridian at our points
3726
    OGRLinearRing *poLR = new OGRLinearRing;
11✔
3727
    {
3728
        double x = 180.0 - EPS;
11✔
3729
        double y = aoPoints[0].y - EPS;
11✔
3730
        poRevCT->Transform(1, &x, &y);
11✔
3731
        poLR->addPoint(x, y);
11✔
3732
    }
3733
    for (const auto &oPoint : aoPoints)
28✔
3734
    {
3735
        double x = 180.0 - EPS;
17✔
3736
        double y = oPoint.y;
17✔
3737
        poRevCT->Transform(1, &x, &y);
17✔
3738
        poLR->addPoint(x, y);
17✔
3739
    }
3740
    {
3741
        double x = 180.0 - EPS;
11✔
3742
        double y = aoPoints.back().y + EPS;
11✔
3743
        poRevCT->Transform(1, &x, &y);
11✔
3744
        poLR->addPoint(x, y);
11✔
3745
    }
3746
    {
3747
        double x = 180.0 + EPS;
11✔
3748
        double y = aoPoints.back().y + EPS;
11✔
3749
        poRevCT->Transform(1, &x, &y);
11✔
3750
        poLR->addPoint(x, y);
11✔
3751
    }
3752
    for (size_t i = aoPoints.size(); i > 0;)
28✔
3753
    {
3754
        --i;
17✔
3755
        const OGRRawPoint &oPoint = aoPoints[i];
17✔
3756
        double x = 180.0 + EPS;
17✔
3757
        double y = oPoint.y;
17✔
3758
        poRevCT->Transform(1, &x, &y);
17✔
3759
        poLR->addPoint(x, y);
17✔
3760
    }
3761
    {
3762
        double x = 180.0 + EPS;
11✔
3763
        double y = aoPoints[0].y - EPS;
11✔
3764
        poRevCT->Transform(1, &x, &y);
11✔
3765
        poLR->addPoint(x, y);
11✔
3766
    }
3767
    poLR->closeRings();
11✔
3768

3769
    OGRPolygon oPolyToCut;
22✔
3770
    oPolyToCut.addRingDirectly(poLR);
11✔
3771

3772
#if DEBUG_VERBOSE
3773
    char *pszWKT = NULL;
3774
    oPolyToCut.exportToWkt(&pszWKT);
3775
    CPLDebug("OGR", "Geometry to cut: %s", pszWKT);
3776
    CPLFree(pszWKT);
3777
#endif
3778

3779
    // Get the geometry without the antimeridian
3780
    auto poInter =
3781
        std::unique_ptr<OGRGeometry>(poDstGeom->Difference(&oPolyToCut));
22✔
3782
    if (poInter != nullptr)
11✔
3783
    {
3784
        poDstGeom = std::move(poInter);
11✔
3785
        bNeedPostCorrectionOut = true;
11✔
3786
    }
3787

3788
    return poDstGeom;
11✔
3789
}
3790

3791
/************************************************************************/
3792
/*                 SnapCoordsCloseToLatLongBounds()                     */
3793
/*                                                                      */
3794
/* This function snaps points really close to the antimerdian or poles  */
3795
/* to their exact longitudes/latitudes.                                 */
3796
/************************************************************************/
3797

3798
static void SnapCoordsCloseToLatLongBounds(OGRGeometry *poGeom)
59✔
3799
{
3800
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
59✔
3801
    switch (eType)
59✔
3802
    {
3803
        case wkbLineString:
28✔
3804
        {
3805
            OGRLineString *poLS = poGeom->toLineString();
28✔
3806
            const double EPS = 1e-8;
28✔
3807
            for (int i = 0; i < poLS->getNumPoints(); i++)
165✔
3808
            {
3809
                OGRPoint p;
274✔
3810
                poLS->getPoint(i, &p);
137✔
3811
                if (fabs(p.getX() - 180.0) < EPS)
137✔
3812
                {
3813
                    p.setX(180.0);
36✔
3814
                    poLS->setPoint(i, &p);
36✔
3815
                }
3816
                else if (fabs(p.getX() - -180.0) < EPS)
101✔
3817
                {
3818
                    p.setX(-180.0);
31✔
3819
                    poLS->setPoint(i, &p);
31✔
3820
                }
3821

3822
                if (fabs(p.getY() - 90.0) < EPS)
137✔
3823
                {
3824
                    p.setY(90.0);
8✔
3825
                    poLS->setPoint(i, &p);
8✔
3826
                }
3827
                else if (fabs(p.getY() - -90.0) < EPS)
129✔
3828
                {
3829
                    p.setY(-90.0);
2✔
3830
                    poLS->setPoint(i, &p);
2✔
3831
                }
3832
            }
3833
            break;
28✔
3834
        }
3835

3836
        case wkbPolygon:
18✔
3837
        {
3838
            OGRPolygon *poPoly = poGeom->toPolygon();
18✔
3839
            if (poPoly->getExteriorRing() != nullptr)
18✔
3840
            {
3841
                SnapCoordsCloseToLatLongBounds(poPoly->getExteriorRing());
18✔
3842
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
18✔
3843
                {
3844
                    SnapCoordsCloseToLatLongBounds(poPoly->getInteriorRing(i));
×
3845
                }
3846
            }
3847
            break;
18✔
3848
        }
3849

3850
        case wkbMultiLineString:
13✔
3851
        case wkbMultiPolygon:
3852
        case wkbGeometryCollection:
3853
        {
3854
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
13✔
3855
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
38✔
3856
            {
3857
                SnapCoordsCloseToLatLongBounds(poGC->getGeometryRef(i));
25✔
3858
            }
3859
            break;
13✔
3860
        }
3861

3862
        default:
×
3863
            break;
×
3864
    }
3865
}
59✔
3866

3867
#endif
3868

3869
/************************************************************************/
3870
/*                  TransformWithOptionsCache::Private                  */
3871
/************************************************************************/
3872

3873
struct OGRGeometryFactory::TransformWithOptionsCache::Private
3874
{
3875
    const OGRSpatialReference *poSourceCRS = nullptr;
3876
    const OGRSpatialReference *poTargetCRS = nullptr;
3877
    const OGRCoordinateTransformation *poCT = nullptr;
3878
    std::unique_ptr<OGRCoordinateTransformation> poRevCT{};
3879
    bool bIsPolar = false;
3880
    bool bIsNorthPolar = false;
3881

3882
    void clear()
48✔
3883
    {
3884
        poSourceCRS = nullptr;
48✔
3885
        poTargetCRS = nullptr;
48✔
3886
        poCT = nullptr;
48✔
3887
        poRevCT.reset();
48✔
3888
        bIsPolar = false;
48✔
3889
        bIsNorthPolar = false;
48✔
3890
    }
48✔
3891
};
3892

3893
/************************************************************************/
3894
/*                     TransformWithOptionsCache()                      */
3895
/************************************************************************/
3896

3897
OGRGeometryFactory::TransformWithOptionsCache::TransformWithOptionsCache()
1,114✔
3898
    : d(new Private())
1,114✔
3899
{
3900
}
1,114✔
3901

3902
/************************************************************************/
3903
/*                     ~TransformWithOptionsCache()                      */
3904
/************************************************************************/
3905

3906
OGRGeometryFactory::TransformWithOptionsCache::~TransformWithOptionsCache()
1,114✔
3907
{
3908
}
1,114✔
3909

3910
/************************************************************************/
3911
/*              isTransformWithOptionsRegularTransform()                */
3912
/************************************************************************/
3913

3914
//! @cond Doxygen_Suppress
3915
/*static */
3916
bool OGRGeometryFactory::isTransformWithOptionsRegularTransform(
61✔
3917
    [[maybe_unused]] const OGRSpatialReference *poSourceCRS,
3918
    [[maybe_unused]] const OGRSpatialReference *poTargetCRS,
3919
    CSLConstList papszOptions)
3920
{
3921
    if (papszOptions)
61✔
3922
        return false;
11✔
3923

3924
#ifdef HAVE_GEOS
3925
    if (poSourceCRS && poTargetCRS && poSourceCRS->IsProjected() &&
50✔
3926
        poTargetCRS->IsGeographic() &&
37✔
3927
        poTargetCRS->GetAxisMappingStrategy() == OAMS_TRADITIONAL_GIS_ORDER &&
131✔
3928
        // check that angular units is degree
3929
        std::fabs(poTargetCRS->GetAngularUnits(nullptr) -
31✔
3930
                  CPLAtof(SRS_UA_DEGREE_CONV)) <=
31✔
3931
            1e-8 * CPLAtof(SRS_UA_DEGREE_CONV))
31✔
3932
    {
3933
        double dfWestLong = 0.0;
31✔
3934
        double dfSouthLat = 0.0;
31✔
3935
        double dfEastLong = 0.0;
31✔
3936
        double dfNorthLat = 0.0;
31✔
3937
        if (poSourceCRS->GetAreaOfUse(&dfWestLong, &dfSouthLat, &dfEastLong,
31✔
3938
                                      &dfNorthLat, nullptr) &&
59✔
3939
            !(dfSouthLat == -90.0 || dfNorthLat == 90.0 ||
28✔
3940
              dfWestLong == -180.0 || dfEastLong == 180.0 ||
28✔
3941
              dfWestLong > dfEastLong))
21✔
3942
        {
3943
            // Not a global geographic CRS
3944
            return true;
21✔
3945
        }
3946
        return false;
10✔
3947
    }
3948
#endif
3949

3950
    return true;
19✔
3951
}
3952

3953
//! @endcond
3954

3955
/************************************************************************/
3956
/*                       transformWithOptions()                         */
3957
/************************************************************************/
3958

3959
/** Transform a geometry.
3960
 *
3961
 * This is an enhanced version of OGRGeometry::Transform().
3962
 *
3963
 * When reprojecting geometries from a Polar Stereographic projection or a
3964
 * projection naturally crossing the antimeridian (like UTM Zone 60) to a
3965
 * geographic CRS, it will cut geometries along the antimeridian. So a
3966
 * LineString might be returned as a MultiLineString.
3967
 *
3968
 * The WRAPDATELINE=YES option might be specified for circumstances to correct
3969
 * geometries that incorrectly go from a longitude on a side of the antimeridian
3970
 * to the other side, like a LINESTRING(-179 0,179 0) will be transformed to
3971
 * a MULTILINESTRING ((-179 0,-180 0),(180 0,179 0)). For that use case, hCT
3972
 * might be NULL.
3973
 *
3974
 * Supported options in papszOptions are:
3975
 * <ul>
3976
 * <li>WRAPDATELINE=YES</li>
3977
 * <li>DATELINEOFFSET=longitude_gap_in_degree. Defaults to 10.</li>
3978
 * </ul>
3979
 *
3980
 * This is the same as the C function OGR_GeomTransformer_Transform().
3981
 *
3982
 * @param poSrcGeom source geometry
3983
 * @param poCT coordinate transformation object, or NULL.
3984
 * @param papszOptions NULL terminated list of options, or NULL.
3985
 * @param cache Cache. May increase performance if persisted between invocations
3986
 * @return (new) transformed geometry.
3987
 */
3988
OGRGeometry *OGRGeometryFactory::transformWithOptions(
183✔
3989
    const OGRGeometry *poSrcGeom, OGRCoordinateTransformation *poCT,
3990
    char **papszOptions, CPL_UNUSED const TransformWithOptionsCache &cache)
3991
{
3992
    auto poDstGeom = std::unique_ptr<OGRGeometry>(poSrcGeom->clone());
366✔
3993
    if (poCT)
183✔
3994
    {
3995
#ifdef HAVE_GEOS
3996
        bool bNeedPostCorrection = false;
148✔
3997
        const auto poSourceCRS = poCT->GetSourceCS();
148✔
3998
        const auto poTargetCRS = poCT->GetTargetCS();
148✔
3999
        const auto eSrcGeomType = wkbFlatten(poSrcGeom->getGeometryType());
148✔
4000
        // Check if we are transforming from projected coordinates to
4001
        // geographic coordinates, with a chance that there might be polar or
4002
        // anti-meridian discontinuities. If so, create the inverse transform.
4003
        if (eSrcGeomType != wkbPoint && eSrcGeomType != wkbMultiPoint &&
276✔
4004
            (poSourceCRS != cache.d->poSourceCRS ||
128✔
4005
             poTargetCRS != cache.d->poTargetCRS || poCT != cache.d->poCT))
80✔
4006
        {
4007
            cache.d->clear();
48✔
4008
            cache.d->poSourceCRS = poSourceCRS;
48✔
4009
            cache.d->poTargetCRS = poTargetCRS;
48✔
4010
            cache.d->poCT = poCT;
48✔
4011
            if (poSourceCRS && poTargetCRS &&
96✔
4012
                !isTransformWithOptionsRegularTransform(
48✔
4013
                    poSourceCRS, poTargetCRS, papszOptions))
4014
            {
4015
                cache.d->poRevCT.reset(OGRCreateCoordinateTransformation(
20✔
4016
                    poTargetCRS, poSourceCRS));
4017
                cache.d->bIsNorthPolar = false;
20✔
4018
                cache.d->bIsPolar = false;
20✔
4019
                cache.d->poRevCT.reset(poCT->GetInverse());
20✔
4020
                if (cache.d->poRevCT &&
60✔
4021
                    IsPolarToGeographic(poCT, cache.d->poRevCT.get(),
20✔
4022
                                        cache.d->bIsNorthPolar))
40✔
4023
                {
4024
                    cache.d->bIsPolar = true;
5✔
4025
                }
4026
            }
4027
        }
4028

4029
        if (auto poRevCT = cache.d->poRevCT.get())
148✔
4030
        {
4031
            if (cache.d->bIsPolar)
23✔
4032
            {
4033
                poDstGeom = TransformBeforePolarToGeographic(
12✔
4034
                    poRevCT, cache.d->bIsNorthPolar, std::move(poDstGeom),
12✔
4035
                    bNeedPostCorrection);
6✔
4036
            }
4037
            else if (IsAntimeridianProjToGeographic(poCT, poRevCT,
17✔
4038
                                                    poDstGeom.get()))
4039
            {
4040
                poDstGeom = TransformBeforeAntimeridianToGeographic(
30✔
4041
                    poCT, poRevCT, std::move(poDstGeom), bNeedPostCorrection);
30✔
4042
            }
4043
        }
4044
#endif
4045
        OGRErr eErr = poDstGeom->transform(poCT);
148✔
4046
        if (eErr != OGRERR_NONE)
148✔
4047
        {
4048
            return nullptr;
×
4049
        }
4050
#ifdef HAVE_GEOS
4051
        if (bNeedPostCorrection)
148✔
4052
        {
4053
            SnapCoordsCloseToLatLongBounds(poDstGeom.get());
16✔
4054
        }
4055
#endif
4056
    }
4057

4058
    if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "WRAPDATELINE", "NO")))
183✔
4059
    {
4060
        if (poDstGeom->getSpatialReference() &&
101✔
4061
            !poDstGeom->getSpatialReference()->IsGeographic())
49✔
4062
        {
4063
            CPLErrorOnce(
×
4064
                CE_Warning, CPLE_AppDefined,
4065
                "WRAPDATELINE is without effect when reprojecting to a "
4066
                "non-geographic CRS");
4067
            return poDstGeom.release();
×
4068
        }
4069
        // TODO and we should probably also test that the axis order + data axis
4070
        // mapping is long-lat...
4071
        const OGRwkbGeometryType eType =
4072
            wkbFlatten(poDstGeom->getGeometryType());
52✔
4073
        if (eType == wkbPoint)
52✔
4074
        {
4075
            OGRPoint *poDstPoint = poDstGeom->toPoint();
9✔
4076
            WrapPointDateLine(poDstPoint);
9✔
4077
        }
4078
        else if (eType == wkbMultiPoint)
43✔
4079
        {
4080
            for (auto *poDstPoint : *(poDstGeom->toMultiPoint()))
5✔
4081
            {
4082
                WrapPointDateLine(poDstPoint);
4✔
4083
            }
4084
        }
4085
        else
4086
        {
4087
            OGREnvelope sEnvelope;
42✔
4088
            poDstGeom->getEnvelope(&sEnvelope);
42✔
4089
            if (sEnvelope.MinX >= -360.0 && sEnvelope.MaxX <= -180.0)
42✔
4090
                AddOffsetToLon(poDstGeom.get(), 360.0);
2✔
4091
            else if (sEnvelope.MinX >= 180.0 && sEnvelope.MaxX <= 360.0)
40✔
4092
                AddOffsetToLon(poDstGeom.get(), -360.0);
2✔
4093
            else
4094
            {
4095
                OGRwkbGeometryType eNewType;
4096
                if (eType == wkbPolygon || eType == wkbMultiPolygon)
38✔
4097
                    eNewType = wkbMultiPolygon;
27✔
4098
                else if (eType == wkbLineString || eType == wkbMultiLineString)
11✔
4099
                    eNewType = wkbMultiLineString;
10✔
4100
                else
4101
                    eNewType = wkbGeometryCollection;
1✔
4102

4103
                auto poMulti = std::unique_ptr<OGRGeometryCollection>(
4104
                    createGeometry(eNewType)->toGeometryCollection());
76✔
4105

4106
                double dfDateLineOffset = CPLAtofM(
38✔
4107
                    CSLFetchNameValueDef(papszOptions, "DATELINEOFFSET", "10"));
4108
                if (dfDateLineOffset <= 0.0 || dfDateLineOffset >= 360.0)
38✔
4109
                    dfDateLineOffset = 10.0;
×
4110

4111
                CutGeometryOnDateLineAndAddToMulti(
38✔
4112
                    poMulti.get(), poDstGeom.get(), dfDateLineOffset);
38✔
4113

4114
                if (poMulti->getNumGeometries() == 0)
38✔
4115
                {
4116
                    // do nothing
4117
                }
4118
                else if (poMulti->getNumGeometries() == 1 &&
39✔
4119
                         (eType == wkbPolygon || eType == wkbLineString))
1✔
4120
                {
4121
                    poDstGeom = poMulti->stealGeometry(0);
12✔
4122
                }
4123
                else
4124
                {
4125
                    poDstGeom = std::move(poMulti);
26✔
4126
                }
4127
            }
4128
        }
4129
    }
4130

4131
    return poDstGeom.release();
183✔
4132
}
4133

4134
/************************************************************************/
4135
/*                         OGRGeomTransformer()                         */
4136
/************************************************************************/
4137

4138
struct OGRGeomTransformer
4139
{
4140
    std::unique_ptr<OGRCoordinateTransformation> poCT{};
4141
    OGRGeometryFactory::TransformWithOptionsCache cache{};
4142
    CPLStringList aosOptions{};
4143

4144
    OGRGeomTransformer() = default;
6✔
4145
    OGRGeomTransformer(const OGRGeomTransformer &) = delete;
4146
    OGRGeomTransformer &operator=(const OGRGeomTransformer &) = delete;
4147
};
4148

4149
/************************************************************************/
4150
/*                     OGR_GeomTransformer_Create()                     */
4151
/************************************************************************/
4152

4153
/** Create a geometry transformer.
4154
 *
4155
 * This is a enhanced version of OGR_G_Transform().
4156
 *
4157
 * When reprojecting geometries from a Polar Stereographic projection or a
4158
 * projection naturally crossing the antimeridian (like UTM Zone 60) to a
4159
 * geographic CRS, it will cut geometries along the antimeridian. So a
4160
 * LineString might be returned as a MultiLineString.
4161
 *
4162
 * The WRAPDATELINE=YES option might be specified for circumstances to correct
4163
 * geometries that incorrectly go from a longitude on a side of the antimeridian
4164
 * to the other side, like a LINESTRING(-179 0,179 0) will be transformed to
4165
 * a MULTILINESTRING ((-179 0,-180 0),(180 0,179 0)). For that use case, hCT
4166
 * might be NULL.
4167
 *
4168
 * Supported options in papszOptions are:
4169
 * <ul>
4170
 * <li>WRAPDATELINE=YES</li>
4171
 * <li>DATELINEOFFSET=longitude_gap_in_degree. Defaults to 10.</li>
4172
 * </ul>
4173
 *
4174
 * This is the same as the C++ method OGRGeometryFactory::transformWithOptions().
4175

4176
 * @param hCT Coordinate transformation object (will be cloned) or NULL.
4177
 * @param papszOptions NULL terminated list of options, or NULL.
4178
 * @return transformer object to free with OGR_GeomTransformer_Destroy()
4179
 * @since GDAL 3.1
4180
 */
4181
OGRGeomTransformerH OGR_GeomTransformer_Create(OGRCoordinateTransformationH hCT,
6✔
4182
                                               CSLConstList papszOptions)
4183
{
4184
    OGRGeomTransformer *transformer = new OGRGeomTransformer;
6✔
4185
    if (hCT)
6✔
4186
    {
4187
        transformer->poCT.reset(
3✔
4188
            OGRCoordinateTransformation::FromHandle(hCT)->Clone());
3✔
4189
    }
4190
    transformer->aosOptions.Assign(CSLDuplicate(papszOptions));
6✔
4191
    return transformer;
6✔
4192
}
4193

4194
/************************************************************************/
4195
/*                     OGR_GeomTransformer_Transform()                  */
4196
/************************************************************************/
4197

4198
/** Transforms a geometry.
4199
 *
4200
 * @param hTransformer transformer object.
4201
 * @param hGeom Source geometry.
4202
 * @return a new geometry (or NULL) to destroy with OGR_G_DestroyGeometry()
4203
 * @since GDAL 3.1
4204
 */
4205
OGRGeometryH OGR_GeomTransformer_Transform(OGRGeomTransformerH hTransformer,
6✔
4206
                                           OGRGeometryH hGeom)
4207
{
4208
    VALIDATE_POINTER1(hTransformer, "OGR_GeomTransformer_Transform", nullptr);
6✔
4209
    VALIDATE_POINTER1(hGeom, "OGR_GeomTransformer_Transform", nullptr);
6✔
4210

4211
    return OGRGeometry::ToHandle(OGRGeometryFactory::transformWithOptions(
12✔
4212
        OGRGeometry::FromHandle(hGeom), hTransformer->poCT.get(),
6✔
4213
        hTransformer->aosOptions.List(), hTransformer->cache));
12✔
4214
}
4215

4216
/************************************************************************/
4217
/*                      OGR_GeomTransformer_Destroy()                   */
4218
/************************************************************************/
4219

4220
/** Destroy a geometry transformer allocated with OGR_GeomTransformer_Create()
4221
 *
4222
 * @param hTransformer transformer object.
4223
 * @since GDAL 3.1
4224
 */
4225
void OGR_GeomTransformer_Destroy(OGRGeomTransformerH hTransformer)
6✔
4226
{
4227
    delete hTransformer;
6✔
4228
}
6✔
4229

4230
/************************************************************************/
4231
/*                OGRGeometryFactory::GetDefaultArcStepSize()           */
4232
/************************************************************************/
4233

4234
/** Return the default value of the angular step used when stroking curves
4235
 * as lines. Defaults to 4 degrees.
4236
 * Can be modified by setting the OGR_ARC_STEPSIZE configuration option.
4237
 * Valid values are in [1e-2, 180] degree range.
4238
 * @since 3.11
4239
 */
4240

4241
/* static */
4242
double OGRGeometryFactory::GetDefaultArcStepSize()
4,375✔
4243
{
4244
    const double dfVal = CPLAtofM(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4"));
4,375✔
4245
    constexpr double MIN_VAL = 1e-2;
4,375✔
4246
    if (dfVal < MIN_VAL)
4,375✔
4247
    {
4248
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
1✔
4249
                     "Too small value for OGR_ARC_STEPSIZE. Clamping it to %f",
4250
                     MIN_VAL);
4251
        return MIN_VAL;
1✔
4252
    }
4253
    constexpr double MAX_VAL = 180;
4,374✔
4254
    if (dfVal > MAX_VAL)
4,374✔
4255
    {
4256
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
1✔
4257
                     "Too large value for OGR_ARC_STEPSIZE. Clamping it to %f",
4258
                     MAX_VAL);
4259
        return MAX_VAL;
1✔
4260
    }
4261
    return dfVal;
4,373✔
4262
}
4263

4264
/************************************************************************/
4265
/*                              DISTANCE()                              */
4266
/************************************************************************/
4267

4268
static inline double DISTANCE(double x1, double y1, double x2, double y2)
312,183✔
4269
{
4270
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
312,183✔
4271
}
4272

4273
/************************************************************************/
4274
/*                        approximateArcAngles()                        */
4275
/************************************************************************/
4276

4277
/**
4278
 * Stroke arc to linestring.
4279
 *
4280
 * Stroke an arc of a circle to a linestring based on a center
4281
 * point, radius, start angle and end angle, all angles in degrees.
4282
 *
4283
 * If the dfMaxAngleStepSizeDegrees is zero, then a default value will be
4284
 * used.  This is currently 4 degrees unless the user has overridden the
4285
 * value with the OGR_ARC_STEPSIZE configuration variable.
4286
 *
4287
 * If the OGR_ARC_MAX_GAP configuration variable is set, the straight-line
4288
 * distance between adjacent pairs of interpolated points will be limited to
4289
 * the specified distance. If the distance between a pair of points exceeds
4290
 * this maximum, additional points are interpolated between the two points.
4291
 *
4292
 * @see CPLSetConfigOption()
4293
 *
4294
 * @param dfCenterX center X
4295
 * @param dfCenterY center Y
4296
 * @param dfZ center Z
4297
 * @param dfPrimaryRadius X radius of ellipse.
4298
 * @param dfSecondaryRadius Y radius of ellipse.
4299
 * @param dfRotation rotation of the ellipse clockwise.
4300
 * @param dfStartAngle angle to first point on arc (clockwise of X-positive)
4301
 * @param dfEndAngle angle to last point on arc (clockwise of X-positive)
4302
 * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
4303
 * arc, zero to use the default setting.
4304
 * @param bUseMaxGap Optional: whether to honor OGR_ARC_MAX_GAP.
4305
 *
4306
 * @return OGRLineString geometry representing an approximation of the arc.
4307
 *
4308
 * @since OGR 1.8.0
4309
 */
4310

4311
OGRGeometry *OGRGeometryFactory::approximateArcAngles(
118✔
4312
    double dfCenterX, double dfCenterY, double dfZ, double dfPrimaryRadius,
4313
    double dfSecondaryRadius, double dfRotation, double dfStartAngle,
4314
    double dfEndAngle, double dfMaxAngleStepSizeDegrees,
4315
    const bool bUseMaxGap /* = false */)
4316

4317
{
4318
    OGRLineString *poLine = new OGRLineString();
118✔
4319
    const double dfRotationRadians = dfRotation * M_PI / 180.0;
118✔
4320

4321
    // Support default arc step setting.
4322
    if (dfMaxAngleStepSizeDegrees < 1e-6)
118✔
4323
    {
4324
        dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
117✔
4325
    }
4326

4327
    // Determine maximum interpolation gap. This is the largest straight-line
4328
    // distance allowed between pairs of interpolated points. Default zero,
4329
    // meaning no gap.
4330
    // coverity[tainted_data]
4331
    const double dfMaxInterpolationGap =
4332
        bUseMaxGap ? CPLAtofM(CPLGetConfigOption("OGR_ARC_MAX_GAP", "0")) : 0.0;
118✔
4333

4334
    // Is this a full circle?
4335
    const bool bIsFullCircle = fabs(dfEndAngle - dfStartAngle) == 360.0;
118✔
4336

4337
    // Switch direction.
4338
    dfStartAngle *= -1;
118✔
4339
    dfEndAngle *= -1;
118✔
4340

4341
    // Figure out the number of slices to make this into.
4342
    int nVertexCount =
4343
        std::max(2, static_cast<int>(ceil(fabs(dfEndAngle - dfStartAngle) /
236✔
4344
                                          dfMaxAngleStepSizeDegrees) +
118✔
4345
                                     1));
118✔
4346
    const double dfSlice = (dfEndAngle - dfStartAngle) / (nVertexCount - 1);
118✔
4347

4348
    // If it is a full circle we will work out the last point separately.
4349
    if (bIsFullCircle)
118✔
4350
    {
4351
        nVertexCount--;
52✔
4352
    }
4353

4354
    /* -------------------------------------------------------------------- */
4355
    /*      Compute the interpolated points.                                */
4356
    /* -------------------------------------------------------------------- */
4357
    double dfLastX = 0.0;
118✔
4358
    double dfLastY = 0.0;
118✔
4359
    int nTotalAddPoints = 0;
118✔
4360
    for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
7,071✔
4361
    {
4362
        const double dfAngleOnEllipse =
6,953✔
4363
            (dfStartAngle + iPoint * dfSlice) * M_PI / 180.0;
6,953✔
4364

4365
        // Compute position on the unrotated ellipse.
4366
        const double dfEllipseX = cos(dfAngleOnEllipse) * dfPrimaryRadius;
6,953✔
4367
        const double dfEllipseY = sin(dfAngleOnEllipse) * dfSecondaryRadius;
6,953✔
4368

4369
        // Is this point too far from the previous point?
4370
        if (iPoint && dfMaxInterpolationGap != 0.0)
6,953✔
4371
        {
4372
            const double dfDistFromLast =
4373
                DISTANCE(dfLastX, dfLastY, dfEllipseX, dfEllipseY);
1✔
4374

4375
            if (dfDistFromLast > dfMaxInterpolationGap)
1✔
4376
            {
4377
                const int nAddPoints =
1✔
4378
                    static_cast<int>(dfDistFromLast / dfMaxInterpolationGap);
1✔
4379
                const double dfAddSlice = dfSlice / (nAddPoints + 1);
1✔
4380

4381
                // Interpolate additional points
4382
                for (int iAddPoint = 0; iAddPoint < nAddPoints; iAddPoint++)
3✔
4383
                {
4384
                    const double dfAddAngleOnEllipse =
2✔
4385
                        (dfStartAngle + (iPoint - 1) * dfSlice +
2✔
4386
                         (iAddPoint + 1) * dfAddSlice) *
2✔
4387
                        (M_PI / 180.0);
4388

4389
                    poLine->setPoint(
2✔
4390
                        iPoint + nTotalAddPoints + iAddPoint,
2✔
4391
                        cos(dfAddAngleOnEllipse) * dfPrimaryRadius,
2✔
4392
                        sin(dfAddAngleOnEllipse) * dfSecondaryRadius, dfZ);
2✔
4393
                }
4394

4395
                nTotalAddPoints += nAddPoints;
1✔
4396
            }
4397
        }
4398

4399
        poLine->setPoint(iPoint + nTotalAddPoints, dfEllipseX, dfEllipseY, dfZ);
6,953✔
4400
        dfLastX = dfEllipseX;
6,953✔
4401
        dfLastY = dfEllipseY;
6,953✔
4402
    }
4403

4404
    /* -------------------------------------------------------------------- */
4405
    /*      Rotate and translate the ellipse.                               */
4406
    /* -------------------------------------------------------------------- */
4407
    nVertexCount = poLine->getNumPoints();
118✔
4408
    for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
7,073✔
4409
    {
4410
        const double dfEllipseX = poLine->getX(iPoint);
6,955✔
4411
        const double dfEllipseY = poLine->getY(iPoint);
6,955✔
4412

4413
        // Rotate this position around the center of the ellipse.
4414
        const double dfArcX = dfCenterX + dfEllipseX * cos(dfRotationRadians) +
6,955✔
4415
                              dfEllipseY * sin(dfRotationRadians);
6,955✔
4416
        const double dfArcY = dfCenterY - dfEllipseX * sin(dfRotationRadians) +
6,955✔
4417
                              dfEllipseY * cos(dfRotationRadians);
6,955✔
4418

4419
        poLine->setPoint(iPoint, dfArcX, dfArcY, dfZ);
6,955✔
4420
    }
4421

4422
    /* -------------------------------------------------------------------- */
4423
    /*      If we're asked to make a full circle, ensure the start and      */
4424
    /*      end points coincide exactly, in spite of any rounding error.    */
4425
    /* -------------------------------------------------------------------- */
4426
    if (bIsFullCircle)
118✔
4427
    {
4428
        OGRPoint oPoint;
104✔
4429
        poLine->getPoint(0, &oPoint);
52✔
4430
        poLine->setPoint(nVertexCount, &oPoint);
52✔
4431
    }
4432

4433
    return poLine;
118✔
4434
}
4435

4436
/************************************************************************/
4437
/*                     OGR_G_ApproximateArcAngles()                     */
4438
/************************************************************************/
4439

4440
/**
4441
 * Stroke arc to linestring.
4442
 *
4443
 * Stroke an arc of a circle to a linestring based on a center
4444
 * point, radius, start angle and end angle, all angles in degrees.
4445
 *
4446
 * If the dfMaxAngleStepSizeDegrees is zero, then a default value will be
4447
 * used.  This is currently 4 degrees unless the user has overridden the
4448
 * value with the OGR_ARC_STEPSIZE configuration variable.
4449
 *
4450
 * @see CPLSetConfigOption()
4451
 *
4452
 * @param dfCenterX center X
4453
 * @param dfCenterY center Y
4454
 * @param dfZ center Z
4455
 * @param dfPrimaryRadius X radius of ellipse.
4456
 * @param dfSecondaryRadius Y radius of ellipse.
4457
 * @param dfRotation rotation of the ellipse clockwise.
4458
 * @param dfStartAngle angle to first point on arc (clockwise of X-positive)
4459
 * @param dfEndAngle angle to last point on arc (clockwise of X-positive)
4460
 * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
4461
 * arc, zero to use the default setting.
4462
 *
4463
 * @return OGRLineString geometry representing an approximation of the arc.
4464
 *
4465
 * @since OGR 1.8.0
4466
 */
4467

4468
OGRGeometryH CPL_DLL OGR_G_ApproximateArcAngles(
1✔
4469
    double dfCenterX, double dfCenterY, double dfZ, double dfPrimaryRadius,
4470
    double dfSecondaryRadius, double dfRotation, double dfStartAngle,
4471
    double dfEndAngle, double dfMaxAngleStepSizeDegrees)
4472

4473
{
4474
    return OGRGeometry::ToHandle(OGRGeometryFactory::approximateArcAngles(
1✔
4475
        dfCenterX, dfCenterY, dfZ, dfPrimaryRadius, dfSecondaryRadius,
4476
        dfRotation, dfStartAngle, dfEndAngle, dfMaxAngleStepSizeDegrees));
1✔
4477
}
4478

4479
/************************************************************************/
4480
/*                           forceToLineString()                        */
4481
/************************************************************************/
4482

4483
/**
4484
 * \brief Convert to line string.
4485
 *
4486
 * Tries to force the provided geometry to be a line string.  This nominally
4487
 * effects a change on multilinestrings.
4488
 * In GDAL 2.0, for polygons or curvepolygons that have a single exterior ring,
4489
 * it will return the ring. For circular strings or compound curves, it will
4490
 * return an approximated line string.
4491
 *
4492
 * The passed in geometry is
4493
 * consumed and a new one returned (or potentially the same one).
4494
 *
4495
 * @param poGeom the input geometry - ownership is passed to the method.
4496
 * @param bOnlyInOrder flag that, if set to FALSE, indicate that the order of
4497
 *                     points in a linestring might be reversed if it enables
4498
 *                     to match the extremity of another linestring. If set
4499
 *                     to TRUE, the start of a linestring must match the end
4500
 *                     of another linestring.
4501
 * @return new geometry.
4502
 */
4503

4504
OGRGeometry *OGRGeometryFactory::forceToLineString(OGRGeometry *poGeom,
177✔
4505
                                                   bool bOnlyInOrder)
4506

4507
{
4508
    if (poGeom == nullptr)
177✔
4509
        return nullptr;
2✔
4510

4511
    const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
175✔
4512

4513
    /* -------------------------------------------------------------------- */
4514
    /*      If this is already a LineString, nothing to do                  */
4515
    /* -------------------------------------------------------------------- */
4516
    if (eGeomType == wkbLineString)
175✔
4517
    {
4518
        // Except if it is a linearring.
4519
        poGeom = OGRCurve::CastToLineString(poGeom->toCurve());
25✔
4520

4521
        return poGeom;
25✔
4522
    }
4523

4524
    /* -------------------------------------------------------------------- */
4525
    /*      If it is a polygon with a single ring, return it                 */
4526
    /* -------------------------------------------------------------------- */
4527
    if (eGeomType == wkbPolygon || eGeomType == wkbCurvePolygon)
150✔
4528
    {
4529
        OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
30✔
4530
        if (poCP->getNumInteriorRings() == 0)
30✔
4531
        {
4532
            OGRCurve *poRing = poCP->stealExteriorRingCurve();
28✔
4533
            delete poCP;
28✔
4534
            return forceToLineString(poRing);
28✔
4535
        }
4536
        return poGeom;
2✔
4537
    }
4538

4539
    /* -------------------------------------------------------------------- */
4540
    /*      If it is a curve line, call CurveToLine()                        */
4541
    /* -------------------------------------------------------------------- */
4542
    if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
120✔
4543
    {
4544
        OGRGeometry *poNewGeom = poGeom->toCurve()->CurveToLine();
69✔
4545
        delete poGeom;
69✔
4546
        return poNewGeom;
69✔
4547
    }
4548

4549
    if (eGeomType != wkbGeometryCollection && eGeomType != wkbMultiLineString &&
51✔
4550
        eGeomType != wkbMultiCurve)
4551
        return poGeom;
20✔
4552

4553
    // Build an aggregated linestring from all the linestrings in the container.
4554
    OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
31✔
4555
    if (poGeom->hasCurveGeometry())
31✔
4556
    {
4557
        OGRGeometryCollection *poNewGC =
4558
            poGC->getLinearGeometry()->toGeometryCollection();
7✔
4559
        delete poGC;
7✔
4560
        poGC = poNewGC;
7✔
4561
    }
4562

4563
    if (poGC->getNumGeometries() == 0)
31✔
4564
    {
4565
        poGeom = new OGRLineString();
3✔
4566
        poGeom->assignSpatialReference(poGC->getSpatialReference());
3✔
4567
        delete poGC;
3✔
4568
        return poGeom;
3✔
4569
    }
4570

4571
    int iGeom0 = 0;
28✔
4572
    while (iGeom0 < poGC->getNumGeometries())
69✔
4573
    {
4574
        if (wkbFlatten(poGC->getGeometryRef(iGeom0)->getGeometryType()) !=
41✔
4575
            wkbLineString)
4576
        {
4577
            iGeom0++;
12✔
4578
            continue;
26✔
4579
        }
4580

4581
        OGRLineString *poLineString0 =
4582
            poGC->getGeometryRef(iGeom0)->toLineString();
29✔
4583
        if (poLineString0->getNumPoints() < 2)
29✔
4584
        {
4585
            iGeom0++;
14✔
4586
            continue;
14✔
4587
        }
4588

4589
        OGRPoint pointStart0;
30✔
4590
        poLineString0->StartPoint(&pointStart0);
15✔
4591
        OGRPoint pointEnd0;
30✔
4592
        poLineString0->EndPoint(&pointEnd0);
15✔
4593

4594
        int iGeom1 = iGeom0 + 1;  // Used after for.
15✔
4595
        for (; iGeom1 < poGC->getNumGeometries(); iGeom1++)
17✔
4596
        {
4597
            if (wkbFlatten(poGC->getGeometryRef(iGeom1)->getGeometryType()) !=
6✔
4598
                wkbLineString)
4599
                continue;
1✔
4600

4601
            OGRLineString *poLineString1 =
4602
                poGC->getGeometryRef(iGeom1)->toLineString();
6✔
4603
            if (poLineString1->getNumPoints() < 2)
6✔
4604
                continue;
1✔
4605

4606
            OGRPoint pointStart1;
5✔
4607
            poLineString1->StartPoint(&pointStart1);
5✔
4608
            OGRPoint pointEnd1;
5✔
4609
            poLineString1->EndPoint(&pointEnd1);
5✔
4610

4611
            if (!bOnlyInOrder && (pointEnd0.Equals(&pointEnd1) ||
5✔
4612
                                  pointStart0.Equals(&pointStart1)))
×
4613
            {
4614
                poLineString1->reversePoints();
×
4615
                poLineString1->StartPoint(&pointStart1);
×
4616
                poLineString1->EndPoint(&pointEnd1);
×
4617
            }
4618

4619
            if (pointEnd0.Equals(&pointStart1))
5✔
4620
            {
4621
                poLineString0->addSubLineString(poLineString1, 1);
4✔
4622
                poGC->removeGeometry(iGeom1);
4✔
4623
                break;
4✔
4624
            }
4625

4626
            if (pointEnd1.Equals(&pointStart0))
1✔
4627
            {
4628
                poLineString1->addSubLineString(poLineString0, 1);
×
4629
                poGC->removeGeometry(iGeom0);
×
4630
                break;
×
4631
            }
4632
        }
4633

4634
        if (iGeom1 == poGC->getNumGeometries())
15✔
4635
        {
4636
            iGeom0++;
14✔
4637
        }
4638
    }
4639

4640
    if (poGC->getNumGeometries() == 1)
28✔
4641
    {
4642
        OGRGeometry *poSingleGeom = poGC->getGeometryRef(0);
20✔
4643
        poGC->removeGeometry(0, FALSE);
20✔
4644
        delete poGC;
20✔
4645

4646
        return poSingleGeom;
20✔
4647
    }
4648

4649
    return poGC;
8✔
4650
}
4651

4652
/************************************************************************/
4653
/*                      OGR_G_ForceToLineString()                       */
4654
/************************************************************************/
4655

4656
/**
4657
 * \brief Convert to line string.
4658
 *
4659
 * This function is the same as the C++ method
4660
 * OGRGeometryFactory::forceToLineString().
4661
 *
4662
 * @param hGeom handle to the geometry to convert (ownership surrendered).
4663
 * @return the converted geometry (ownership to caller).
4664
 *
4665
 * @since GDAL/OGR 1.10.0
4666
 */
4667

4668
OGRGeometryH OGR_G_ForceToLineString(OGRGeometryH hGeom)
60✔
4669

4670
{
4671
    return OGRGeometry::ToHandle(
60✔
4672
        OGRGeometryFactory::forceToLineString(OGRGeometry::FromHandle(hGeom)));
60✔
4673
}
4674

4675
/************************************************************************/
4676
/*                           forceTo()                                  */
4677
/************************************************************************/
4678

4679
/**
4680
 * \brief Convert to another geometry type
4681
 *
4682
 * Tries to force the provided geometry to the specified geometry type.
4683
 *
4684
 * It can promote 'single' geometry type to their corresponding collection type
4685
 * (see OGR_GT_GetCollection()) or the reverse. non-linear geometry type to
4686
 * their corresponding linear geometry type (see OGR_GT_GetLinear()), by
4687
 * possibly approximating circular arcs they may contain.  Regarding conversion
4688
 * from linear geometry types to curve geometry types, only "wrapping" will be
4689
 * done. No attempt to retrieve potential circular arcs by de-approximating
4690
 * stroking will be done. For that, OGRGeometry::getCurveGeometry() can be used.
4691
 *
4692
 * The passed in geometry is consumed and a new one returned (or potentially the
4693
 * same one).
4694
 *
4695
 * Starting with GDAL 3.9, this method honours the dimensionality of eTargetType.
4696
 *
4697
 * @param poGeom the input geometry - ownership is passed to the method.
4698
 * @param eTargetType target output geometry type.
4699
 * @param papszOptions options as a null-terminated list of strings or NULL.
4700
 * @return new geometry, or nullptr in case of error.
4701
 *
4702
 * @since GDAL 2.0
4703
 */
4704

4705
OGRGeometry *OGRGeometryFactory::forceTo(OGRGeometry *poGeom,
5,034✔
4706
                                         OGRwkbGeometryType eTargetType,
4707
                                         const char *const *papszOptions)
4708
{
4709
    if (poGeom == nullptr)
5,034✔
4710
        return poGeom;
×
4711

4712
    const OGRwkbGeometryType eTargetTypeFlat = wkbFlatten(eTargetType);
5,034✔
4713
    if (eTargetTypeFlat == wkbUnknown)
5,034✔
4714
        return poGeom;
268✔
4715

4716
    if (poGeom->IsEmpty())
4,766✔
4717
    {
4718
        OGRGeometry *poRet = createGeometry(eTargetType);
277✔
4719
        if (poRet)
277✔
4720
        {
4721
            poRet->assignSpatialReference(poGeom->getSpatialReference());
277✔
4722
            poRet->set3D(OGR_GT_HasZ(eTargetType));
277✔
4723
            poRet->setMeasured(OGR_GT_HasM(eTargetType));
277✔
4724
        }
4725
        delete poGeom;
277✔
4726
        return poRet;
277✔
4727
    }
4728

4729
    OGRwkbGeometryType eType = poGeom->getGeometryType();
4,489✔
4730
    OGRwkbGeometryType eTypeFlat = wkbFlatten(eType);
4,489✔
4731

4732
    if (eTargetTypeFlat != eTargetType && (eType == eTypeFlat))
4,489✔
4733
    {
4734
        auto poGeomNew = forceTo(poGeom, eTargetTypeFlat, papszOptions);
66✔
4735
        if (poGeomNew)
66✔
4736
        {
4737
            poGeomNew->set3D(OGR_GT_HasZ(eTargetType));
66✔
4738
            poGeomNew->setMeasured(OGR_GT_HasM(eTargetType));
66✔
4739
        }
4740
        return poGeomNew;
66✔
4741
    }
4742

4743
    if (eTypeFlat == eTargetTypeFlat)
4,423✔
4744
    {
4745
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
535✔
4746
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
535✔
4747
        return poGeom;
535✔
4748
    }
4749

4750
    eType = eTypeFlat;
3,888✔
4751

4752
    if (OGR_GT_IsSubClassOf(eType, wkbPolyhedralSurface) &&
5,609✔
4753
        (eTargetTypeFlat == wkbMultiSurface ||
1,721✔
4754
         eTargetTypeFlat == wkbGeometryCollection))
4755
    {
4756
        OGRwkbGeometryType eTempGeomType = wkbMultiPolygon;
853✔
4757
        if (OGR_GT_HasZ(eTargetType))
853✔
4758
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
849✔
4759
        if (OGR_GT_HasM(eTargetType))
853✔
4760
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
×
4761
        return forceTo(forceTo(poGeom, eTempGeomType, papszOptions),
853✔
4762
                       eTargetType, papszOptions);
853✔
4763
    }
4764

4765
    if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
3,035✔
4766
        eTargetTypeFlat == wkbGeometryCollection)
4767
    {
4768
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
920✔
4769
        auto poRet = OGRGeometryCollection::CastToGeometryCollection(poGC);
920✔
4770
        poRet->set3D(OGR_GT_HasZ(eTargetType));
920✔
4771
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
920✔
4772
        return poRet;
920✔
4773
    }
4774

4775
    if (eType == wkbTriangle && eTargetTypeFlat == wkbPolyhedralSurface)
2,115✔
4776
    {
4777
        OGRPolyhedralSurface *poPS = new OGRPolyhedralSurface();
1✔
4778
        poPS->assignSpatialReference(poGeom->getSpatialReference());
1✔
4779
        poPS->addGeometryDirectly(OGRTriangle::CastToPolygon(poGeom));
1✔
4780
        poPS->set3D(OGR_GT_HasZ(eTargetType));
1✔
4781
        poPS->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4782
        return poPS;
1✔
4783
    }
4784
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbPolyhedralSurface)
2,114✔
4785
    {
4786
        OGRPolyhedralSurface *poPS = new OGRPolyhedralSurface();
3✔
4787
        poPS->assignSpatialReference(poGeom->getSpatialReference());
3✔
4788
        poPS->addGeometryDirectly(poGeom);
3✔
4789
        poPS->set3D(OGR_GT_HasZ(eTargetType));
3✔
4790
        poPS->setMeasured(OGR_GT_HasM(eTargetType));
3✔
4791
        return poPS;
3✔
4792
    }
4793
    else if (eType == wkbMultiPolygon &&
2,111✔
4794
             eTargetTypeFlat == wkbPolyhedralSurface)
4795
    {
4796
        OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
2✔
4797
        OGRPolyhedralSurface *poPS = new OGRPolyhedralSurface();
2✔
4798
        for (int i = 0; i < poMP->getNumGeometries(); ++i)
4✔
4799
        {
4800
            poPS->addGeometry(poMP->getGeometryRef(i));
2✔
4801
        }
4802
        delete poGeom;
2✔
4803
        poPS->set3D(OGR_GT_HasZ(eTargetType));
2✔
4804
        poPS->setMeasured(OGR_GT_HasM(eTargetType));
2✔
4805
        return poPS;
2✔
4806
    }
4807
    else if (eType == wkbTIN && eTargetTypeFlat == wkbPolyhedralSurface)
2,109✔
4808
    {
4809
        poGeom = OGRTriangulatedSurface::CastToPolyhedralSurface(
1✔
4810
            poGeom->toTriangulatedSurface());
4811
    }
4812
    else if (eType == wkbCurvePolygon &&
2,108✔
4813
             eTargetTypeFlat == wkbPolyhedralSurface)
4814
    {
4815
        OGRwkbGeometryType eTempGeomType = wkbPolygon;
1✔
4816
        if (OGR_GT_HasZ(eTargetType))
1✔
4817
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
×
4818
        if (OGR_GT_HasM(eTargetType))
1✔
4819
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
×
4820
        return forceTo(forceTo(poGeom, eTempGeomType, papszOptions),
1✔
4821
                       eTargetType, papszOptions);
1✔
4822
    }
4823
    else if (eType == wkbMultiSurface &&
2,107✔
4824
             eTargetTypeFlat == wkbPolyhedralSurface)
4825
    {
4826
        OGRwkbGeometryType eTempGeomType = wkbMultiPolygon;
1✔
4827
        if (OGR_GT_HasZ(eTargetType))
1✔
4828
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
×
4829
        if (OGR_GT_HasM(eTargetType))
1✔
4830
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
×
4831
        return forceTo(forceTo(poGeom, eTempGeomType, papszOptions),
1✔
4832
                       eTargetType, papszOptions);
1✔
4833
    }
4834

4835
    else if (eType == wkbTriangle && eTargetTypeFlat == wkbTIN)
2,106✔
4836
    {
4837
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
1✔
4838
        poTS->assignSpatialReference(poGeom->getSpatialReference());
1✔
4839
        poTS->addGeometryDirectly(poGeom);
1✔
4840
        poTS->set3D(OGR_GT_HasZ(eTargetType));
1✔
4841
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4842
        return poTS;
1✔
4843
    }
4844
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbTIN)
2,105✔
4845
    {
4846
        OGRPolygon *poPoly = poGeom->toPolygon();
4✔
4847
        OGRLinearRing *poLR = poPoly->getExteriorRing();
4✔
4848
        if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
7✔
4849
              poPoly->getNumInteriorRings() == 0))
3✔
4850
        {
4851
            return poGeom;
1✔
4852
        }
4853
        OGRErr eErr = OGRERR_NONE;
3✔
4854
        OGRTriangle *poTriangle = new OGRTriangle(*poPoly, eErr);
3✔
4855
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
3✔
4856
        poTS->assignSpatialReference(poGeom->getSpatialReference());
3✔
4857
        poTS->addGeometryDirectly(poTriangle);
3✔
4858
        delete poGeom;
3✔
4859
        poTS->set3D(OGR_GT_HasZ(eTargetType));
3✔
4860
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
3✔
4861
        return poTS;
3✔
4862
    }
4863
    else if (eType == wkbMultiPolygon && eTargetTypeFlat == wkbTIN)
2,101✔
4864
    {
4865
        OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
1✔
4866
        for (const auto poPoly : *poMP)
2✔
4867
        {
4868
            const OGRLinearRing *poLR = poPoly->getExteriorRing();
1✔
4869
            if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
2✔
4870
                  poPoly->getNumInteriorRings() == 0))
1✔
4871
            {
4872
                return poGeom;
×
4873
            }
4874
        }
4875
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
1✔
4876
        poTS->assignSpatialReference(poGeom->getSpatialReference());
1✔
4877
        for (const auto poPoly : *poMP)
2✔
4878
        {
4879
            OGRErr eErr = OGRERR_NONE;
1✔
4880
            poTS->addGeometryDirectly(new OGRTriangle(*poPoly, eErr));
1✔
4881
        }
4882
        delete poGeom;
1✔
4883
        poTS->set3D(OGR_GT_HasZ(eTargetType));
1✔
4884
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4885
        return poTS;
1✔
4886
    }
4887
    else if (eType == wkbPolyhedralSurface && eTargetTypeFlat == wkbTIN)
2,100✔
4888
    {
4889
        OGRPolyhedralSurface *poPS = poGeom->toPolyhedralSurface();
2✔
4890
        for (const auto poPoly : *poPS)
3✔
4891
        {
4892
            const OGRLinearRing *poLR = poPoly->getExteriorRing();
2✔
4893
            if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
3✔
4894
                  poPoly->getNumInteriorRings() == 0))
1✔
4895
            {
4896
                poGeom->set3D(OGR_GT_HasZ(eTargetType));
1✔
4897
                poGeom->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4898
                return poGeom;
1✔
4899
            }
4900
        }
4901
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
1✔
4902
        poTS->assignSpatialReference(poGeom->getSpatialReference());
1✔
4903
        for (const auto poPoly : *poPS)
2✔
4904
        {
4905
            OGRErr eErr = OGRERR_NONE;
1✔
4906
            poTS->addGeometryDirectly(new OGRTriangle(*poPoly, eErr));
1✔
4907
        }
4908
        delete poGeom;
1✔
4909
        poTS->set3D(OGR_GT_HasZ(eTargetType));
1✔
4910
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4911
        return poTS;
1✔
4912
    }
4913

4914
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbTriangle)
2,098✔
4915
    {
4916
        OGRPolygon *poPoly = poGeom->toPolygon();
7✔
4917
        OGRLinearRing *poLR = poPoly->getExteriorRing();
7✔
4918
        if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
13✔
4919
              poPoly->getNumInteriorRings() == 0))
6✔
4920
        {
4921
            poGeom->set3D(OGR_GT_HasZ(eTargetType));
1✔
4922
            poGeom->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4923
            return poGeom;
1✔
4924
        }
4925
        OGRErr eErr = OGRERR_NONE;
6✔
4926
        OGRTriangle *poTriangle = new OGRTriangle(*poPoly, eErr);
6✔
4927
        delete poGeom;
6✔
4928
        poTriangle->set3D(OGR_GT_HasZ(eTargetType));
6✔
4929
        poTriangle->setMeasured(OGR_GT_HasM(eTargetType));
6✔
4930
        return poTriangle;
6✔
4931
    }
4932

4933
    if (eTargetTypeFlat == wkbTriangle || eTargetTypeFlat == wkbTIN ||
2,092✔
4934
        eTargetTypeFlat == wkbPolyhedralSurface)
4935
    {
4936
        OGRwkbGeometryType eTempGeomType = wkbPolygon;
9✔
4937
        if (OGR_GT_HasZ(eTargetType))
9✔
4938
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
×
4939
        if (OGR_GT_HasM(eTargetType))
9✔
4940
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
1✔
4941
        OGRGeometry *poPoly = forceTo(poGeom, eTempGeomType, papszOptions);
9✔
4942
        if (poPoly == poGeom)
9✔
4943
            return poGeom;
×
4944
        return forceTo(poPoly, eTargetType, papszOptions);
9✔
4945
    }
4946

4947
    if (eType == wkbTriangle && eTargetTypeFlat == wkbGeometryCollection)
2,083✔
4948
    {
4949
        OGRGeometryCollection *poGC = new OGRGeometryCollection();
1✔
4950
        poGC->assignSpatialReference(poGeom->getSpatialReference());
1✔
4951
        poGC->addGeometryDirectly(poGeom);
1✔
4952
        poGC->set3D(OGR_GT_HasZ(eTargetType));
1✔
4953
        poGC->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4954
        return poGC;
1✔
4955
    }
4956

4957
    // Promote single to multi.
4958
    if (!OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
3,878✔
4959
        OGR_GT_IsSubClassOf(OGR_GT_GetCollection(eType), eTargetType))
1,796✔
4960
    {
4961
        OGRGeometry *poRet = createGeometry(eTargetType);
497✔
4962
        if (poRet == nullptr)
497✔
4963
        {
4964
            delete poGeom;
×
4965
            return nullptr;
×
4966
        }
4967
        poRet->assignSpatialReference(poGeom->getSpatialReference());
497✔
4968
        if (eType == wkbLineString)
497✔
4969
            poGeom = OGRCurve::CastToLineString(poGeom->toCurve());
43✔
4970
        poRet->toGeometryCollection()->addGeometryDirectly(poGeom);
497✔
4971
        poRet->set3D(OGR_GT_HasZ(eTargetType));
497✔
4972
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
497✔
4973
        return poRet;
497✔
4974
    }
4975

4976
    const bool bIsCurve = CPL_TO_BOOL(OGR_GT_IsCurve(eType));
1,585✔
4977
    if (bIsCurve && eTargetTypeFlat == wkbCompoundCurve)
1,585✔
4978
    {
4979
        auto poRet = OGRCurve::CastToCompoundCurve(poGeom->toCurve());
32✔
4980
        if (poRet)
32✔
4981
        {
4982
            poRet->set3D(OGR_GT_HasZ(eTargetType));
30✔
4983
            poRet->setMeasured(OGR_GT_HasM(eTargetType));
30✔
4984
        }
4985
        return poRet;
32✔
4986
    }
4987
    else if (bIsCurve && eTargetTypeFlat == wkbCurvePolygon)
1,553✔
4988
    {
4989
        OGRCurve *poCurve = poGeom->toCurve();
26✔
4990
        if (poCurve->getNumPoints() >= 3 && poCurve->get_IsClosed())
26✔
4991
        {
4992
            OGRCurvePolygon *poCP = new OGRCurvePolygon();
18✔
4993
            if (poCP->addRingDirectly(poCurve) == OGRERR_NONE)
18✔
4994
            {
4995
                poCP->assignSpatialReference(poGeom->getSpatialReference());
18✔
4996
                poCP->set3D(OGR_GT_HasZ(eTargetType));
18✔
4997
                poCP->setMeasured(OGR_GT_HasM(eTargetType));
18✔
4998
                return poCP;
18✔
4999
            }
5000
            else
5001
            {
5002
                delete poCP;
×
5003
            }
5004
        }
8✔
5005
    }
5006
    else if (eType == wkbLineString &&
1,604✔
5007
             OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface))
77✔
5008
    {
5009
        OGRGeometry *poTmp = forceTo(poGeom, wkbPolygon, papszOptions);
23✔
5010
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
23✔
5011
            return forceTo(poTmp, eTargetType, papszOptions);
15✔
5012
    }
5013
    else if (bIsCurve && eTargetTypeFlat == wkbMultiSurface)
1,504✔
5014
    {
5015
        OGRGeometry *poTmp = forceTo(poGeom, wkbCurvePolygon, papszOptions);
10✔
5016
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
10✔
5017
            return forceTo(poTmp, eTargetType, papszOptions);
10✔
5018
    }
5019
    else if (bIsCurve && eTargetTypeFlat == wkbMultiPolygon)
1,494✔
5020
    {
5021
        OGRGeometry *poTmp = forceTo(poGeom, wkbPolygon, papszOptions);
13✔
5022
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
13✔
5023
            return forceTo(poTmp, eTargetType, papszOptions);
13✔
5024
    }
5025
    else if (eType == wkbTriangle && eTargetTypeFlat == wkbCurvePolygon)
1,481✔
5026
    {
5027
        auto poRet = OGRSurface::CastToCurvePolygon(
1✔
5028
            OGRTriangle::CastToPolygon(poGeom)->toSurface());
5029
        poRet->set3D(OGR_GT_HasZ(eTargetType));
1✔
5030
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
1✔
5031
        return poRet;
1✔
5032
    }
5033
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbCurvePolygon)
1,480✔
5034
    {
5035
        auto poRet = OGRSurface::CastToCurvePolygon(poGeom->toPolygon());
19✔
5036
        poRet->set3D(OGR_GT_HasZ(eTargetType));
19✔
5037
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
19✔
5038
        return poRet;
19✔
5039
    }
5040
    else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
1,461✔
5041
             eTargetTypeFlat == wkbCompoundCurve)
5042
    {
5043
        OGRCurvePolygon *poPoly = poGeom->toCurvePolygon();
15✔
5044
        if (poPoly->getNumInteriorRings() == 0)
15✔
5045
        {
5046
            OGRCurve *poRet = poPoly->stealExteriorRingCurve();
14✔
5047
            if (poRet)
14✔
5048
                poRet->assignSpatialReference(poGeom->getSpatialReference());
14✔
5049
            delete poPoly;
14✔
5050
            return forceTo(poRet, eTargetType, papszOptions);
14✔
5051
        }
5052
    }
5053
    else if (eType == wkbMultiPolygon && eTargetTypeFlat == wkbMultiSurface)
1,446✔
5054
    {
5055
        auto poRet =
5056
            OGRMultiPolygon::CastToMultiSurface(poGeom->toMultiPolygon());
14✔
5057
        poRet->set3D(OGR_GT_HasZ(eTargetType));
14✔
5058
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
14✔
5059
        return poRet;
14✔
5060
    }
5061
    else if (eType == wkbMultiLineString && eTargetTypeFlat == wkbMultiCurve)
1,432✔
5062
    {
5063
        auto poRet =
5064
            OGRMultiLineString::CastToMultiCurve(poGeom->toMultiLineString());
9✔
5065
        poRet->set3D(OGR_GT_HasZ(eTargetType));
9✔
5066
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
9✔
5067
        return poRet;
9✔
5068
    }
5069
    else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1,423✔
5070
    {
5071
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
263✔
5072
        if (poGC->getNumGeometries() == 1)
263✔
5073
        {
5074
            OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
170✔
5075
            if (poSubGeom)
170✔
5076
            {
5077
                poSubGeom->assignSpatialReference(
170✔
5078
                    poGeom->getSpatialReference());
170✔
5079
                poGC->removeGeometry(0, FALSE);
170✔
5080
                OGRGeometry *poRet =
5081
                    forceTo(poSubGeom->clone(), eTargetType, papszOptions);
170✔
5082
                if (OGR_GT_IsSubClassOf(wkbFlatten(poRet->getGeometryType()),
170✔
5083
                                        eTargetType))
170✔
5084
                {
5085
                    delete poGC;
135✔
5086
                    delete poSubGeom;
135✔
5087
                    return poRet;
135✔
5088
                }
5089
                poGC->addGeometryDirectly(poSubGeom);
35✔
5090
                poRet->set3D(OGR_GT_HasZ(eTargetType));
35✔
5091
                poRet->setMeasured(OGR_GT_HasM(eTargetType));
35✔
5092
                delete poRet;
35✔
5093
            }
5094
        }
5095
    }
5096
    else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
1,274✔
5097
             (OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface) ||
114✔
5098
              OGR_GT_IsSubClassOf(eTargetType, wkbMultiCurve)))
102✔
5099
    {
5100
        OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
43✔
5101
        if (poCP->getNumInteriorRings() == 0)
43✔
5102
        {
5103
            OGRCurve *poRing = poCP->getExteriorRingCurve();
41✔
5104
            poRing->assignSpatialReference(poGeom->getSpatialReference());
41✔
5105
            OGRwkbGeometryType eRingType = poRing->getGeometryType();
41✔
5106
            OGRGeometry *poRingDup = poRing->clone();
41✔
5107
            OGRGeometry *poRet = forceTo(poRingDup, eTargetType, papszOptions);
41✔
5108
            if (poRet->getGeometryType() != eRingType &&
57✔
5109
                !(eTypeFlat == wkbPolygon &&
16✔
5110
                  eTargetTypeFlat == wkbMultiLineString))
5111
            {
5112
                delete poCP;
29✔
5113
                return poRet;
29✔
5114
            }
5115
            else
5116
            {
5117
                delete poRet;
12✔
5118
            }
5119
        }
5120
    }
5121

5122
    if (eTargetTypeFlat == wkbLineString)
1,280✔
5123
    {
5124
        poGeom = forceToLineString(poGeom);
89✔
5125
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
89✔
5126
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
89✔
5127
    }
5128
    else if (eTargetTypeFlat == wkbPolygon)
1,191✔
5129
    {
5130
        poGeom = forceToPolygon(poGeom);
99✔
5131
        if (poGeom)
99✔
5132
        {
5133
            poGeom->set3D(OGR_GT_HasZ(eTargetType));
99✔
5134
            poGeom->setMeasured(OGR_GT_HasM(eTargetType));
99✔
5135
        }
5136
    }
5137
    else if (eTargetTypeFlat == wkbMultiPolygon)
1,092✔
5138
    {
5139
        poGeom = forceToMultiPolygon(poGeom);
912✔
5140
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
912✔
5141
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
912✔
5142
    }
5143
    else if (eTargetTypeFlat == wkbMultiLineString)
180✔
5144
    {
5145
        poGeom = forceToMultiLineString(poGeom);
37✔
5146
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
37✔
5147
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
37✔
5148
    }
5149
    else if (eTargetTypeFlat == wkbMultiPoint)
143✔
5150
    {
5151
        poGeom = forceToMultiPoint(poGeom);
22✔
5152
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
22✔
5153
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
22✔
5154
    }
5155

5156
    return poGeom;
1,280✔
5157
}
5158

5159
/************************************************************************/
5160
/*                          OGR_G_ForceTo()                             */
5161
/************************************************************************/
5162

5163
/**
5164
 * \brief Convert to another geometry type
5165
 *
5166
 * This function is the same as the C++ method OGRGeometryFactory::forceTo().
5167
 *
5168
 * @param hGeom the input geometry - ownership is passed to the method.
5169
 * @param eTargetType target output geometry type.
5170
 * @param papszOptions options as a null-terminated list of strings or NULL.
5171
 * @return new geometry.
5172
 *
5173
 * @since GDAL 2.0
5174
 */
5175

5176
OGRGeometryH OGR_G_ForceTo(OGRGeometryH hGeom, OGRwkbGeometryType eTargetType,
848✔
5177
                           char **papszOptions)
5178

5179
{
5180
    return OGRGeometry::ToHandle(OGRGeometryFactory::forceTo(
848✔
5181
        OGRGeometry::FromHandle(hGeom), eTargetType, papszOptions));
848✔
5182
}
5183

5184
/************************************************************************/
5185
/*                         GetCurveParameters()                          */
5186
/************************************************************************/
5187

5188
/**
5189
 * \brief Returns the parameter of an arc circle.
5190
 *
5191
 * Angles are return in radians, with trigonometic convention (counter clock
5192
 * wise)
5193
 *
5194
 * @param x0 x of first point
5195
 * @param y0 y of first point
5196
 * @param x1 x of intermediate point
5197
 * @param y1 y of intermediate point
5198
 * @param x2 x of final point
5199
 * @param y2 y of final point
5200
 * @param R radius (output)
5201
 * @param cx x of arc center (output)
5202
 * @param cy y of arc center (output)
5203
 * @param alpha0 angle between center and first point, in radians (output)
5204
 * @param alpha1 angle between center and intermediate point, in radians
5205
 * (output)
5206
 * @param alpha2 angle between center and final point, in radians (output)
5207
 * @return TRUE if the points are not aligned and define an arc circle.
5208
 *
5209
 * @since GDAL 2.0
5210
 */
5211

5212
int OGRGeometryFactory::GetCurveParameters(double x0, double y0, double x1,
188,070✔
5213
                                           double y1, double x2, double y2,
5214
                                           double &R, double &cx, double &cy,
5215
                                           double &alpha0, double &alpha1,
5216
                                           double &alpha2)
5217
{
5218
    if (std::isnan(x0) || std::isnan(y0) || std::isnan(x1) || std::isnan(y1) ||
564,210✔
5219
        std::isnan(x2) || std::isnan(y2))
564,210✔
5220
    {
5221
        return FALSE;
×
5222
    }
5223

5224
    // Circle.
5225
    if (x0 == x2 && y0 == y2)
188,070✔
5226
    {
5227
        if (x0 != x1 || y0 != y1)
149✔
5228
        {
5229
            cx = (x0 + x1) / 2;
148✔
5230
            cy = (y0 + y1) / 2;
148✔
5231
            R = DISTANCE(cx, cy, x0, y0);
148✔
5232
            // Arbitrarily pick counter-clock-wise order (like PostGIS does).
5233
            alpha0 = atan2(y0 - cy, x0 - cx);
148✔
5234
            alpha1 = alpha0 + M_PI;
148✔
5235
            alpha2 = alpha0 + 2 * M_PI;
148✔
5236
            return TRUE;
148✔
5237
        }
5238
        else
5239
        {
5240
            return FALSE;
1✔
5241
        }
5242
    }
5243

5244
    double dx01 = x1 - x0;
187,921✔
5245
    double dy01 = y1 - y0;
187,921✔
5246
    double dx12 = x2 - x1;
187,921✔
5247
    double dy12 = y2 - y1;
187,921✔
5248

5249
    // Normalize above values so as to make sure we don't end up with
5250
    // computing a difference of too big values.
5251
    double dfScale = fabs(dx01);
187,921✔
5252
    if (fabs(dy01) > dfScale)
187,921✔
5253
        dfScale = fabs(dy01);
93,710✔
5254
    if (fabs(dx12) > dfScale)
187,921✔
5255
        dfScale = fabs(dx12);
46,866✔
5256
    if (fabs(dy12) > dfScale)
187,921✔
5257
        dfScale = fabs(dy12);
46,819✔
5258
    const double dfInvScale = 1.0 / dfScale;
187,921✔
5259
    dx01 *= dfInvScale;
187,921✔
5260
    dy01 *= dfInvScale;
187,921✔
5261
    dx12 *= dfInvScale;
187,921✔
5262
    dy12 *= dfInvScale;
187,921✔
5263

5264
    const double det = dx01 * dy12 - dx12 * dy01;
187,921✔
5265
    if (fabs(det) < 1.0e-8 || std::isnan(det))
187,921✔
5266
    {
5267
        return FALSE;
127✔
5268
    }
5269
    const double x01_mid = (x0 + x1) * dfInvScale;
187,794✔
5270
    const double x12_mid = (x1 + x2) * dfInvScale;
187,794✔
5271
    const double y01_mid = (y0 + y1) * dfInvScale;
187,794✔
5272
    const double y12_mid = (y1 + y2) * dfInvScale;
187,794✔
5273
    const double c01 = dx01 * x01_mid + dy01 * y01_mid;
187,794✔
5274
    const double c12 = dx12 * x12_mid + dy12 * y12_mid;
187,794✔
5275
    cx = 0.5 * dfScale * (c01 * dy12 - c12 * dy01) / det;
187,794✔
5276
    cy = 0.5 * dfScale * (-c01 * dx12 + c12 * dx01) / det;
187,794✔
5277

5278
    alpha0 = atan2((y0 - cy) * dfInvScale, (x0 - cx) * dfInvScale);
187,794✔
5279
    alpha1 = atan2((y1 - cy) * dfInvScale, (x1 - cx) * dfInvScale);
187,794✔
5280
    alpha2 = atan2((y2 - cy) * dfInvScale, (x2 - cx) * dfInvScale);
187,794✔
5281
    R = DISTANCE(cx, cy, x0, y0);
187,794✔
5282

5283
    // If det is negative, the orientation if clockwise.
5284
    if (det < 0)
187,794✔
5285
    {
5286
        if (alpha1 > alpha0)
92,947✔
5287
            alpha1 -= 2 * M_PI;
1,309✔
5288
        if (alpha2 > alpha1)
92,947✔
5289
            alpha2 -= 2 * M_PI;
3,321✔
5290
    }
5291
    else
5292
    {
5293
        if (alpha1 < alpha0)
94,847✔
5294
            alpha1 += 2 * M_PI;
1,344✔
5295
        if (alpha2 < alpha1)
94,847✔
5296
            alpha2 += 2 * M_PI;
3,266✔
5297
    }
5298

5299
    CPLAssert((alpha0 <= alpha1 && alpha1 <= alpha2) ||
187,794✔
5300
              (alpha0 >= alpha1 && alpha1 >= alpha2));
5301

5302
    return TRUE;
187,794✔
5303
}
5304

5305
/************************************************************************/
5306
/*                      OGRGeometryFactoryStrokeArc()                   */
5307
/************************************************************************/
5308

5309
static void OGRGeometryFactoryStrokeArc(OGRLineString *poLine, double cx,
4,324✔
5310
                                        double cy, double R, double z0,
5311
                                        double z1, int bHasZ, double alpha0,
5312
                                        double alpha1, double dfStep,
5313
                                        int bStealthConstraints)
5314
{
5315
    const int nSign = dfStep > 0 ? 1 : -1;
4,324✔
5316

5317
    // Constant angle between all points, so as to not depend on winding order.
5318
    const double dfNumSteps = fabs((alpha1 - alpha0) / dfStep) + 0.5;
4,324✔
5319
    if (dfNumSteps >= std::numeric_limits<int>::max() ||
4,324✔
5320
        dfNumSteps <= std::numeric_limits<int>::min() || std::isnan(dfNumSteps))
4,324✔
5321
    {
5322
        CPLError(CE_Warning, CPLE_AppDefined,
×
5323
                 "OGRGeometryFactoryStrokeArc: bogus steps: "
5324
                 "%lf %lf %lf %lf",
5325
                 alpha0, alpha1, dfStep, dfNumSteps);
5326
        return;
×
5327
    }
5328

5329
    int nSteps = static_cast<int>(dfNumSteps);
4,324✔
5330
    if (bStealthConstraints)
4,324✔
5331
    {
5332
        // We need at least 6 intermediate vertex, and if more additional
5333
        // multiples of 2.
5334
        if (nSteps < 1 + 6)
4,134✔
5335
            nSteps = 1 + 6;
99✔
5336
        else
5337
            nSteps = 1 + 6 + 2 * ((nSteps - (1 + 6) + (2 - 1)) / 2);
4,035✔
5338
    }
5339
    else if (nSteps < 4)
190✔
5340
    {
5341
        nSteps = 4;
186✔
5342
    }
5343
    dfStep = nSign * fabs((alpha1 - alpha0) / nSteps);
4,324✔
5344
    double alpha = alpha0 + dfStep;
4,324✔
5345

5346
    for (; (alpha - alpha1) * nSign < -1e-8; alpha += dfStep)
231,784✔
5347
    {
5348
        const double dfX = cx + R * cos(alpha);
227,460✔
5349
        const double dfY = cy + R * sin(alpha);
227,460✔
5350
        if (bHasZ)
227,460✔
5351
        {
5352
            const double z =
9,104✔
5353
                z0 + (z1 - z0) * (alpha - alpha0) / (alpha1 - alpha0);
9,104✔
5354
            poLine->addPoint(dfX, dfY, z);
9,104✔
5355
        }
5356
        else
5357
        {
5358
            poLine->addPoint(dfX, dfY);
218,356✔
5359
        }
5360
    }
5361
}
5362

5363
/************************************************************************/
5364
/*                         OGRGF_SetHiddenValue()                       */
5365
/************************************************************************/
5366

5367
// TODO(schwehr): Cleanup these static constants.
5368
constexpr int HIDDEN_ALPHA_WIDTH = 32;
5369
constexpr GUInt32 HIDDEN_ALPHA_SCALE =
5370
    static_cast<GUInt32>((static_cast<GUIntBig>(1) << HIDDEN_ALPHA_WIDTH) - 2);
5371
constexpr int HIDDEN_ALPHA_HALF_WIDTH = (HIDDEN_ALPHA_WIDTH / 2);
5372
constexpr int HIDDEN_ALPHA_HALF_MASK = (1 << HIDDEN_ALPHA_HALF_WIDTH) - 1;
5373

5374
// Encode 16-bit nValue in the 8-lsb of dfX and dfY.
5375

5376
#ifdef CPL_LSB
5377
constexpr int DOUBLE_LSB_OFFSET = 0;
5378
#else
5379
constexpr int DOUBLE_LSB_OFFSET = 7;
5380
#endif
5381

5382
static void OGRGF_SetHiddenValue(GUInt16 nValue, double &dfX, double &dfY)
227,326✔
5383
{
5384
    GByte abyData[8] = {};
227,326✔
5385

5386
    memcpy(abyData, &dfX, sizeof(double));
227,326✔
5387
    abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue & 0xFF);
227,326✔
5388
    memcpy(&dfX, abyData, sizeof(double));
227,326✔
5389

5390
    memcpy(abyData, &dfY, sizeof(double));
227,326✔
5391
    abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue >> 8);
227,326✔
5392
    memcpy(&dfY, abyData, sizeof(double));
227,326✔
5393
}
227,326✔
5394

5395
/************************************************************************/
5396
/*                         OGRGF_GetHiddenValue()                       */
5397
/************************************************************************/
5398

5399
// Decode 16-bit nValue from the 8-lsb of dfX and dfY.
5400
static GUInt16 OGRGF_GetHiddenValue(double dfX, double dfY)
182,926✔
5401
{
5402
    GByte abyData[8] = {};
182,926✔
5403
    memcpy(abyData, &dfX, sizeof(double));
182,926✔
5404
    GUInt16 nValue = abyData[DOUBLE_LSB_OFFSET];
182,926✔
5405
    memcpy(abyData, &dfY, sizeof(double));
182,926✔
5406
    nValue |= (abyData[DOUBLE_LSB_OFFSET] << 8);
182,926✔
5407

5408
    return nValue;
182,926✔
5409
}
5410

5411
/************************************************************************/
5412
/*                     OGRGF_NeedSwithArcOrder()                        */
5413
/************************************************************************/
5414

5415
// We need to define a full ordering between starting point and ending point
5416
// whatever it is.
5417
static bool OGRGF_NeedSwithArcOrder(double x0, double y0, double x2, double y2)
9,433✔
5418
{
5419
    return x0 < x2 || (x0 == x2 && y0 < y2);
9,433✔
5420
}
5421

5422
/************************************************************************/
5423
/*                         curveToLineString()                          */
5424
/************************************************************************/
5425

5426
/* clang-format off */
5427
/**
5428
 * \brief Converts an arc circle into an approximate line string
5429
 *
5430
 * The arc circle is defined by a first point, an intermediate point and a
5431
 * final point.
5432
 *
5433
 * The provided dfMaxAngleStepSizeDegrees is a hint. The discretization
5434
 * algorithm may pick a slightly different value.
5435
 *
5436
 * So as to avoid gaps when rendering curve polygons that share common arcs,
5437
 * this method is guaranteed to return a line with reversed vertex if called
5438
 * with inverted first and final point, and identical intermediate point.
5439
 *
5440
 * @param x0 x of first point
5441
 * @param y0 y of first point
5442
 * @param z0 z of first point
5443
 * @param x1 x of intermediate point
5444
 * @param y1 y of intermediate point
5445
 * @param z1 z of intermediate point
5446
 * @param x2 x of final point
5447
 * @param y2 y of final point
5448
 * @param z2 z of final point
5449
 * @param bHasZ TRUE if z must be taken into account
5450
 * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
5451
 * arc, zero to use the default setting.
5452
 * @param papszOptions options as a null-terminated list of strings or NULL.
5453
 * Recognized options:
5454
 * <ul>
5455
 * <li>ADD_INTERMEDIATE_POINT=STEALTH/YES/NO (Default to STEALTH).
5456
 *         Determine if and how the intermediate point must be output in the
5457
 *         linestring.  If set to STEALTH, no explicit intermediate point is
5458
 *         added but its properties are encoded in low significant bits of
5459
 *         intermediate points and OGRGeometryFactory::curveFromLineString() can
5460
 *         decode them.  This is the best compromise for round-tripping in OGR
5461
 *         and better results with PostGIS
5462
 *         <a href="http://postgis.org/docs/ST_LineToCurve.html">ST_LineToCurve()</a>.
5463
 *         If set to YES, the intermediate point is explicitly added to the
5464
 *         linestring. If set to NO, the intermediate point is not explicitly
5465
 *         added.
5466
 * </li>
5467
 * </ul>
5468
 *
5469
 * @return the converted geometry (ownership to caller).
5470
 *
5471
 * @since GDAL 2.0
5472
 */
5473
/* clang-format on */
5474

5475
OGRLineString *OGRGeometryFactory::curveToLineString(
6,358✔
5476
    double x0, double y0, double z0, double x1, double y1, double z1, double x2,
5477
    double y2, double z2, int bHasZ, double dfMaxAngleStepSizeDegrees,
5478
    const char *const *papszOptions)
5479
{
5480
    // So as to make sure the same curve followed in both direction results
5481
    // in perfectly(=binary identical) symmetrical points.
5482
    if (OGRGF_NeedSwithArcOrder(x0, y0, x2, y2))
6,358✔
5483
    {
5484
        OGRLineString *poLS =
5485
            curveToLineString(x2, y2, z2, x1, y1, z1, x0, y0, z0, bHasZ,
2,128✔
5486
                              dfMaxAngleStepSizeDegrees, papszOptions);
5487
        poLS->reversePoints();
2,128✔
5488
        return poLS;
2,128✔
5489
    }
5490

5491
    double R = 0.0;
4,230✔
5492
    double cx = 0.0;
4,230✔
5493
    double cy = 0.0;
4,230✔
5494
    double alpha0 = 0.0;
4,230✔
5495
    double alpha1 = 0.0;
4,230✔
5496
    double alpha2 = 0.0;
4,230✔
5497

5498
    OGRLineString *poLine = new OGRLineString();
4,230✔
5499
    bool bIsArc = true;
4,230✔
5500
    if (!GetCurveParameters(x0, y0, x1, y1, x2, y2, R, cx, cy, alpha0, alpha1,
4,230✔
5501
                            alpha2))
5502
    {
5503
        bIsArc = false;
93✔
5504
        cx = 0.0;
93✔
5505
        cy = 0.0;
93✔
5506
        R = 0.0;
93✔
5507
        alpha0 = 0.0;
93✔
5508
        alpha1 = 0.0;
93✔
5509
        alpha2 = 0.0;
93✔
5510
    }
5511

5512
    const int nSign = alpha1 >= alpha0 ? 1 : -1;
4,230✔
5513

5514
    // support default arc step setting.
5515
    if (dfMaxAngleStepSizeDegrees < 1e-6)
4,230✔
5516
    {
5517
        dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
4,211✔
5518
    }
5519

5520
    double dfStep = dfMaxAngleStepSizeDegrees / 180 * M_PI;
4,230✔
5521
    if (dfStep <= 0.01 / 180 * M_PI)
4,230✔
5522
    {
5523
        CPLDebug("OGR", "Too small arc step size: limiting to 0.01 degree.");
×
5524
        dfStep = 0.01 / 180 * M_PI;
×
5525
    }
5526

5527
    dfStep *= nSign;
4,230✔
5528

5529
    if (bHasZ)
4,230✔
5530
        poLine->addPoint(x0, y0, z0);
252✔
5531
    else
5532
        poLine->addPoint(x0, y0);
3,978✔
5533

5534
    bool bAddIntermediatePoint = false;
4,230✔
5535
    bool bStealth = true;
4,230✔
5536
    for (const char *const *papszIter = papszOptions; papszIter && *papszIter;
4,236✔
5537
         papszIter++)
5538
    {
5539
        char *pszKey = nullptr;
6✔
5540
        const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
6✔
5541
        if (pszKey != nullptr && EQUAL(pszKey, "ADD_INTERMEDIATE_POINT"))
6✔
5542
        {
5543
            if (EQUAL(pszValue, "YES") || EQUAL(pszValue, "TRUE") ||
4✔
5544
                EQUAL(pszValue, "ON"))
3✔
5545
            {
5546
                bAddIntermediatePoint = true;
1✔
5547
                bStealth = false;
1✔
5548
            }
5549
            else if (EQUAL(pszValue, "NO") || EQUAL(pszValue, "FALSE") ||
3✔
5550
                     EQUAL(pszValue, "OFF"))
1✔
5551
            {
5552
                bAddIntermediatePoint = false;
2✔
5553
                bStealth = false;
2✔
5554
            }
5555
            else if (EQUAL(pszValue, "STEALTH"))
5556
            {
5557
                // default.
5558
            }
5559
        }
5560
        else
5561
        {
5562
            CPLError(CE_Warning, CPLE_NotSupported, "Unsupported option: %s",
2✔
5563
                     *papszIter);
5564
        }
5565
        CPLFree(pszKey);
6✔
5566
    }
5567

5568
    if (!bIsArc || bAddIntermediatePoint)
4,230✔
5569
    {
5570
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z1, bHasZ, alpha0,
94✔
5571
                                    alpha1, dfStep, FALSE);
5572

5573
        if (bHasZ)
94✔
5574
            poLine->addPoint(x1, y1, z1);
25✔
5575
        else
5576
            poLine->addPoint(x1, y1);
69✔
5577

5578
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z1, z2, bHasZ, alpha1,
94✔
5579
                                    alpha2, dfStep, FALSE);
5580
    }
5581
    else
5582
    {
5583
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z2, bHasZ, alpha0,
4,136✔
5584
                                    alpha2, dfStep, bStealth);
5585

5586
        if (bStealth && poLine->getNumPoints() > 6)
4,136✔
5587
        {
5588
            // 'Hide' the angle of the intermediate point in the 8
5589
            // low-significant bits of the x, y of the first 2 computed points
5590
            // (so 32 bits), then put 0xFF, and on the last couple points put
5591
            // again the angle but in reverse order, so that overall the
5592
            // low-significant bits of all the points are symmetrical w.r.t the
5593
            // mid-point.
5594
            const double dfRatio = (alpha1 - alpha0) / (alpha2 - alpha0);
4,134✔
5595
            double dfAlphaRatio = 0.5 + HIDDEN_ALPHA_SCALE * dfRatio;
4,134✔
5596
            if (dfAlphaRatio < 0.0)
4,134✔
5597
            {
5598
                CPLError(CE_Warning, CPLE_AppDefined, "AlphaRation < 0: %lf",
×
5599
                         dfAlphaRatio);
5600
                dfAlphaRatio *= -1;
×
5601
            }
5602
            else if (dfAlphaRatio >= std::numeric_limits<GUInt32>::max() ||
8,268✔
5603
                     std::isnan(dfAlphaRatio))
4,134✔
5604
            {
5605
                CPLError(CE_Warning, CPLE_AppDefined,
×
5606
                         "AlphaRatio too large: %lf", dfAlphaRatio);
5607
                dfAlphaRatio = std::numeric_limits<GUInt32>::max();
×
5608
            }
5609
            const GUInt32 nAlphaRatio = static_cast<GUInt32>(dfAlphaRatio);
4,134✔
5610
            const GUInt16 nAlphaRatioLow = nAlphaRatio & HIDDEN_ALPHA_HALF_MASK;
4,134✔
5611
            const GUInt16 nAlphaRatioHigh =
4,134✔
5612
                nAlphaRatio >> HIDDEN_ALPHA_HALF_WIDTH;
4,134✔
5613
            // printf("alpha0=%f, alpha1=%f, alpha2=%f, dfRatio=%f, "/*ok*/
5614
            //        "nAlphaRatio = %u\n",
5615
            //        alpha0, alpha1, alpha2, dfRatio, nAlphaRatio);
5616

5617
            CPLAssert(((poLine->getNumPoints() - 1 - 6) % 2) == 0);
4,134✔
5618

5619
            for (int i = 1; i + 1 < poLine->getNumPoints(); i += 2)
117,797✔
5620
            {
5621
                GUInt16 nVal = 0xFFFF;
113,663✔
5622

5623
                double dfX = poLine->getX(i);
113,663✔
5624
                double dfY = poLine->getY(i);
113,663✔
5625
                if (i == 1)
113,663✔
5626
                    nVal = nAlphaRatioLow;
4,134✔
5627
                else if (i == poLine->getNumPoints() - 2)
109,529✔
5628
                    nVal = nAlphaRatioHigh;
4,134✔
5629
                OGRGF_SetHiddenValue(nVal, dfX, dfY);
113,663✔
5630
                poLine->setPoint(i, dfX, dfY);
113,663✔
5631

5632
                dfX = poLine->getX(i + 1);
113,663✔
5633
                dfY = poLine->getY(i + 1);
113,663✔
5634
                if (i == 1)
113,663✔
5635
                    nVal = nAlphaRatioHigh;
4,134✔
5636
                else if (i == poLine->getNumPoints() - 2)
109,529✔
5637
                    nVal = nAlphaRatioLow;
4,134✔
5638
                OGRGF_SetHiddenValue(nVal, dfX, dfY);
113,663✔
5639
                poLine->setPoint(i + 1, dfX, dfY);
113,663✔
5640
            }
5641
        }
5642
    }
5643

5644
    if (bHasZ)
4,230✔
5645
        poLine->addPoint(x2, y2, z2);
252✔
5646
    else
5647
        poLine->addPoint(x2, y2);
3,978✔
5648

5649
    return poLine;
4,230✔
5650
}
5651

5652
/************************************************************************/
5653
/*                         OGRGF_FixAngle()                             */
5654
/************************************************************************/
5655

5656
// Fix dfAngle by offsets of 2 PI so that it lies between dfAngleStart and
5657
// dfAngleStop, whatever their respective order.
5658
static double OGRGF_FixAngle(double dfAngleStart, double dfAngleStop,
181,709✔
5659
                             double dfAngle)
5660
{
5661
    if (dfAngleStart < dfAngleStop)
181,709✔
5662
    {
5663
        while (dfAngle <= dfAngleStart + 1e-8)
127,337✔
5664
            dfAngle += 2 * M_PI;
35,483✔
5665
    }
5666
    else
5667
    {
5668
        while (dfAngle >= dfAngleStart - 1e-8)
123,536✔
5669
            dfAngle -= 2 * M_PI;
33,681✔
5670
    }
5671
    return dfAngle;
181,709✔
5672
}
5673

5674
/************************************************************************/
5675
/*                         OGRGF_DetectArc()                            */
5676
/************************************************************************/
5677

5678
// #define VERBOSE_DEBUG_CURVEFROMLINESTRING
5679

5680
static inline bool IS_ALMOST_INTEGER(double x)
12,243✔
5681
{
5682
    const double val = fabs(x - floor(x + 0.5));
12,243✔
5683
    return val < 1.0e-8;
12,243✔
5684
}
5685

5686
static int OGRGF_DetectArc(const OGRLineString *poLS, int i,
3,476✔
5687
                           OGRCompoundCurve *&poCC, OGRCircularString *&poCS,
5688
                           OGRLineString *&poLSNew)
5689
{
5690
    if (i + 3 >= poLS->getNumPoints())
3,476✔
5691
        return -1;
305✔
5692

5693
    OGRPoint p0;
6,342✔
5694
    OGRPoint p1;
6,342✔
5695
    OGRPoint p2;
6,342✔
5696
    poLS->getPoint(i, &p0);
3,171✔
5697
    poLS->getPoint(i + 1, &p1);
3,171✔
5698
    poLS->getPoint(i + 2, &p2);
3,171✔
5699
    double R_1 = 0.0;
3,171✔
5700
    double cx_1 = 0.0;
3,171✔
5701
    double cy_1 = 0.0;
3,171✔
5702
    double alpha0_1 = 0.0;
3,171✔
5703
    double alpha1_1 = 0.0;
3,171✔
5704
    double alpha2_1 = 0.0;
3,171✔
5705
    if (!(OGRGeometryFactory::GetCurveParameters(
6,335✔
5706
              p0.getX(), p0.getY(), p1.getX(), p1.getY(), p2.getX(), p2.getY(),
5707
              R_1, cx_1, cy_1, alpha0_1, alpha1_1, alpha2_1) &&
5708
          fabs(alpha2_1 - alpha0_1) < 2.0 * 20.0 / 180.0 * M_PI))
3,164✔
5709
    {
5710
        return -1;
24✔
5711
    }
5712

5713
    const double dfDeltaAlpha10 = alpha1_1 - alpha0_1;
3,147✔
5714
    const double dfDeltaAlpha21 = alpha2_1 - alpha1_1;
3,147✔
5715
    const double dfMaxDeltaAlpha =
5716
        std::max(fabs(dfDeltaAlpha10), fabs(dfDeltaAlpha21));
3,147✔
5717
    GUInt32 nAlphaRatioRef =
5718
        OGRGF_GetHiddenValue(p1.getX(), p1.getY()) |
3,147✔
5719
        (OGRGF_GetHiddenValue(p2.getX(), p2.getY()) << HIDDEN_ALPHA_HALF_WIDTH);
3,147✔
5720
    bool bFoundFFFFFFFFPattern = false;
3,147✔
5721
    bool bFoundReversedAlphaRatioRef = false;
3,147✔
5722
    bool bValidAlphaRatio = nAlphaRatioRef > 0 && nAlphaRatioRef < 0xFFFFFFFF;
3,147✔
5723
    int nCountValidAlphaRatio = 1;
3,147✔
5724

5725
    double dfScale = std::max(1.0, R_1);
3,147✔
5726
    dfScale = std::max(dfScale, fabs(cx_1));
3,147✔
5727
    dfScale = std::max(dfScale, fabs(cy_1));
3,147✔
5728
    dfScale = pow(10.0, ceil(log10(dfScale)));
3,147✔
5729
    const double dfInvScale = 1.0 / dfScale;
3,147✔
5730

5731
    const int bInitialConstantStep =
3,147✔
5732
        (fabs(dfDeltaAlpha10 - dfDeltaAlpha21) / dfMaxDeltaAlpha) < 1.0e-4;
3,147✔
5733
    const double dfDeltaEpsilon =
3,147✔
5734
        bInitialConstantStep ? dfMaxDeltaAlpha * 1e-4 : dfMaxDeltaAlpha / 10;
3,147✔
5735

5736
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5737
    printf("----------------------------\n");             /*ok*/
5738
    printf("Curve beginning at offset i = %d\n", i);      /*ok*/
5739
    printf("Initial alpha ratio = %u\n", nAlphaRatioRef); /*ok*/
5740
    /*ok*/ printf("Initial R = %.16g, cx = %.16g, cy = %.16g\n", R_1, cx_1,
5741
                  cy_1);
5742
    printf("dfScale = %f\n", dfScale);   /*ok*/
5743
    printf("bInitialConstantStep = %d, " /*ok*/
5744
           "fabs(dfDeltaAlpha10 - dfDeltaAlpha21)=%.8g, "
5745
           "dfMaxDeltaAlpha = %.8f, "
5746
           "dfDeltaEpsilon = %.8f (%.8f)\n",
5747
           bInitialConstantStep, fabs(dfDeltaAlpha10 - dfDeltaAlpha21),
5748
           dfMaxDeltaAlpha, dfDeltaEpsilon, 1.0 / 180.0 * M_PI);
5749
#endif
5750
    int iMidPoint = -1;
3,147✔
5751
    double dfLastValidAlpha = alpha2_1;
3,147✔
5752

5753
    double dfLastLogRelDiff = 0;
3,147✔
5754

5755
    OGRPoint p3;
6,294✔
5756
    int j = i + 1;  // Used after for.
3,147✔
5757
    for (; j + 2 < poLS->getNumPoints(); j++)
183,299✔
5758
    {
5759
        poLS->getPoint(j, &p1);
180,251✔
5760
        poLS->getPoint(j + 1, &p2);
180,251✔
5761
        poLS->getPoint(j + 2, &p3);
180,251✔
5762
        double R_2 = 0.0;
180,251✔
5763
        double cx_2 = 0.0;
180,251✔
5764
        double cy_2 = 0.0;
180,251✔
5765
        double alpha0_2 = 0.0;
180,251✔
5766
        double alpha1_2 = 0.0;
180,251✔
5767
        double alpha2_2 = 0.0;
180,251✔
5768
        // Check that the new candidate arc shares the same
5769
        // radius, center and winding order.
5770
        if (!(OGRGeometryFactory::GetCurveParameters(
180,251✔
5771
                p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(),
5772
                p3.getY(), R_2, cx_2, cy_2, alpha0_2, alpha1_2, alpha2_2)))
5773
        {
5774
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5775
            printf("End of curve at j=%d\n : straight line", j); /*ok*/
5776
#endif
5777
            break;
99✔
5778
        }
5779

5780
        const double dfRelDiffR = fabs(R_1 - R_2) * dfInvScale;
180,243✔
5781
        const double dfRelDiffCx = fabs(cx_1 - cx_2) * dfInvScale;
180,243✔
5782
        const double dfRelDiffCy = fabs(cy_1 - cy_2) * dfInvScale;
180,243✔
5783

5784
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5785
        printf("j=%d: R = %.16g, cx = %.16g, cy = %.16g, " /*ok*/
5786
               "rel_diff_R=%.8g rel_diff_cx=%.8g rel_diff_cy=%.8g\n",
5787
               j, R_2, cx_2, cy_2, dfRelDiffR, dfRelDiffCx, dfRelDiffCy);
5788
#endif
5789

5790
        if (dfRelDiffR > 1.0e-7 || dfRelDiffCx > 1.0e-7 ||
180,243✔
5791
            dfRelDiffCy > 1.0e-7 ||
180,174✔
5792
            dfDeltaAlpha10 * (alpha1_2 - alpha0_2) < 0.0)
180,174✔
5793
        {
5794
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5795
            printf("End of curve at j=%d\n", j); /*ok*/
5796
#endif
5797
            break;
5798
        }
5799

5800
        if (dfRelDiffR > 0.0 && dfRelDiffCx > 0.0 && dfRelDiffCy > 0.0)
180,174✔
5801
        {
5802
            const double dfLogRelDiff = std::min(
5803
                std::min(fabs(log10(dfRelDiffR)), fabs(log10(dfRelDiffCx))),
360,326✔
5804
                fabs(log10(dfRelDiffCy)));
180,163✔
5805
            // printf("dfLogRelDiff = %f, dfLastLogRelDiff=%f, "/*ok*/
5806
            //        "dfLogRelDiff - dfLastLogRelDiff=%f\n",
5807
            //         dfLogRelDiff, dfLastLogRelDiff,
5808
            //         dfLogRelDiff - dfLastLogRelDiff);
5809
            if (dfLogRelDiff > 0.0 && dfLastLogRelDiff >= 8.0 &&
180,163✔
5810
                dfLogRelDiff <= 8.0 && dfLogRelDiff < dfLastLogRelDiff - 2.0)
2✔
5811
            {
5812
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5813
                printf("End of curve at j=%d. Significant different in " /*ok*/
5814
                       "relative error w.r.t previous points\n",
5815
                       j);
5816
#endif
5817
                break;
2✔
5818
            }
5819
            dfLastLogRelDiff = dfLogRelDiff;
180,161✔
5820
        }
5821

5822
        const double dfStep10 = fabs(alpha1_2 - alpha0_2);
180,172✔
5823
        const double dfStep21 = fabs(alpha2_2 - alpha1_2);
180,172✔
5824
        // Check that the angle step is consistent with the original step.
5825
        if (!(dfStep10 < 2.0 * dfMaxDeltaAlpha &&
180,172✔
5826
              dfStep21 < 2.0 * dfMaxDeltaAlpha))
180,172✔
5827
        {
5828
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5829
            printf("End of curve at j=%d: dfStep10=%f, dfStep21=%f, " /*ok*/
5830
                   "2*dfMaxDeltaAlpha=%f\n",
5831
                   j, dfStep10, dfStep21, 2 * dfMaxDeltaAlpha);
5832
#endif
5833
            break;
5834
        }
5835

5836
        if (bValidAlphaRatio && j > i + 1 && (i % 2) != (j % 2))
180,171✔
5837
        {
5838
            const GUInt32 nAlphaRatioReversed =
5839
                (OGRGF_GetHiddenValue(p1.getX(), p1.getY())
88,316✔
5840
                 << HIDDEN_ALPHA_HALF_WIDTH) |
176,632✔
5841
                (OGRGF_GetHiddenValue(p2.getX(), p2.getY()));
88,316✔
5842
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5843
            printf("j=%d, nAlphaRatioReversed = %u\n", /*ok*/
5844
                   j, nAlphaRatioReversed);
5845
#endif
5846
            if (!bFoundFFFFFFFFPattern && nAlphaRatioReversed == 0xFFFFFFFF)
88,316✔
5847
            {
5848
                bFoundFFFFFFFFPattern = true;
3,075✔
5849
                nCountValidAlphaRatio++;
3,075✔
5850
            }
5851
            else if (bFoundFFFFFFFFPattern && !bFoundReversedAlphaRatioRef &&
85,241✔
5852
                     nAlphaRatioReversed == 0xFFFFFFFF)
5853
            {
5854
                nCountValidAlphaRatio++;
82,139✔
5855
            }
5856
            else if (bFoundFFFFFFFFPattern && !bFoundReversedAlphaRatioRef &&
3,102✔
5857
                     nAlphaRatioReversed == nAlphaRatioRef)
5858
            {
5859
                bFoundReversedAlphaRatioRef = true;
3,075✔
5860
                nCountValidAlphaRatio++;
3,075✔
5861
            }
5862
            else
5863
            {
5864
                if (bInitialConstantStep &&
27✔
5865
                    fabs(dfLastValidAlpha - alpha0_1) >= M_PI &&
26✔
5866
                    nCountValidAlphaRatio > 10)
5867
                {
5868
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5869
                    printf("End of curve at j=%d: " /*ok*/
5870
                           "fabs(dfLastValidAlpha - alpha0_1)=%f, "
5871
                           "nCountValidAlphaRatio=%d\n",
5872
                           j, fabs(dfLastValidAlpha - alpha0_1),
5873
                           nCountValidAlphaRatio);
5874
#endif
5875
                    if (dfLastValidAlpha - alpha0_1 > 0)
19✔
5876
                    {
5877
                        while (dfLastValidAlpha - alpha0_1 - dfMaxDeltaAlpha -
21✔
5878
                                   M_PI >
14✔
5879
                               -dfMaxDeltaAlpha / 10)
14✔
5880
                        {
5881
                            dfLastValidAlpha -= dfMaxDeltaAlpha;
7✔
5882
                            j--;
7✔
5883
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5884
                            printf(/*ok*/
5885
                                   "--> corrected as fabs(dfLastValidAlpha - "
5886
                                   "alpha0_1)=%f, j=%d\n",
5887
                                   fabs(dfLastValidAlpha - alpha0_1), j);
5888
#endif
5889
                        }
5890
                    }
5891
                    else
5892
                    {
5893
                        while (dfLastValidAlpha - alpha0_1 + dfMaxDeltaAlpha +
36✔
5894
                                   M_PI <
24✔
5895
                               dfMaxDeltaAlpha / 10)
24✔
5896
                        {
5897
                            dfLastValidAlpha += dfMaxDeltaAlpha;
12✔
5898
                            j--;
12✔
5899
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5900
                            printf(/*ok*/
5901
                                   "--> corrected as fabs(dfLastValidAlpha - "
5902
                                   "alpha0_1)=%f, j=%d\n",
5903
                                   fabs(dfLastValidAlpha - alpha0_1), j);
5904
#endif
5905
                        }
5906
                    }
5907
                    poLS->getPoint(j + 1, &p2);
19✔
5908
                    break;
19✔
5909
                }
5910

5911
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5912
                printf("j=%d, nAlphaRatioReversed = %u --> inconsistent " /*ok*/
5913
                       "values across arc. Don't use it\n",
5914
                       j, nAlphaRatioReversed);
5915
#endif
5916
                bValidAlphaRatio = false;
8✔
5917
            }
5918
        }
5919

5920
        // Correct current end angle, consistently with start angle.
5921
        dfLastValidAlpha = OGRGF_FixAngle(alpha0_1, alpha1_1, alpha2_2);
180,152✔
5922

5923
        // Try to detect the precise intermediate point of the
5924
        // arc circle by detecting irregular angle step
5925
        // This is OK if we don't detect the right point or fail
5926
        // to detect it.
5927
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5928
        printf("j=%d A(0,1)-maxDelta=%.8f A(1,2)-maxDelta=%.8f " /*ok*/
5929
               "x1=%.8f y1=%.8f x2=%.8f y2=%.8f x3=%.8f y3=%.8f\n",
5930
               j, fabs(dfStep10 - dfMaxDeltaAlpha),
5931
               fabs(dfStep21 - dfMaxDeltaAlpha), p1.getX(), p1.getY(),
5932
               p2.getX(), p2.getY(), p3.getX(), p3.getY());
5933
#endif
5934
        if (j > i + 1 && iMidPoint < 0 && dfDeltaEpsilon < 1.0 / 180.0 * M_PI)
180,152✔
5935
        {
5936
            if (fabs(dfStep10 - dfMaxDeltaAlpha) > dfDeltaEpsilon)
176,673✔
5937
                iMidPoint = j + ((bInitialConstantStep) ? 0 : 1);
8✔
5938
            else if (fabs(dfStep21 - dfMaxDeltaAlpha) > dfDeltaEpsilon)
176,665✔
5939
                iMidPoint = j + ((bInitialConstantStep) ? 1 : 2);
4✔
5940
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5941
            if (iMidPoint >= 0)
5942
            {
5943
                OGRPoint pMid;
5944
                poLS->getPoint(iMidPoint, &pMid);
5945
                printf("Midpoint detected at j = %d, iMidPoint = %d, " /*ok*/
5946
                       "x=%.8f y=%.8f\n",
5947
                       j, iMidPoint, pMid.getX(), pMid.getY());
5948
            }
5949
#endif
5950
        }
5951
    }
5952

5953
    // Take a minimum threshold of consecutive points
5954
    // on the arc to avoid false positives.
5955
    if (j < i + 3)
3,147✔
5956
        return -1;
61✔
5957

5958
    bValidAlphaRatio &= bFoundFFFFFFFFPattern && bFoundReversedAlphaRatioRef;
3,086✔
5959

5960
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5961
    printf("bValidAlphaRatio=%d bFoundFFFFFFFFPattern=%d, " /*ok*/
5962
           "bFoundReversedAlphaRatioRef=%d\n",
5963
           static_cast<int>(bValidAlphaRatio),
5964
           static_cast<int>(bFoundFFFFFFFFPattern),
5965
           static_cast<int>(bFoundReversedAlphaRatioRef));
5966
    printf("alpha0_1=%f dfLastValidAlpha=%f\n", /*ok*/
5967
           alpha0_1, dfLastValidAlpha);
5968
#endif
5969

5970
    if (poLSNew != nullptr)
3,086✔
5971
    {
5972
        double dfScale2 = std::max(1.0, fabs(p0.getX()));
11✔
5973
        dfScale2 = std::max(dfScale2, fabs(p0.getY()));
11✔
5974
        // Not strictly necessary, but helps having 'clean' lines without
5975
        // duplicated points.
5976
        constexpr double dfToleranceEps =
11✔
5977
            OGRCompoundCurve::DEFAULT_TOLERANCE_EPSILON;
5978
        if (fabs(poLSNew->getX(poLSNew->getNumPoints() - 1) - p0.getX()) >
11✔
5979
                dfToleranceEps * dfScale2 ||
12✔
5980
            fabs(poLSNew->getY(poLSNew->getNumPoints() - 1) - p0.getY()) >
1✔
5981
                dfToleranceEps * dfScale2)
1✔
5982
            poLSNew->addPoint(&p0);
10✔
5983
        if (poLSNew->getNumPoints() >= 2)
11✔
5984
        {
5985
            if (poCC == nullptr)
10✔
5986
                poCC = new OGRCompoundCurve();
3✔
5987
            poCC->addCurveDirectly(poLSNew);
10✔
5988
        }
5989
        else
5990
            delete poLSNew;
1✔
5991
        poLSNew = nullptr;
11✔
5992
    }
5993

5994
    if (poCS == nullptr)
3,086✔
5995
    {
5996
        poCS = new OGRCircularString();
3,062✔
5997
        poCS->addPoint(&p0);
3,062✔
5998
    }
5999

6000
    OGRPoint *poFinalPoint = (j + 2 >= poLS->getNumPoints()) ? &p3 : &p2;
3,086✔
6001

6002
    double dfXMid = 0.0;
3,086✔
6003
    double dfYMid = 0.0;
3,086✔
6004
    double dfZMid = 0.0;
3,086✔
6005
    if (bValidAlphaRatio)
3,086✔
6006
    {
6007
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6008
        printf("Using alpha ratio...\n"); /*ok*/
6009
#endif
6010
        double dfAlphaMid = 0.0;
3,075✔
6011
        if (OGRGF_NeedSwithArcOrder(p0.getX(), p0.getY(), poFinalPoint->getX(),
3,075✔
6012
                                    poFinalPoint->getY()))
6013
        {
6014
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6015
            printf("Switching angles\n"); /*ok*/
6016
#endif
6017
            dfAlphaMid = dfLastValidAlpha + nAlphaRatioRef *
1,509✔
6018
                                                (alpha0_1 - dfLastValidAlpha) /
1,509✔
6019
                                                HIDDEN_ALPHA_SCALE;
6020
            dfAlphaMid = OGRGF_FixAngle(alpha0_1, dfLastValidAlpha, dfAlphaMid);
1,509✔
6021
        }
6022
        else
6023
        {
6024
            dfAlphaMid = alpha0_1 + nAlphaRatioRef *
1,566✔
6025
                                        (dfLastValidAlpha - alpha0_1) /
1,566✔
6026
                                        HIDDEN_ALPHA_SCALE;
6027
        }
6028

6029
        dfXMid = cx_1 + R_1 * cos(dfAlphaMid);
3,075✔
6030
        dfYMid = cy_1 + R_1 * sin(dfAlphaMid);
3,075✔
6031

6032
        if (poLS->getCoordinateDimension() == 3)
3,075✔
6033
        {
6034
            double dfLastAlpha = 0.0;
2✔
6035
            double dfLastZ = 0.0;
2✔
6036
            int k = i;  // Used after for.
2✔
6037
            for (; k < j + 2; k++)
48✔
6038
            {
6039
                OGRPoint p;
48✔
6040
                poLS->getPoint(k, &p);
48✔
6041
                double dfAlpha = atan2(p.getY() - cy_1, p.getX() - cx_1);
48✔
6042
                dfAlpha = OGRGF_FixAngle(alpha0_1, dfLastValidAlpha, dfAlpha);
48✔
6043
                if (k > i &&
48✔
6044
                    ((dfAlpha < dfLastValidAlpha && dfAlphaMid < dfAlpha) ||
46✔
6045
                     (dfAlpha > dfLastValidAlpha && dfAlphaMid > dfAlpha)))
23✔
6046
                {
6047
                    const double dfRatio =
2✔
6048
                        (dfAlphaMid - dfLastAlpha) / (dfAlpha - dfLastAlpha);
2✔
6049
                    dfZMid = (1 - dfRatio) * dfLastZ + dfRatio * p.getZ();
2✔
6050
                    break;
2✔
6051
                }
6052
                dfLastAlpha = dfAlpha;
46✔
6053
                dfLastZ = p.getZ();
46✔
6054
            }
6055
            if (k == j + 2)
2✔
6056
                dfZMid = dfLastZ;
×
6057
            if (IS_ALMOST_INTEGER(dfZMid))
2✔
6058
                dfZMid = static_cast<int>(floor(dfZMid + 0.5));
2✔
6059
        }
6060

6061
        // A few rounding strategies in case the mid point was at "exact"
6062
        // coordinates.
6063
        if (R_1 > 1e-5)
3,075✔
6064
        {
6065
            const bool bStartEndInteger =
6066
                IS_ALMOST_INTEGER(p0.getX()) && IS_ALMOST_INTEGER(p0.getY()) &&
9,185✔
6067
                IS_ALMOST_INTEGER(poFinalPoint->getX()) &&
9,185✔
6068
                IS_ALMOST_INTEGER(poFinalPoint->getY());
3,056✔
6069
            if (bStartEndInteger &&
3,069✔
6070
                fabs(dfXMid - floor(dfXMid + 0.5)) / dfScale < 1e-4 &&
3,056✔
6071
                fabs(dfYMid - floor(dfYMid + 0.5)) / dfScale < 1e-4)
3,037✔
6072
            {
6073
                dfXMid = static_cast<int>(floor(dfXMid + 0.5));
3,037✔
6074
                dfYMid = static_cast<int>(floor(dfYMid + 0.5));
3,037✔
6075
                // Sometimes rounding to closest is not best approach
6076
                // Try neighbouring integers to look for the one that
6077
                // minimize the error w.r.t to the arc center
6078
                // But only do that if the radius is greater than
6079
                // the magnitude of the delta that we will try!
6080
                double dfBestRError =
6081
                    fabs(R_1 - DISTANCE(dfXMid, dfYMid, cx_1, cy_1));
3,037✔
6082
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6083
                printf("initial_error=%f\n", dfBestRError); /*ok*/
6084
#endif
6085
                int iBestX = 0;
3,037✔
6086
                int iBestY = 0;
3,037✔
6087
                if (dfBestRError > 0.001 && R_1 > 2)
3,037✔
6088
                {
6089
                    int nSearchRadius = 1;
3✔
6090
                    // Extend the search radius if the arc circle radius
6091
                    // is much higher than the coordinate values.
6092
                    double dfMaxCoords =
6093
                        std::max(fabs(p0.getX()), fabs(p0.getY()));
3✔
6094
                    dfMaxCoords = std::max(dfMaxCoords, poFinalPoint->getX());
3✔
6095
                    dfMaxCoords = std::max(dfMaxCoords, poFinalPoint->getY());
3✔
6096
                    dfMaxCoords = std::max(dfMaxCoords, dfXMid);
3✔
6097
                    dfMaxCoords = std::max(dfMaxCoords, dfYMid);
3✔
6098
                    if (R_1 > dfMaxCoords * 1000)
3✔
6099
                        nSearchRadius = 100;
3✔
6100
                    else if (R_1 > dfMaxCoords * 10)
×
6101
                        nSearchRadius = 10;
×
6102
                    for (int iY = -nSearchRadius; iY <= nSearchRadius; iY++)
606✔
6103
                    {
6104
                        for (int iX = -nSearchRadius; iX <= nSearchRadius; iX++)
121,806✔
6105
                        {
6106
                            const double dfCandidateX = dfXMid + iX;
121,203✔
6107
                            const double dfCandidateY = dfYMid + iY;
121,203✔
6108
                            if (fabs(dfCandidateX - p0.getX()) < 1e-8 &&
121,203✔
6109
                                fabs(dfCandidateY - p0.getY()) < 1e-8)
×
6110
                                continue;
×
6111
                            if (fabs(dfCandidateX - poFinalPoint->getX()) <
121,203✔
6112
                                    1e-8 &&
121,203✔
6113
                                fabs(dfCandidateY - poFinalPoint->getY()) <
×
6114
                                    1e-8)
6115
                                continue;
×
6116
                            const double dfRError =
6117
                                fabs(R_1 - DISTANCE(dfCandidateX, dfCandidateY,
121,203✔
6118
                                                    cx_1, cy_1));
121,203✔
6119
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6120
                            printf("x=%d y=%d error=%f besterror=%f\n", /*ok*/
6121
                                   static_cast<int>(dfXMid + iX),
6122
                                   static_cast<int>(dfYMid + iY), dfRError,
6123
                                   dfBestRError);
6124
#endif
6125
                            if (dfRError < dfBestRError)
121,203✔
6126
                            {
6127
                                iBestX = iX;
20✔
6128
                                iBestY = iY;
20✔
6129
                                dfBestRError = dfRError;
20✔
6130
                            }
6131
                        }
6132
                    }
6133
                }
6134
                dfXMid += iBestX;
3,037✔
6135
                dfYMid += iBestY;
3,037✔
6136
            }
6137
            else
6138
            {
6139
                // Limit the number of significant figures in decimal
6140
                // representation.
6141
                if (fabs(dfXMid) < 100000000.0)
32✔
6142
                {
6143
                    dfXMid =
32✔
6144
                        static_cast<GIntBig>(floor(dfXMid * 100000000 + 0.5)) /
32✔
6145
                        100000000.0;
6146
                }
6147
                if (fabs(dfYMid) < 100000000.0)
32✔
6148
                {
6149
                    dfYMid =
32✔
6150
                        static_cast<GIntBig>(floor(dfYMid * 100000000 + 0.5)) /
32✔
6151
                        100000000.0;
6152
                }
6153
            }
6154
        }
6155

6156
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6157
        printf("dfAlphaMid=%f, x_mid = %f, y_mid = %f\n", /*ok*/
6158
               dfLastValidAlpha, dfXMid, dfYMid);
6159
#endif
6160
    }
6161

6162
    // If this is a full circle of a non-polygonal zone, we must
6163
    // use a 5-point representation to keep the winding order.
6164
    if (p0.Equals(poFinalPoint) &&
3,097✔
6165
        !EQUAL(poLS->getGeometryName(), "LINEARRING"))
11✔
6166
    {
6167
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6168
        printf("Full circle of a non-polygonal zone\n"); /*ok*/
6169
#endif
6170
        poLS->getPoint((i + j + 2) / 4, &p1);
1✔
6171
        poCS->addPoint(&p1);
1✔
6172
        if (bValidAlphaRatio)
1✔
6173
        {
6174
            p1.setX(dfXMid);
1✔
6175
            p1.setY(dfYMid);
1✔
6176
            if (poLS->getCoordinateDimension() == 3)
1✔
6177
                p1.setZ(dfZMid);
×
6178
        }
6179
        else
6180
        {
6181
            poLS->getPoint((i + j + 1) / 2, &p1);
×
6182
        }
6183
        poCS->addPoint(&p1);
1✔
6184
        poLS->getPoint(3 * (i + j + 2) / 4, &p1);
1✔
6185
        poCS->addPoint(&p1);
1✔
6186
    }
6187

6188
    else if (bValidAlphaRatio)
3,085✔
6189
    {
6190
        p1.setX(dfXMid);
3,074✔
6191
        p1.setY(dfYMid);
3,074✔
6192
        if (poLS->getCoordinateDimension() == 3)
3,074✔
6193
            p1.setZ(dfZMid);
2✔
6194
        poCS->addPoint(&p1);
3,074✔
6195
    }
6196

6197
    // If we have found a candidate for a precise intermediate
6198
    // point, use it.
6199
    else if (iMidPoint >= 1 && iMidPoint < j)
11✔
6200
    {
6201
        poLS->getPoint(iMidPoint, &p1);
3✔
6202
        poCS->addPoint(&p1);
3✔
6203
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6204
        printf("Using detected midpoint...\n");                   /*ok*/
6205
        printf("x_mid = %f, y_mid = %f\n", p1.getX(), p1.getY()); /*ok*/
6206
#endif
6207
    }
6208
    // Otherwise pick up the mid point between both extremities.
6209
    else
6210
    {
6211
        poLS->getPoint((i + j + 1) / 2, &p1);
8✔
6212
        poCS->addPoint(&p1);
8✔
6213
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6214
        printf("Pickup 'random' midpoint at index=%d...\n", /*ok*/
6215
               (i + j + 1) / 2);
6216
        printf("x_mid = %f, y_mid = %f\n", p1.getX(), p1.getY()); /*ok*/
6217
#endif
6218
    }
6219
    poCS->addPoint(poFinalPoint);
3,086✔
6220

6221
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6222
    printf("----------------------------\n"); /*ok*/
6223
#endif
6224

6225
    if (j + 2 >= poLS->getNumPoints())
3,086✔
6226
        return -2;
3,048✔
6227
    return j + 1;
38✔
6228
}
6229

6230
/************************************************************************/
6231
/*                        curveFromLineString()                         */
6232
/************************************************************************/
6233

6234
/**
6235
 * \brief Try to convert a linestring approximating curves into a curve.
6236
 *
6237
 * This method can return a COMPOUNDCURVE, a CIRCULARSTRING or a LINESTRING.
6238
 *
6239
 * This method is the reverse of curveFromLineString().
6240
 *
6241
 * @param poLS handle to the geometry to convert.
6242
 * @param papszOptions options as a null-terminated list of strings.
6243
 *                     Unused for now. Must be set to NULL.
6244
 *
6245
 * @return the converted geometry (ownership to caller).
6246
 *
6247
 * @since GDAL 2.0
6248
 */
6249

6250
OGRCurve *OGRGeometryFactory::curveFromLineString(
3,198✔
6251
    const OGRLineString *poLS, CPL_UNUSED const char *const *papszOptions)
6252
{
6253
    OGRCompoundCurve *poCC = nullptr;
3,198✔
6254
    OGRCircularString *poCS = nullptr;
3,198✔
6255
    OGRLineString *poLSNew = nullptr;
3,198✔
6256
    const int nLSNumPoints = poLS->getNumPoints();
3,198✔
6257
    const bool bIsClosed = nLSNumPoints >= 4 && poLS->get_IsClosed();
3,198✔
6258
    for (int i = 0; i < nLSNumPoints; /* nothing */)
3,626✔
6259
    {
6260
        const int iNewI = OGRGF_DetectArc(poLS, i, poCC, poCS, poLSNew);
3,476✔
6261
        if (iNewI == -2)
3,476✔
6262
            break;
3,048✔
6263
        if (iNewI >= 0)
428✔
6264
        {
6265
            i = iNewI;
38✔
6266
            continue;
38✔
6267
        }
6268

6269
        if (poCS != nullptr)
390✔
6270
        {
6271
            if (poCC == nullptr)
14✔
6272
                poCC = new OGRCompoundCurve();
5✔
6273
            poCC->addCurveDirectly(poCS);
14✔
6274
            poCS = nullptr;
14✔
6275
        }
6276

6277
        OGRPoint p;
390✔
6278
        poLS->getPoint(i, &p);
390✔
6279
        if (poLSNew == nullptr)
390✔
6280
        {
6281
            poLSNew = new OGRLineString();
160✔
6282
            poLSNew->addPoint(&p);
160✔
6283
        }
6284
        // Not strictly necessary, but helps having 'clean' lines without
6285
        // duplicated points.
6286
        else
6287
        {
6288
            double dfScale = std::max(1.0, fabs(p.getX()));
230✔
6289
            dfScale = std::max(dfScale, fabs(p.getY()));
230✔
6290
            if (bIsClosed && i == nLSNumPoints - 1)
230✔
6291
                dfScale = 0;
7✔
6292
            constexpr double dfToleranceEps =
230✔
6293
                OGRCompoundCurve::DEFAULT_TOLERANCE_EPSILON;
6294
            if (fabs(poLSNew->getX(poLSNew->getNumPoints() - 1) - p.getX()) >
230✔
6295
                    dfToleranceEps * dfScale ||
239✔
6296
                fabs(poLSNew->getY(poLSNew->getNumPoints() - 1) - p.getY()) >
9✔
6297
                    dfToleranceEps * dfScale)
9✔
6298
            {
6299
                poLSNew->addPoint(&p);
229✔
6300
            }
6301
        }
6302

6303
        i++;
390✔
6304
    }
6305

6306
    OGRCurve *poRet = nullptr;
3,198✔
6307

6308
    if (poLSNew != nullptr && poLSNew->getNumPoints() < 2)
3,198✔
6309
    {
6310
        delete poLSNew;
1✔
6311
        poLSNew = nullptr;
1✔
6312
        if (poCC != nullptr)
1✔
6313
        {
6314
            if (poCC->getNumCurves() == 1)
1✔
6315
            {
6316
                poRet = poCC->stealCurve(0);
1✔
6317
                delete poCC;
1✔
6318
                poCC = nullptr;
1✔
6319
            }
6320
            else
6321
                poRet = poCC;
×
6322
        }
6323
        else
6324
            poRet = poLS->clone();
×
6325
    }
6326
    else if (poCC != nullptr)
3,197✔
6327
    {
6328
        if (poLSNew)
7✔
6329
            poCC->addCurveDirectly(poLSNew);
6✔
6330
        else
6331
            poCC->addCurveDirectly(poCS);
1✔
6332
        poRet = poCC;
7✔
6333
    }
6334
    else if (poLSNew != nullptr)
3,190✔
6335
        poRet = poLSNew;
142✔
6336
    else if (poCS != nullptr)
3,048✔
6337
        poRet = poCS;
3,047✔
6338
    else
6339
        poRet = poLS->clone();
1✔
6340

6341
    poRet->assignSpatialReference(poLS->getSpatialReference());
3,198✔
6342

6343
    return poRet;
3,198✔
6344
}
6345

6346
/************************************************************************/
6347
/*                   createFromGeoJson( const char* )                   */
6348
/************************************************************************/
6349

6350
/**
6351
 * @brief Create geometry from GeoJson fragment.
6352
 * @param pszJsonString The GeoJSON fragment for the geometry.
6353
 * @param nSize (new in GDAL 3.4) Optional length of the string
6354
 *              if it is not null-terminated
6355
 * @return a geometry on success, or NULL on error.
6356
 * @since GDAL 2.3
6357
 */
6358
OGRGeometry *OGRGeometryFactory::createFromGeoJson(const char *pszJsonString,
5✔
6359
                                                   int nSize)
6360
{
6361
    CPLJSONDocument oDocument;
10✔
6362
    if (!oDocument.LoadMemory(reinterpret_cast<const GByte *>(pszJsonString),
5✔
6363
                              nSize))
6364
    {
6365
        return nullptr;
3✔
6366
    }
6367

6368
    return createFromGeoJson(oDocument.GetRoot());
2✔
6369
}
6370

6371
/************************************************************************/
6372
/*              createFromGeoJson( const CPLJSONObject& )               */
6373
/************************************************************************/
6374

6375
/**
6376
 * @brief Create geometry from GeoJson fragment.
6377
 * @param oJsonObject The JSONObject class describes the GeoJSON geometry.
6378
 * @return a geometry on success, or NULL on error.
6379
 * @since GDAL 2.3
6380
 */
6381
OGRGeometry *
6382
OGRGeometryFactory::createFromGeoJson(const CPLJSONObject &oJsonObject)
2✔
6383
{
6384
    if (!oJsonObject.IsValid())
2✔
6385
    {
6386
        return nullptr;
×
6387
    }
6388

6389
    // TODO: Move from GeoJSON driver functions create geometry here, and
6390
    // replace json-c specific json_object to CPLJSONObject
6391
    return OGRGeoJSONReadGeometry(
2✔
6392
        static_cast<json_object *>(oJsonObject.GetInternalHandle()));
4✔
6393
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc