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

OSGeo / gdal / 15721865619

18 Jun 2025 01:40AM UTC coverage: 71.065% (-0.001%) from 71.066%
15721865619

Pull #12592

github

web-flow
Merge 924ef7459 into dec8c71bd
Pull Request #12592: VRTDerivedBand expressions: Add _CENTER_X_ and _CENTER_Y_ built-in variables

32 of 34 new or added lines in 2 files covered. (94.12%)

67 existing lines in 32 files now uncovered.

571764 of 804570 relevant lines covered (71.06%)

251330.6 hits per line

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

91.46
/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,086✔
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,062✔
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,062✔
142
    nBytesConsumedOut = 0;
98,062✔
143
    *ppoReturn = nullptr;
98,062✔
144

145
    if (nBytes < 9 && nBytes != static_cast<size_t>(-1))
98,062✔
146
        return OGRERR_NOT_ENOUGH_DATA;
1,393✔
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,669✔
153
    if (nByteOrder != wkbXDR && nByteOrder != wkbNDR)
96,669✔
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,376✔
171
    const OGRErr err =
172
        OGRReadWKBGeometryType(l_pabyData, eWkbVariant, &eGeometryType);
96,376✔
173

174
    if (err != OGRERR_NONE)
96,376✔
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,813✔
182

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

186
    /* -------------------------------------------------------------------- */
187
    /*      Import from binary.                                             */
188
    /* -------------------------------------------------------------------- */
189
    const OGRErr eErr = poGeom->importFromWkb(l_pabyData, nBytes, eWkbVariant,
191,626✔
190
                                              nBytesConsumedOut);
95,813✔
191
    if (eErr != OGRERR_NONE)
95,813✔
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,059✔
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,498✔
209
    *ppoReturn = poGeom;
88,498✔
210

211
    return OGRERR_NONE;
88,498✔
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,538✔
340
                                         const OGRSpatialReference *poSR,
341
                                         OGRGeometry **ppoReturn)
342

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

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

354
    /* -------------------------------------------------------------------- */
355
    /*      Instantiate a geometry of the appropriate type.                 */
356
    /* -------------------------------------------------------------------- */
357
    OGRGeometry *poGeom = nullptr;
123,538✔
358
    if (STARTS_WITH_CI(szToken, "POINT"))
123,538✔
359
    {
360
        poGeom = new OGRPoint();
97,227✔
361
    }
362
    else if (STARTS_WITH_CI(szToken, "LINESTRING"))
26,311✔
363
    {
364
        poGeom = new OGRLineString();
1,654✔
365
    }
366
    else if (STARTS_WITH_CI(szToken, "POLYGON"))
24,657✔
367
    {
368
        poGeom = new OGRPolygon();
16,242✔
369
    }
370
    else if (STARTS_WITH_CI(szToken, "TRIANGLE"))
8,415✔
371
    {
372
        poGeom = new OGRTriangle();
62✔
373
    }
374
    else if (STARTS_WITH_CI(szToken, "GEOMETRYCOLLECTION"))
8,353✔
375
    {
376
        poGeom = new OGRGeometryCollection();
518✔
377
    }
378
    else if (STARTS_WITH_CI(szToken, "MULTIPOLYGON"))
7,835✔
379
    {
380
        poGeom = new OGRMultiPolygon();
914✔
381
    }
382
    else if (STARTS_WITH_CI(szToken, "MULTIPOINT"))
6,921✔
383
    {
384
        poGeom = new OGRMultiPoint();
583✔
385
    }
386
    else if (STARTS_WITH_CI(szToken, "MULTILINESTRING"))
6,338✔
387
    {
388
        poGeom = new OGRMultiLineString();
628✔
389
    }
390
    else if (STARTS_WITH_CI(szToken, "CIRCULARSTRING"))
5,710✔
391
    {
392
        poGeom = new OGRCircularString();
3,520✔
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,457✔
430

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

452
    return eErr;
122,457✔
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,054✔
541
                                   OGRGeometryH *phGeometry)
542

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

550
/************************************************************************/
551
/*                           createGeometry()                           */
552
/************************************************************************/
553

554
/**
555
 * \brief Create an empty geometry of desired type.
556
 *
557
 * This is equivalent to allocating the desired geometry with new, but
558
 * the allocation is guaranteed to take place in the context of the
559
 * GDAL/OGR heap.
560
 *
561
 * This method is the same as the C function OGR_G_CreateGeometry().
562
 *
563
 * @param eGeometryType the type code of the geometry class to be instantiated.
564
 *
565
 * @return the newly create geometry or NULL on failure. Should be freed with
566
 *          OGRGeometryFactory::destroyGeometry() after use.
567
 */
568

569
OGRGeometry *
570
OGRGeometryFactory::createGeometry(OGRwkbGeometryType eGeometryType)
264,474✔
571

572
{
573
    OGRGeometry *poGeom = nullptr;
264,474✔
574
    switch (wkbFlatten(eGeometryType))
264,474✔
575
    {
576
        case wkbPoint:
182,995✔
577
            poGeom = new (std::nothrow) OGRPoint();
365,990✔
578
            break;
182,995✔
579

580
        case wkbLineString:
11,549✔
581
            poGeom = new (std::nothrow) OGRLineString();
23,098✔
582
            break;
11,549✔
583

584
        case wkbPolygon:
28,903✔
585
            poGeom = new (std::nothrow) OGRPolygon();
57,806✔
586
            break;
28,903✔
587

588
        case wkbGeometryCollection:
1,981✔
589
            poGeom = new (std::nothrow) OGRGeometryCollection();
3,962✔
590
            break;
1,981✔
591

592
        case wkbMultiPolygon:
3,137✔
593
            poGeom = new (std::nothrow) OGRMultiPolygon();
6,274✔
594
            break;
3,137✔
595

596
        case wkbMultiPoint:
1,401✔
597
            poGeom = new (std::nothrow) OGRMultiPoint();
2,802✔
598
            break;
1,401✔
599

600
        case wkbMultiLineString:
1,922✔
601
            poGeom = new (std::nothrow) OGRMultiLineString();
3,844✔
602
            break;
1,922✔
603

604
        case wkbLinearRing:
60✔
605
            poGeom = new (std::nothrow) OGRLinearRing();
120✔
606
            break;
60✔
607

608
        case wkbCircularString:
69✔
609
            poGeom = new (std::nothrow) OGRCircularString();
138✔
610
            break;
69✔
611

612
        case wkbCompoundCurve:
1,982✔
613
            poGeom = new (std::nothrow) OGRCompoundCurve();
3,964✔
614
            break;
1,982✔
615

616
        case wkbCurvePolygon:
46✔
617
            poGeom = new (std::nothrow) OGRCurvePolygon();
92✔
618
            break;
46✔
619

620
        case wkbMultiCurve:
1,121✔
621
            poGeom = new (std::nothrow) OGRMultiCurve();
2,242✔
622
            break;
1,121✔
623

624
        case wkbMultiSurface:
1,183✔
625
            poGeom = new (std::nothrow) OGRMultiSurface();
2,366✔
626
            break;
1,183✔
627

628
        case wkbTriangle:
14,502✔
629
            poGeom = new (std::nothrow) OGRTriangle();
29,004✔
630
            break;
14,502✔
631

632
        case wkbPolyhedralSurface:
7,379✔
633
            poGeom = new (std::nothrow) OGRPolyhedralSurface();
14,758✔
634
            break;
7,379✔
635

636
        case wkbTIN:
6,243✔
637
            poGeom = new (std::nothrow) OGRTriangulatedSurface();
12,486✔
638
            break;
6,243✔
639

640
        case wkbUnknown:
1✔
641
            break;
1✔
642

643
        default:
×
644
            CPLAssert(false);
×
645
            break;
646
    }
647
    if (poGeom)
264,474✔
648
    {
649
        if (OGR_GT_HasZ(eGeometryType))
264,473✔
650
            poGeom->set3D(true);
64,745✔
651
        if (OGR_GT_HasM(eGeometryType))
264,473✔
652
            poGeom->setMeasured(true);
59,822✔
653
    }
654
    return poGeom;
264,474✔
655
}
656

657
/************************************************************************/
658
/*                        OGR_G_CreateGeometry()                        */
659
/************************************************************************/
660
/**
661
 * \brief Create an empty geometry of desired type.
662
 *
663
 * This is equivalent to allocating the desired geometry with new, but
664
 * the allocation is guaranteed to take place in the context of the
665
 * GDAL/OGR heap.
666
 *
667
 * This function is the same as the CPP method
668
 * OGRGeometryFactory::createGeometry.
669
 *
670
 * @param eGeometryType the type code of the geometry to be created.
671
 *
672
 * @return handle to the newly create geometry or NULL on failure. Should be
673
 *         freed with OGR_G_DestroyGeometry() after use.
674
 */
675

676
OGRGeometryH OGR_G_CreateGeometry(OGRwkbGeometryType eGeometryType)
165,394✔
677

678
{
679
    return OGRGeometry::ToHandle(
165,394✔
680
        OGRGeometryFactory::createGeometry(eGeometryType));
165,394✔
681
}
682

683
/************************************************************************/
684
/*                          destroyGeometry()                           */
685
/************************************************************************/
686

687
/**
688
 * \brief Destroy geometry object.
689
 *
690
 * Equivalent to invoking delete on a geometry, but it guaranteed to take
691
 * place within the context of the GDAL/OGR heap.
692
 *
693
 * This method is the same as the C function OGR_G_DestroyGeometry().
694
 *
695
 * @param poGeom the geometry to deallocate.
696
 */
697

698
void OGRGeometryFactory::destroyGeometry(OGRGeometry *poGeom)
2✔
699

700
{
701
    delete poGeom;
2✔
702
}
2✔
703

704
/************************************************************************/
705
/*                        OGR_G_DestroyGeometry()                       */
706
/************************************************************************/
707
/**
708
 * \brief Destroy geometry object.
709
 *
710
 * Equivalent to invoking delete on a geometry, but it guaranteed to take
711
 * place within the context of the GDAL/OGR heap.
712
 *
713
 * This function is the same as the CPP method
714
 * OGRGeometryFactory::destroyGeometry.
715
 *
716
 * @param hGeom handle to the geometry to delete.
717
 */
718

719
void OGR_G_DestroyGeometry(OGRGeometryH hGeom)
290,118✔
720

721
{
722
    delete OGRGeometry::FromHandle(hGeom);
290,118✔
723
}
290,118✔
724

725
/************************************************************************/
726
/*                           forceToPolygon()                           */
727
/************************************************************************/
728

729
/**
730
 * \brief Convert to polygon.
731
 *
732
 * Tries to force the provided geometry to be a polygon. This effects a change
733
 * on multipolygons.
734
 * Starting with GDAL 2.0, curve polygons or closed curves will be changed to
735
 * polygons.  The passed in geometry is consumed and a new one returned (or
736
 * potentially the same one).
737
 *
738
 * Note: the resulting polygon may break the Simple Features rules for polygons,
739
 * for example when converting from a multi-part multipolygon.
740
 *
741
 * @param poGeom the input geometry - ownership is passed to the method.
742
 * @return new geometry.
743
 */
744

745
OGRGeometry *OGRGeometryFactory::forceToPolygon(OGRGeometry *poGeom)
148✔
746

747
{
748
    if (poGeom == nullptr)
148✔
749
        return nullptr;
×
750

751
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
148✔
752

753
    if (eGeomType == wkbCurvePolygon)
148✔
754
    {
755
        OGRCurvePolygon *poCurve = poGeom->toCurvePolygon();
34✔
756

757
        if (!poGeom->hasCurveGeometry(TRUE))
34✔
758
            return OGRSurface::CastToPolygon(poCurve);
14✔
759

760
        OGRPolygon *poPoly = poCurve->CurvePolyToPoly();
20✔
761
        delete poGeom;
20✔
762
        return poPoly;
20✔
763
    }
764

765
    // base polygon or triangle
766
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolygon))
114✔
767
    {
768
        return OGRSurface::CastToPolygon(poGeom->toSurface());
7✔
769
    }
770

771
    if (OGR_GT_IsCurve(eGeomType))
107✔
772
    {
773
        OGRCurve *poCurve = poGeom->toCurve();
60✔
774
        if (poCurve->getNumPoints() >= 3 && poCurve->get_IsClosed())
60✔
775
        {
776
            OGRPolygon *poPolygon = new OGRPolygon();
40✔
777
            poPolygon->assignSpatialReference(poGeom->getSpatialReference());
40✔
778

779
            if (!poGeom->hasCurveGeometry(TRUE))
40✔
780
            {
781
                poPolygon->addRingDirectly(OGRCurve::CastToLinearRing(poCurve));
26✔
782
            }
783
            else
784
            {
785
                OGRLineString *poLS = poCurve->CurveToLine();
14✔
786
                poPolygon->addRingDirectly(OGRCurve::CastToLinearRing(poLS));
14✔
787
                delete poGeom;
14✔
788
            }
789
            return poPolygon;
40✔
790
        }
791
    }
792

793
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
67✔
794
    {
795
        OGRPolyhedralSurface *poPS = poGeom->toPolyhedralSurface();
6✔
796
        if (poPS->getNumGeometries() == 1)
6✔
797
        {
798
            poGeom = OGRSurface::CastToPolygon(
5✔
799
                poPS->getGeometryRef(0)->clone()->toSurface());
5✔
800
            delete poPS;
5✔
801
            return poGeom;
5✔
802
        }
803
    }
804

805
    if (eGeomType != wkbGeometryCollection && eGeomType != wkbMultiPolygon &&
62✔
806
        eGeomType != wkbMultiSurface)
807
        return poGeom;
38✔
808

809
    // Build an aggregated polygon from all the polygon rings in the container.
810
    OGRPolygon *poPolygon = new OGRPolygon();
24✔
811
    OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
24✔
812
    if (poGeom->hasCurveGeometry())
24✔
813
    {
814
        OGRGeometryCollection *poNewGC =
815
            poGC->getLinearGeometry()->toGeometryCollection();
5✔
816
        delete poGC;
5✔
817
        poGeom = poNewGC;
5✔
818
        poGC = poNewGC;
5✔
819
    }
820

821
    poPolygon->assignSpatialReference(poGeom->getSpatialReference());
24✔
822

823
    for (int iGeom = 0; iGeom < poGC->getNumGeometries(); iGeom++)
53✔
824
    {
825
        if (wkbFlatten(poGC->getGeometryRef(iGeom)->getGeometryType()) !=
29✔
826
            wkbPolygon)
827
            continue;
12✔
828

829
        OGRPolygon *poOldPoly = poGC->getGeometryRef(iGeom)->toPolygon();
17✔
830

831
        if (poOldPoly->getExteriorRing() == nullptr)
17✔
832
            continue;
3✔
833

834
        poPolygon->addRingDirectly(poOldPoly->stealExteriorRing());
14✔
835

836
        for (int iRing = 0; iRing < poOldPoly->getNumInteriorRings(); iRing++)
22✔
837
            poPolygon->addRingDirectly(poOldPoly->stealInteriorRing(iRing));
8✔
838
    }
839

840
    delete poGC;
24✔
841

842
    return poPolygon;
24✔
843
}
844

845
/************************************************************************/
846
/*                        OGR_G_ForceToPolygon()                        */
847
/************************************************************************/
848

849
/**
850
 * \brief Convert to polygon.
851
 *
852
 * This function is the same as the C++ method
853
 * OGRGeometryFactory::forceToPolygon().
854
 *
855
 * @param hGeom handle to the geometry to convert (ownership surrendered).
856
 * @return the converted geometry (ownership to caller).
857
 *
858
 * @since GDAL/OGR 1.8.0
859
 */
860

861
OGRGeometryH OGR_G_ForceToPolygon(OGRGeometryH hGeom)
46✔
862

863
{
864
    return OGRGeometry::ToHandle(
46✔
865
        OGRGeometryFactory::forceToPolygon(OGRGeometry::FromHandle(hGeom)));
46✔
866
}
867

868
/************************************************************************/
869
/*                        forceToMultiPolygon()                         */
870
/************************************************************************/
871

872
/**
873
 * \brief Convert to multipolygon.
874
 *
875
 * Tries to force the provided geometry to be a multipolygon.  Currently
876
 * this just effects a change on polygons.  The passed in geometry is
877
 * consumed and a new one returned (or potentially the same one).
878
 *
879
 * @return new geometry.
880
 */
881

882
OGRGeometry *OGRGeometryFactory::forceToMultiPolygon(OGRGeometry *poGeom)
3,728✔
883

884
{
885
    if (poGeom == nullptr)
3,728✔
886
        return nullptr;
×
887

888
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
3,728✔
889

890
    /* -------------------------------------------------------------------- */
891
    /*      If this is already a MultiPolygon, nothing to do                */
892
    /* -------------------------------------------------------------------- */
893
    if (eGeomType == wkbMultiPolygon)
3,728✔
894
    {
895
        return poGeom;
40✔
896
    }
897

898
    /* -------------------------------------------------------------------- */
899
    /*      If this is already a MultiSurface with compatible content,      */
900
    /*      just cast                                                       */
901
    /* -------------------------------------------------------------------- */
902
    if (eGeomType == wkbMultiSurface)
3,688✔
903
    {
904
        OGRMultiSurface *poMS = poGeom->toMultiSurface();
9✔
905
        if (!poMS->hasCurveGeometry(TRUE))
9✔
906
        {
907
            return OGRMultiSurface::CastToMultiPolygon(poMS);
4✔
908
        }
909
    }
910

911
    /* -------------------------------------------------------------------- */
912
    /*      Check for the case of a geometrycollection that can be          */
913
    /*      promoted to MultiPolygon.                                       */
914
    /* -------------------------------------------------------------------- */
915
    if (eGeomType == wkbGeometryCollection || eGeomType == wkbMultiSurface)
3,684✔
916
    {
917
        bool bAllPoly = true;
73✔
918
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
73✔
919
        if (poGeom->hasCurveGeometry())
73✔
920
        {
921
            OGRGeometryCollection *poNewGC =
922
                poGC->getLinearGeometry()->toGeometryCollection();
6✔
923
            delete poGC;
6✔
924
            poGeom = poNewGC;
6✔
925
            poGC = poNewGC;
6✔
926
        }
927

928
        bool bCanConvertToMultiPoly = true;
73✔
929
        for (int iGeom = 0; iGeom < poGC->getNumGeometries(); iGeom++)
294✔
930
        {
931
            OGRwkbGeometryType eSubGeomType =
932
                wkbFlatten(poGC->getGeometryRef(iGeom)->getGeometryType());
221✔
933
            if (eSubGeomType != wkbPolygon)
221✔
934
                bAllPoly = false;
172✔
935
            if (eSubGeomType != wkbMultiPolygon && eSubGeomType != wkbPolygon &&
221✔
936
                eSubGeomType != wkbPolyhedralSurface && eSubGeomType != wkbTIN)
134✔
937
            {
938
                bCanConvertToMultiPoly = false;
16✔
939
            }
940
        }
941

942
        if (!bCanConvertToMultiPoly)
73✔
943
            return poGeom;
12✔
944

945
        OGRMultiPolygon *poMP = new OGRMultiPolygon();
61✔
946
        poMP->assignSpatialReference(poGeom->getSpatialReference());
61✔
947

948
        while (poGC->getNumGeometries() > 0)
264✔
949
        {
950
            OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
203✔
951
            poGC->removeGeometry(0, FALSE);
203✔
952
            if (bAllPoly)
203✔
953
            {
954
                poMP->addGeometryDirectly(poSubGeom);
47✔
955
            }
956
            else
957
            {
958
                poSubGeom = forceToMultiPolygon(poSubGeom);
156✔
959
                OGRMultiPolygon *poSubMP = poSubGeom->toMultiPolygon();
156✔
960
                while (poSubMP != nullptr && poSubMP->getNumGeometries() > 0)
414✔
961
                {
962
                    poMP->addGeometryDirectly(poSubMP->getGeometryRef(0));
258✔
963
                    poSubMP->removeGeometry(0, FALSE);
258✔
964
                }
965
                delete poSubMP;
156✔
966
            }
967
        }
968

969
        delete poGC;
61✔
970

971
        return poMP;
61✔
972
    }
973

974
    if (eGeomType == wkbCurvePolygon)
3,611✔
975
    {
976
        OGRPolygon *poPoly = poGeom->toCurvePolygon()->CurvePolyToPoly();
5✔
977
        OGRMultiPolygon *poMP = new OGRMultiPolygon();
5✔
978
        poMP->assignSpatialReference(poGeom->getSpatialReference());
5✔
979
        poMP->addGeometryDirectly(poPoly);
5✔
980
        delete poGeom;
5✔
981
        return poMP;
5✔
982
    }
983

984
    /* -------------------------------------------------------------------- */
985
    /*      If it is PolyhedralSurface or TIN, then pretend it is a         */
986
    /*      multipolygon.                                                   */
987
    /* -------------------------------------------------------------------- */
988
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
3,606✔
989
    {
990
        return OGRPolyhedralSurface::CastToMultiPolygon(
992✔
991
            poGeom->toPolyhedralSurface());
992✔
992
    }
993

994
    if (eGeomType == wkbTriangle)
2,614✔
995
    {
996
        return forceToMultiPolygon(forceToPolygon(poGeom));
2✔
997
    }
998

999
    /* -------------------------------------------------------------------- */
1000
    /*      Eventually we should try to split the polygon into component    */
1001
    /*      island polygons.  But that is a lot of work and can be put off. */
1002
    /* -------------------------------------------------------------------- */
1003
    if (eGeomType != wkbPolygon)
2,612✔
1004
        return poGeom;
30✔
1005

1006
    OGRMultiPolygon *poMP = new OGRMultiPolygon();
2,582✔
1007
    poMP->assignSpatialReference(poGeom->getSpatialReference());
2,582✔
1008
    poMP->addGeometryDirectly(poGeom);
2,582✔
1009

1010
    return poMP;
2,582✔
1011
}
1012

1013
/************************************************************************/
1014
/*                     OGR_G_ForceToMultiPolygon()                      */
1015
/************************************************************************/
1016

1017
/**
1018
 * \brief Convert to multipolygon.
1019
 *
1020
 * This function is the same as the C++ method
1021
 * OGRGeometryFactory::forceToMultiPolygon().
1022
 *
1023
 * @param hGeom handle to the geometry to convert (ownership surrendered).
1024
 * @return the converted geometry (ownership to caller).
1025
 *
1026
 * @since GDAL/OGR 1.8.0
1027
 */
1028

1029
OGRGeometryH OGR_G_ForceToMultiPolygon(OGRGeometryH hGeom)
47✔
1030

1031
{
1032
    return OGRGeometry::ToHandle(OGRGeometryFactory::forceToMultiPolygon(
47✔
1033
        OGRGeometry::FromHandle(hGeom)));
47✔
1034
}
1035

1036
/************************************************************************/
1037
/*                        forceToMultiPoint()                           */
1038
/************************************************************************/
1039

1040
/**
1041
 * \brief Convert to multipoint.
1042
 *
1043
 * Tries to force the provided geometry to be a multipoint.  Currently
1044
 * this just effects a change on points or collection of points.
1045
 * The passed in geometry is
1046
 * consumed and a new one returned (or potentially the same one).
1047
 *
1048
 * @return new geometry.
1049
 */
1050

1051
OGRGeometry *OGRGeometryFactory::forceToMultiPoint(OGRGeometry *poGeom)
67✔
1052

1053
{
1054
    if (poGeom == nullptr)
67✔
1055
        return nullptr;
×
1056

1057
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
67✔
1058

1059
    /* -------------------------------------------------------------------- */
1060
    /*      If this is already a MultiPoint, nothing to do                  */
1061
    /* -------------------------------------------------------------------- */
1062
    if (eGeomType == wkbMultiPoint)
67✔
1063
    {
1064
        return poGeom;
2✔
1065
    }
1066

1067
    /* -------------------------------------------------------------------- */
1068
    /*      Check for the case of a geometrycollection that can be          */
1069
    /*      promoted to MultiPoint.                                         */
1070
    /* -------------------------------------------------------------------- */
1071
    if (eGeomType == wkbGeometryCollection)
65✔
1072
    {
1073
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
14✔
1074
        for (const auto &poMember : poGC)
18✔
1075
        {
1076
            if (wkbFlatten(poMember->getGeometryType()) != wkbPoint)
14✔
1077
                return poGeom;
10✔
1078
        }
1079

1080
        OGRMultiPoint *poMP = new OGRMultiPoint();
4✔
1081
        poMP->assignSpatialReference(poGeom->getSpatialReference());
4✔
1082

1083
        while (poGC->getNumGeometries() > 0)
8✔
1084
        {
1085
            poMP->addGeometryDirectly(poGC->getGeometryRef(0));
4✔
1086
            poGC->removeGeometry(0, FALSE);
4✔
1087
        }
1088

1089
        delete poGC;
4✔
1090

1091
        return poMP;
4✔
1092
    }
1093

1094
    if (eGeomType != wkbPoint)
51✔
1095
        return poGeom;
44✔
1096

1097
    OGRMultiPoint *poMP = new OGRMultiPoint();
7✔
1098
    poMP->assignSpatialReference(poGeom->getSpatialReference());
7✔
1099
    poMP->addGeometryDirectly(poGeom);
7✔
1100

1101
    return poMP;
7✔
1102
}
1103

1104
/************************************************************************/
1105
/*                      OGR_G_ForceToMultiPoint()                       */
1106
/************************************************************************/
1107

1108
/**
1109
 * \brief Convert to multipoint.
1110
 *
1111
 * This function is the same as the C++ method
1112
 * OGRGeometryFactory::forceToMultiPoint().
1113
 *
1114
 * @param hGeom handle to the geometry to convert (ownership surrendered).
1115
 * @return the converted geometry (ownership to caller).
1116
 *
1117
 * @since GDAL/OGR 1.8.0
1118
 */
1119

1120
OGRGeometryH OGR_G_ForceToMultiPoint(OGRGeometryH hGeom)
41✔
1121

1122
{
1123
    return OGRGeometry::ToHandle(
41✔
1124
        OGRGeometryFactory::forceToMultiPoint(OGRGeometry::FromHandle(hGeom)));
41✔
1125
}
1126

1127
/************************************************************************/
1128
/*                        forceToMultiLinestring()                      */
1129
/************************************************************************/
1130

1131
/**
1132
 * \brief Convert to multilinestring.
1133
 *
1134
 * Tries to force the provided geometry to be a multilinestring.
1135
 *
1136
 * - linestrings are placed in a multilinestring.
1137
 * - circularstrings and compoundcurves will be approximated and placed in a
1138
 * multilinestring.
1139
 * - geometry collections will be converted to multilinestring if they only
1140
 * contain linestrings.
1141
 * - polygons will be changed to a collection of linestrings (one per ring).
1142
 * - curvepolygons will be approximated and changed to a collection of
1143
 ( linestrings (one per ring).
1144
 *
1145
 * The passed in geometry is
1146
 * consumed and a new one returned (or potentially the same one).
1147
 *
1148
 * @return new geometry.
1149
 */
1150

1151
OGRGeometry *OGRGeometryFactory::forceToMultiLineString(OGRGeometry *poGeom)
2,134✔
1152

1153
{
1154
    if (poGeom == nullptr)
2,134✔
1155
        return nullptr;
×
1156

1157
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
2,134✔
1158

1159
    /* -------------------------------------------------------------------- */
1160
    /*      If this is already a MultiLineString, nothing to do             */
1161
    /* -------------------------------------------------------------------- */
1162
    if (eGeomType == wkbMultiLineString)
2,134✔
1163
    {
1164
        return poGeom;
2✔
1165
    }
1166

1167
    /* -------------------------------------------------------------------- */
1168
    /*      Check for the case of a geometrycollection that can be          */
1169
    /*      promoted to MultiLineString.                                    */
1170
    /* -------------------------------------------------------------------- */
1171
    if (eGeomType == wkbGeometryCollection)
2,132✔
1172
    {
1173
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
16✔
1174
        if (poGeom->hasCurveGeometry())
16✔
1175
        {
1176
            OGRGeometryCollection *poNewGC =
1177
                poGC->getLinearGeometry()->toGeometryCollection();
1✔
1178
            delete poGC;
1✔
1179
            poGeom = poNewGC;
1✔
1180
            poGC = poNewGC;
1✔
1181
        }
1182

1183
        for (auto &&poMember : poGC)
24✔
1184
        {
1185
            if (wkbFlatten(poMember->getGeometryType()) != wkbLineString)
18✔
1186
            {
1187
                return poGeom;
10✔
1188
            }
1189
        }
1190

1191
        OGRMultiLineString *poMP = new OGRMultiLineString();
6✔
1192
        poMP->assignSpatialReference(poGeom->getSpatialReference());
6✔
1193

1194
        while (poGC->getNumGeometries() > 0)
14✔
1195
        {
1196
            poMP->addGeometryDirectly(poGC->getGeometryRef(0));
8✔
1197
            poGC->removeGeometry(0, FALSE);
8✔
1198
        }
1199

1200
        delete poGC;
6✔
1201

1202
        return poMP;
6✔
1203
    }
1204

1205
    /* -------------------------------------------------------------------- */
1206
    /*      Turn a linestring into a multilinestring.                       */
1207
    /* -------------------------------------------------------------------- */
1208
    if (eGeomType == wkbLineString)
2,116✔
1209
    {
1210
        OGRMultiLineString *poMP = new OGRMultiLineString();
2,030✔
1211
        poMP->assignSpatialReference(poGeom->getSpatialReference());
2,030✔
1212
        poMP->addGeometryDirectly(poGeom);
2,030✔
1213
        return poMP;
2,030✔
1214
    }
1215

1216
    /* -------------------------------------------------------------------- */
1217
    /*      Convert polygons into a multilinestring.                        */
1218
    /* -------------------------------------------------------------------- */
1219
    if (OGR_GT_IsSubClassOf(eGeomType, wkbCurvePolygon))
86✔
1220
    {
1221
        OGRMultiLineString *poMLS = new OGRMultiLineString();
28✔
1222
        poMLS->assignSpatialReference(poGeom->getSpatialReference());
28✔
1223

1224
        const auto AddRingFromSrcPoly = [poMLS](const OGRPolygon *poPoly)
57✔
1225
        {
1226
            for (int iRing = 0; iRing < poPoly->getNumInteriorRings() + 1;
61✔
1227
                 iRing++)
1228
            {
1229
                const OGRLineString *poLR;
1230

1231
                if (iRing == 0)
35✔
1232
                {
1233
                    poLR = poPoly->getExteriorRing();
28✔
1234
                    if (poLR == nullptr)
28✔
1235
                        break;
2✔
1236
                }
1237
                else
1238
                    poLR = poPoly->getInteriorRing(iRing - 1);
7✔
1239

1240
                if (poLR == nullptr || poLR->getNumPoints() == 0)
33✔
1241
                    continue;
4✔
1242

1243
                auto poNewLS = new OGRLineString();
29✔
1244
                poNewLS->addSubLineString(poLR);
29✔
1245
                poMLS->addGeometryDirectly(poNewLS);
29✔
1246
            }
1247
        };
28✔
1248

1249
        if (OGR_GT_IsSubClassOf(eGeomType, wkbPolygon))
28✔
1250
        {
1251
            AddRingFromSrcPoly(poGeom->toPolygon());
24✔
1252
        }
1253
        else
1254
        {
1255
            auto poTmpPoly = std::unique_ptr<OGRPolygon>(
1256
                poGeom->toCurvePolygon()->CurvePolyToPoly());
8✔
1257
            AddRingFromSrcPoly(poTmpPoly.get());
4✔
1258
        }
1259

1260
        delete poGeom;
28✔
1261

1262
        return poMLS;
28✔
1263
    }
1264

1265
    /* -------------------------------------------------------------------- */
1266
    /*      If it is PolyhedralSurface or TIN, then pretend it is a         */
1267
    /*      multipolygon.                                                   */
1268
    /* -------------------------------------------------------------------- */
1269
    if (OGR_GT_IsSubClassOf(eGeomType, wkbPolyhedralSurface))
58✔
1270
    {
1271
        poGeom = CPLAssertNotNull(forceToMultiPolygon(poGeom));
×
1272
        eGeomType = wkbMultiPolygon;
×
1273
    }
1274

1275
    /* -------------------------------------------------------------------- */
1276
    /*      Convert multi-polygons into a multilinestring.                  */
1277
    /* -------------------------------------------------------------------- */
1278
    if (eGeomType == wkbMultiPolygon || eGeomType == wkbMultiSurface)
58✔
1279
    {
1280
        OGRMultiLineString *poMLS = new OGRMultiLineString();
9✔
1281
        poMLS->assignSpatialReference(poGeom->getSpatialReference());
9✔
1282

1283
        const auto AddRingFromSrcMP = [poMLS](const OGRMultiPolygon *poSrcMP)
22✔
1284
        {
1285
            for (auto &&poPoly : poSrcMP)
21✔
1286
            {
1287
                for (auto &&poLR : poPoly)
27✔
1288
                {
1289
                    if (poLR->IsEmpty())
15✔
1290
                        continue;
2✔
1291

1292
                    OGRLineString *poNewLS = new OGRLineString();
13✔
1293
                    poNewLS->addSubLineString(poLR);
13✔
1294
                    poMLS->addGeometryDirectly(poNewLS);
13✔
1295
                }
1296
            }
1297
        };
9✔
1298

1299
        if (eGeomType == wkbMultiPolygon)
9✔
1300
        {
1301
            AddRingFromSrcMP(poGeom->toMultiPolygon());
6✔
1302
        }
1303
        else
1304
        {
1305
            auto poTmpMPoly = std::unique_ptr<OGRMultiPolygon>(
1306
                poGeom->getLinearGeometry()->toMultiPolygon());
6✔
1307
            AddRingFromSrcMP(poTmpMPoly.get());
3✔
1308
        }
1309

1310
        delete poGeom;
9✔
1311
        return poMLS;
9✔
1312
    }
1313

1314
    /* -------------------------------------------------------------------- */
1315
    /*      If it is a curve line, approximate it and wrap in a multilinestring
1316
     */
1317
    /* -------------------------------------------------------------------- */
1318
    if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
49✔
1319
    {
1320
        OGRMultiLineString *poMP = new OGRMultiLineString();
20✔
1321
        poMP->assignSpatialReference(poGeom->getSpatialReference());
20✔
1322
        poMP->addGeometryDirectly(poGeom->toCurve()->CurveToLine());
20✔
1323
        delete poGeom;
20✔
1324
        return poMP;
20✔
1325
    }
1326

1327
    /* -------------------------------------------------------------------- */
1328
    /*      If this is already a MultiCurve with compatible content,        */
1329
    /*      just cast                                                       */
1330
    /* -------------------------------------------------------------------- */
1331
    if (eGeomType == wkbMultiCurve &&
38✔
1332
        !poGeom->toMultiCurve()->hasCurveGeometry(TRUE))
9✔
1333
    {
1334
        return OGRMultiCurve::CastToMultiLineString(poGeom->toMultiCurve());
3✔
1335
    }
1336

1337
    /* -------------------------------------------------------------------- */
1338
    /*      If it is a multicurve, call getLinearGeometry()                */
1339
    /* -------------------------------------------------------------------- */
1340
    if (eGeomType == wkbMultiCurve)
26✔
1341
    {
1342
        OGRGeometry *poNewGeom = poGeom->getLinearGeometry();
6✔
1343
        CPLAssert(wkbFlatten(poNewGeom->getGeometryType()) ==
6✔
1344
                  wkbMultiLineString);
1345
        delete poGeom;
6✔
1346
        return poNewGeom->toMultiLineString();
6✔
1347
    }
1348

1349
    return poGeom;
20✔
1350
}
1351

1352
/************************************************************************/
1353
/*                    OGR_G_ForceToMultiLineString()                    */
1354
/************************************************************************/
1355

1356
/**
1357
 * \brief Convert to multilinestring.
1358
 *
1359
 * This function is the same as the C++ method
1360
 * OGRGeometryFactory::forceToMultiLineString().
1361
 *
1362
 * @param hGeom handle to the geometry to convert (ownership surrendered).
1363
 * @return the converted geometry (ownership to caller).
1364
 *
1365
 * @since GDAL/OGR 1.8.0
1366
 */
1367

1368
OGRGeometryH OGR_G_ForceToMultiLineString(OGRGeometryH hGeom)
50✔
1369

1370
{
1371
    return OGRGeometry::ToHandle(OGRGeometryFactory::forceToMultiLineString(
50✔
1372
        OGRGeometry::FromHandle(hGeom)));
50✔
1373
}
1374

1375
/************************************************************************/
1376
/*                      removeLowerDimensionSubGeoms()                  */
1377
/************************************************************************/
1378

1379
/** \brief Remove sub-geometries from a geometry collection that do not have
1380
 *         the maximum topological dimensionality of the collection.
1381
 *
1382
 * This is typically to be used as a cleanup phase after running
1383
 * OGRGeometry::MakeValid()
1384
 *
1385
 * For example, MakeValid() on a polygon can return a geometry collection of
1386
 * polygons and linestrings. Calling this method will return either a polygon
1387
 * or multipolygon by dropping those linestrings.
1388
 *
1389
 * On a non-geometry collection, this will return a clone of the passed
1390
 * geometry.
1391
 *
1392
 * @param poGeom input geometry
1393
 * @return a new geometry.
1394
 *
1395
 * @since GDAL 3.1.0
1396
 */
1397
OGRGeometry *
1398
OGRGeometryFactory::removeLowerDimensionSubGeoms(const OGRGeometry *poGeom)
32✔
1399
{
1400
    if (poGeom == nullptr)
32✔
1401
        return nullptr;
×
1402
    if (wkbFlatten(poGeom->getGeometryType()) != wkbGeometryCollection ||
47✔
1403
        poGeom->IsEmpty())
15✔
1404
    {
1405
        return poGeom->clone();
18✔
1406
    }
1407
    const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
14✔
1408
    int nMaxDim = 0;
14✔
1409
    OGRBoolean bHasCurve = FALSE;
14✔
1410
    for (const auto poSubGeom : *poGC)
39✔
1411
    {
1412
        nMaxDim = std::max(nMaxDim, poSubGeom->getDimension());
25✔
1413
        bHasCurve |= poSubGeom->hasCurveGeometry();
25✔
1414
    }
1415
    int nCountAtMaxDim = 0;
14✔
1416
    const OGRGeometry *poGeomAtMaxDim = nullptr;
14✔
1417
    for (const auto poSubGeom : *poGC)
39✔
1418
    {
1419
        if (poSubGeom->getDimension() == nMaxDim)
25✔
1420
        {
1421
            poGeomAtMaxDim = poSubGeom;
19✔
1422
            nCountAtMaxDim++;
19✔
1423
        }
1424
    }
1425
    if (nCountAtMaxDim == 1 && poGeomAtMaxDim != nullptr)
14✔
1426
    {
1427
        return poGeomAtMaxDim->clone();
9✔
1428
    }
1429
    OGRGeometryCollection *poRet =
1430
        (nMaxDim == 0)
5✔
1431
            ? static_cast<OGRGeometryCollection *>(new OGRMultiPoint())
10✔
1432
        : (nMaxDim == 1)
5✔
1433
            ? (!bHasCurve
10✔
1434
                   ? static_cast<OGRGeometryCollection *>(
4✔
1435
                         new OGRMultiLineString())
1✔
1436
                   : static_cast<OGRGeometryCollection *>(new OGRMultiCurve()))
1✔
1437
        : (nMaxDim == 2 && !bHasCurve)
3✔
1438
            ? static_cast<OGRGeometryCollection *>(new OGRMultiPolygon())
6✔
1439
            : static_cast<OGRGeometryCollection *>(new OGRMultiSurface());
1✔
1440
    for (const auto poSubGeom : *poGC)
15✔
1441
    {
1442
        if (poSubGeom->getDimension() == nMaxDim)
10✔
1443
        {
1444
            if (OGR_GT_IsSubClassOf(poSubGeom->getGeometryType(),
10✔
1445
                                    wkbGeometryCollection))
10✔
1446
            {
1447
                const OGRGeometryCollection *poSubGeomAsGC =
1448
                    poSubGeom->toGeometryCollection();
1✔
1449
                for (const auto poSubSubGeom : *poSubGeomAsGC)
2✔
1450
                {
1451
                    if (poSubSubGeom->getDimension() == nMaxDim)
1✔
1452
                    {
1453
                        poRet->addGeometryDirectly(poSubSubGeom->clone());
1✔
1454
                    }
1455
                }
1456
            }
1457
            else
1458
            {
1459
                poRet->addGeometryDirectly(poSubGeom->clone());
9✔
1460
            }
1461
        }
1462
    }
1463
    return poRet;
5✔
1464
}
1465

1466
/************************************************************************/
1467
/*                  OGR_G_RemoveLowerDimensionSubGeoms()                */
1468
/************************************************************************/
1469

1470
/** \brief Remove sub-geometries from a geometry collection that do not have
1471
 *         the maximum topological dimensionality of the collection.
1472
 *
1473
 * This function is the same as the C++ method
1474
 * OGRGeometryFactory::removeLowerDimensionSubGeoms().
1475
 *
1476
 * @param hGeom handle to the geometry to convert
1477
 * @return a new geometry.
1478
 *
1479
 * @since GDAL 3.1.0
1480
 */
1481

1482
OGRGeometryH OGR_G_RemoveLowerDimensionSubGeoms(const OGRGeometryH hGeom)
18✔
1483

1484
{
1485
    return OGRGeometry::ToHandle(
18✔
1486
        OGRGeometryFactory::removeLowerDimensionSubGeoms(
1487
            OGRGeometry::FromHandle(hGeom)));
36✔
1488
}
1489

1490
/************************************************************************/
1491
/*                          organizePolygons()                          */
1492
/************************************************************************/
1493

1494
struct sPolyExtended
85,332✔
1495
{
1496
    CPL_DISALLOW_COPY_ASSIGN(sPolyExtended)
1497
    sPolyExtended() = default;
60,266✔
1498
    sPolyExtended(sPolyExtended &&) = default;
112,052✔
1499
    sPolyExtended &operator=(sPolyExtended &&) = default;
1500

1501
    OGRGeometry *poGeometry = nullptr;
1502
    OGRCurvePolygon *poPolygon = nullptr;
1503
    OGREnvelope sEnvelope{};
1504
    OGRCurve *poExteriorRing = nullptr;
1505
    OGRPoint poAPoint{};
1506
    int nInitialIndex = 0;
1507
    OGRCurvePolygon *poEnclosingPolygon = nullptr;
1508
    double dfArea = 0.0;
1509
    bool bIsTopLevel = false;
1510
    bool bIsCW = false;
1511
    bool bIsPolygon = false;
1512
};
1513

1514
static bool OGRGeometryFactoryCompareArea(const sPolyExtended &sPoly1,
4,973✔
1515
                                          const sPolyExtended &sPoly2)
1516
{
1517
    return sPoly2.dfArea < sPoly1.dfArea;
4,973✔
1518
}
1519

1520
static bool OGRGeometryFactoryCompareByIndex(const sPolyExtended &sPoly1,
518,784✔
1521
                                             const sPolyExtended &sPoly2)
1522
{
1523
    return sPoly1.nInitialIndex < sPoly2.nInitialIndex;
518,784✔
1524
}
1525

1526
constexpr int N_CRITICAL_PART_NUMBER = 100;
1527

1528
enum OrganizePolygonMethod
1529
{
1530
    METHOD_NORMAL,
1531
    METHOD_SKIP,
1532
    METHOD_ONLY_CCW,
1533
    METHOD_CCW_INNER_JUST_AFTER_CW_OUTER
1534
};
1535

1536
/**
1537
 * \brief Organize polygons based on geometries.
1538
 *
1539
 * Analyse a set of rings (passed as simple polygons), and based on a
1540
 * geometric analysis convert them into a polygon with inner rings,
1541
 * (or a MultiPolygon if dealing with more than one polygon) that follow the
1542
 * OGC Simple Feature specification.
1543
 *
1544
 * All the input geometries must be OGRPolygon/OGRCurvePolygon with only a valid
1545
 * exterior ring (at least 4 points) and no interior rings.
1546
 *
1547
 * The passed in geometries become the responsibility of the method, but the
1548
 * papoPolygons "pointer array" remains owned by the caller.
1549
 *
1550
 * For faster computation, a polygon is considered to be inside
1551
 * another one if a single point of its external ring is included into the other
1552
 * one. (unless 'OGR_DEBUG_ORGANIZE_POLYGONS' configuration option is set to
1553
 * TRUE. In that case, a slower algorithm that tests exact topological
1554
 * relationships is used if GEOS is available.)
1555
 *
1556
 * In cases where a big number of polygons is passed to this function, the
1557
 * default processing may be really slow. You can skip the processing by adding
1558
 * METHOD=SKIP to the option list (the result of the function will be a
1559
 * multi-polygon with all polygons as toplevel polygons) or only make it analyze
1560
 * counterclockwise polygons by adding METHOD=ONLY_CCW to the option list if you
1561
 * can assume that the outline of holes is counterclockwise defined (this is the
1562
 * convention for example in shapefiles, Personal Geodatabases or File
1563
 * Geodatabases).
1564
 *
1565
 * For FileGDB, in most cases, but not always, a faster method than ONLY_CCW can
1566
 * be used. It is CCW_INNER_JUST_AFTER_CW_OUTER. When using it, inner rings are
1567
 * assumed to be counterclockwise oriented, and following immediately the outer
1568
 * ring (clockwise oriented) that they belong to. If that assumption is not met,
1569
 * an inner ring could be attached to the wrong outer ring, so this method must
1570
 * be used with care.
1571
 *
1572
 * If the OGR_ORGANIZE_POLYGONS configuration option is defined, its value will
1573
 * override the value of the METHOD option of papszOptions (useful to modify the
1574
 * behavior of the shapefile driver)
1575
 *
1576
 * @param papoPolygons array of geometry pointers - should all be OGRPolygons
1577
 * or OGRCurvePolygons. Ownership of the geometries is passed, but not of the
1578
 * array itself.
1579
 * @param nPolygonCount number of items in papoPolygons
1580
 * @param pbIsValidGeometry value may be set to FALSE if an invalid result is
1581
 * detected. Validity checks vary according to the method used and are are limited
1582
 * to what is needed to link inner rings to outer rings, so a result of TRUE
1583
 * does not mean that OGRGeometry::IsValid() returns TRUE.
1584
 * @param papszOptions a list of strings for passing options
1585
 *
1586
 * @return a single resulting geometry (either OGRPolygon, OGRCurvePolygon,
1587
 * OGRMultiPolygon, OGRMultiSurface or OGRGeometryCollection). Returns a
1588
 * POLYGON EMPTY in the case of nPolygonCount being 0.
1589
 */
1590

1591
OGRGeometry *OGRGeometryFactory::organizePolygons(OGRGeometry **papoPolygons,
48,253✔
1592
                                                  int nPolygonCount,
1593
                                                  int *pbIsValidGeometry,
1594
                                                  const char **papszOptions)
1595
{
1596
    if (nPolygonCount == 0)
48,253✔
1597
    {
1598
        if (pbIsValidGeometry)
4✔
1599
            *pbIsValidGeometry = TRUE;
×
1600

1601
        return new OGRPolygon();
4✔
1602
    }
1603

1604
    OGRGeometry *geom = nullptr;
48,249✔
1605
    OrganizePolygonMethod method = METHOD_NORMAL;
48,249✔
1606
    bool bHasCurves = false;
48,249✔
1607

1608
    /* -------------------------------------------------------------------- */
1609
    /*      Trivial case of a single polygon.                               */
1610
    /* -------------------------------------------------------------------- */
1611
    if (nPolygonCount == 1)
48,249✔
1612
    {
1613
        OGRwkbGeometryType eType =
1614
            wkbFlatten(papoPolygons[0]->getGeometryType());
33,438✔
1615

1616
        bool bIsValid = true;
33,438✔
1617

1618
        if (eType != wkbPolygon && eType != wkbCurvePolygon)
33,438✔
1619
        {
1620
            CPLError(CE_Warning, CPLE_AppDefined,
3✔
1621
                     "organizePolygons() received a non-Polygon geometry.");
1622
            bIsValid = false;
3✔
1623
            delete papoPolygons[0];
3✔
1624
            geom = new OGRPolygon();
3✔
1625
        }
1626
        else
1627
        {
1628
            geom = papoPolygons[0];
33,435✔
1629
        }
1630

1631
        papoPolygons[0] = nullptr;
33,438✔
1632

1633
        if (pbIsValidGeometry)
33,438✔
1634
            *pbIsValidGeometry = bIsValid;
33,426✔
1635

1636
        return geom;
33,438✔
1637
    }
1638

1639
    bool bUseFastVersion = true;
14,811✔
1640
    if (CPLTestBool(CPLGetConfigOption("OGR_DEBUG_ORGANIZE_POLYGONS", "NO")))
14,811✔
1641
    {
1642
        /* ------------------------------------------------------------------ */
1643
        /*      A wee bit of a warning.                                       */
1644
        /* ------------------------------------------------------------------ */
1645
        static int firstTime = 1;
1646
        // cppcheck-suppress knownConditionTrueFalse
1647
        if (!haveGEOS() && firstTime)
×
1648
        {
1649
            CPLDebug(
×
1650
                "OGR",
1651
                "In OGR_DEBUG_ORGANIZE_POLYGONS mode, GDAL should be built "
1652
                "with GEOS support enabled in order "
1653
                "OGRGeometryFactory::organizePolygons to provide reliable "
1654
                "results on complex polygons.");
1655
            firstTime = 0;
×
1656
        }
1657
        // cppcheck-suppress knownConditionTrueFalse
1658
        bUseFastVersion = !haveGEOS();
×
1659
    }
1660

1661
    /* -------------------------------------------------------------------- */
1662
    /*      Setup per polygon envelope and area information.                */
1663
    /* -------------------------------------------------------------------- */
1664
    std::vector<sPolyExtended> asPolyEx;
29,622✔
1665
    asPolyEx.reserve(nPolygonCount);
14,811✔
1666

1667
    bool bValidTopology = true;
14,811✔
1668
    bool bMixedUpGeometries = false;
14,811✔
1669
    bool bFoundCCW = false;
14,811✔
1670

1671
    const char *pszMethodValue = CSLFetchNameValue(papszOptions, "METHOD");
14,811✔
1672
    const char *pszMethodValueOption =
1673
        CPLGetConfigOption("OGR_ORGANIZE_POLYGONS", nullptr);
14,811✔
1674
    if (pszMethodValueOption != nullptr && pszMethodValueOption[0] != '\0')
14,811✔
1675
        pszMethodValue = pszMethodValueOption;
13,944✔
1676

1677
    if (pszMethodValue != nullptr)
14,811✔
1678
    {
1679
        if (EQUAL(pszMethodValue, "SKIP"))
14,316✔
1680
        {
1681
            method = METHOD_SKIP;
13,948✔
1682
            bMixedUpGeometries = true;
13,948✔
1683
        }
1684
        else if (EQUAL(pszMethodValue, "ONLY_CCW"))
368✔
1685
        {
1686
            method = METHOD_ONLY_CCW;
296✔
1687
        }
1688
        else if (EQUAL(pszMethodValue, "CCW_INNER_JUST_AFTER_CW_OUTER"))
72✔
1689
        {
1690
            method = METHOD_CCW_INNER_JUST_AFTER_CW_OUTER;
×
1691
        }
1692
        else if (!EQUAL(pszMethodValue, "DEFAULT"))
72✔
1693
        {
1694
            CPLError(CE_Warning, CPLE_AppDefined,
×
1695
                     "Unrecognized value for METHOD option : %s",
1696
                     pszMethodValue);
1697
        }
1698
    }
1699

1700
    int nCountCWPolygon = 0;
14,811✔
1701
    int indexOfCWPolygon = -1;
14,811✔
1702

1703
    for (int i = 0; i < nPolygonCount; i++)
75,080✔
1704
    {
1705
        OGRwkbGeometryType eType =
1706
            wkbFlatten(papoPolygons[i]->getGeometryType());
60,269✔
1707

1708
        if (eType != wkbPolygon && eType != wkbCurvePolygon)
60,269✔
1709
        {
1710
            // Ignore any points or lines that find their way in here.
1711
            CPLError(CE_Warning, CPLE_AppDefined,
3✔
1712
                     "organizePolygons() received a non-Polygon geometry.");
1713
            delete papoPolygons[i];
3✔
1714
            continue;
3✔
1715
        }
1716

1717
        sPolyExtended sPolyEx;
120,532✔
1718

1719
        sPolyEx.nInitialIndex = i;
60,266✔
1720
        sPolyEx.poGeometry = papoPolygons[i];
60,266✔
1721
        sPolyEx.poPolygon = papoPolygons[i]->toCurvePolygon();
60,266✔
1722

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

1725
        if (eType == wkbCurvePolygon)
60,266✔
1726
            bHasCurves = true;
33✔
1727
        if (!sPolyEx.poPolygon->IsEmpty() &&
60,266✔
1728
            sPolyEx.poPolygon->getNumInteriorRings() == 0 &&
120,532✔
1729
            sPolyEx.poPolygon->getExteriorRingCurve()->getNumPoints() >= 4)
60,266✔
1730
        {
1731
            if (method != METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
60,264✔
1732
                sPolyEx.dfArea = sPolyEx.poPolygon->get_Area();
60,264✔
1733
            sPolyEx.poExteriorRing = sPolyEx.poPolygon->getExteriorRingCurve();
60,264✔
1734
            sPolyEx.poExteriorRing->StartPoint(&sPolyEx.poAPoint);
60,264✔
1735
            if (eType == wkbPolygon)
60,264✔
1736
            {
1737
                sPolyEx.bIsCW = CPL_TO_BOOL(
60,231✔
1738
                    sPolyEx.poExteriorRing->toLinearRing()->isClockwise());
60,231✔
1739
                sPolyEx.bIsPolygon = true;
60,231✔
1740
            }
1741
            else
1742
            {
1743
                OGRLineString *poLS = sPolyEx.poExteriorRing->CurveToLine();
33✔
1744
                OGRLinearRing oLR;
66✔
1745
                oLR.addSubLineString(poLS);
33✔
1746
                sPolyEx.bIsCW = CPL_TO_BOOL(oLR.isClockwise());
33✔
1747
                sPolyEx.bIsPolygon = false;
33✔
1748
                delete poLS;
33✔
1749
            }
1750
            if (sPolyEx.bIsCW)
60,264✔
1751
            {
1752
                indexOfCWPolygon = i;
17,168✔
1753
                nCountCWPolygon++;
17,168✔
1754
            }
1755
            if (!bFoundCCW)
60,264✔
1756
                bFoundCCW = !(sPolyEx.bIsCW);
29,646✔
1757
        }
1758
        else
1759
        {
1760
            if (!bMixedUpGeometries)
2✔
1761
            {
1762
                CPLError(CE_Warning, CPLE_AppDefined,
×
1763
                         "organizePolygons() received an unexpected geometry.  "
1764
                         "Either a polygon with interior rings, or a polygon "
1765
                         "with less than 4 points, or a non-Polygon geometry.  "
1766
                         "Return arguments as a collection.");
1767
                bMixedUpGeometries = true;
×
1768
            }
1769
        }
1770

1771
        asPolyEx.push_back(std::move(sPolyEx));
60,266✔
1772
    }
1773

1774
    // If we are in ONLY_CCW mode and that we have found that there is only one
1775
    // outer ring, then it is pretty easy : we can assume that all other rings
1776
    // are inside.
1777
    if ((method == METHOD_ONLY_CCW ||
14,811✔
1778
         method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER) &&
296✔
1779
        nCountCWPolygon == 1 && bUseFastVersion)
115✔
1780
    {
1781
        OGRCurvePolygon *poCP = asPolyEx[indexOfCWPolygon].poPolygon;
115✔
1782
        for (int i = 0; i < static_cast<int>(asPolyEx.size()); i++)
391✔
1783
        {
1784
            if (i != indexOfCWPolygon)
276✔
1785
            {
1786
                poCP->addRingDirectly(
161✔
1787
                    asPolyEx[i].poPolygon->stealExteriorRingCurve());
161✔
1788
                delete asPolyEx[i].poPolygon;
161✔
1789
            }
1790
        }
1791

1792
        if (pbIsValidGeometry)
115✔
1793
            *pbIsValidGeometry = TRUE;
113✔
1794
        return poCP;
115✔
1795
    }
1796

1797
    if (method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER && asPolyEx[0].bIsCW)
14,696✔
1798
    {
1799
        // Inner rings are CCW oriented and follow immediately the outer
1800
        // ring (that is CW oriented) in which they are included.
1801
        OGRMultiSurface *poMulti = nullptr;
×
1802
        OGRCurvePolygon *poCur = asPolyEx[0].poPolygon;
×
1803
        OGRGeometry *poRet = poCur;
×
1804
        // We have already checked that the first ring is CW.
1805
        OGREnvelope *psEnvelope = &(asPolyEx[0].sEnvelope);
×
1806
        for (std::size_t i = 1; i < asPolyEx.size(); i++)
×
1807
        {
1808
            if (asPolyEx[i].bIsCW)
×
1809
            {
1810
                if (poMulti == nullptr)
×
1811
                {
1812
                    if (bHasCurves)
×
1813
                        poMulti = new OGRMultiSurface();
×
1814
                    else
1815
                        poMulti = new OGRMultiPolygon();
×
1816
                    poRet = poMulti;
×
1817
                    poMulti->addGeometryDirectly(poCur);
×
1818
                }
1819
                poCur = asPolyEx[i].poPolygon;
×
1820
                poMulti->addGeometryDirectly(poCur);
×
1821
                psEnvelope = &(asPolyEx[i].sEnvelope);
×
1822
            }
1823
            else
1824
            {
1825
                poCur->addRingDirectly(
×
1826
                    asPolyEx[i].poPolygon->stealExteriorRingCurve());
×
1827
                if (!(asPolyEx[i].poAPoint.getX() >= psEnvelope->MinX &&
×
1828
                      asPolyEx[i].poAPoint.getX() <= psEnvelope->MaxX &&
×
1829
                      asPolyEx[i].poAPoint.getY() >= psEnvelope->MinY &&
×
1830
                      asPolyEx[i].poAPoint.getY() <= psEnvelope->MaxY))
×
1831
                {
1832
                    CPLError(CE_Warning, CPLE_AppDefined,
×
1833
                             "Part %d does not respect "
1834
                             "CCW_INNER_JUST_AFTER_CW_OUTER rule",
1835
                             static_cast<int>(i));
1836
                }
1837
                delete asPolyEx[i].poPolygon;
×
1838
            }
1839
        }
1840

1841
        if (pbIsValidGeometry)
×
1842
            *pbIsValidGeometry = TRUE;
×
1843
        return poRet;
×
1844
    }
1845
    else if (method == METHOD_CCW_INNER_JUST_AFTER_CW_OUTER)
14,696✔
1846
    {
1847
        method = METHOD_ONLY_CCW;
×
1848
        for (std::size_t i = 0; i < asPolyEx.size(); i++)
×
1849
            asPolyEx[i].dfArea = asPolyEx[i].poPolygon->get_Area();
×
1850
    }
1851

1852
    // Emits a warning if the number of parts is sufficiently big to anticipate
1853
    // for very long computation time, and the user didn't specify an explicit
1854
    // method.
1855
    if (nPolygonCount > N_CRITICAL_PART_NUMBER && method == METHOD_NORMAL &&
14,696✔
1856
        pszMethodValue == nullptr)
1857
    {
1858
        static int firstTime = 1;
1859
        if (firstTime)
×
1860
        {
1861
            if (bFoundCCW)
×
1862
            {
1863
                CPLError(
×
1864
                    CE_Warning, CPLE_AppDefined,
1865
                    "organizePolygons() received a polygon with more than %d "
1866
                    "parts. The processing may be really slow.  "
1867
                    "You can skip the processing by setting METHOD=SKIP, "
1868
                    "or only make it analyze counter-clock wise parts by "
1869
                    "setting METHOD=ONLY_CCW if you can assume that the "
1870
                    "outline of holes is counter-clock wise defined",
1871
                    N_CRITICAL_PART_NUMBER);
1872
            }
1873
            else
1874
            {
1875
                CPLError(
×
1876
                    CE_Warning, CPLE_AppDefined,
1877
                    "organizePolygons() received a polygon with more than %d "
1878
                    "parts.  The processing may be really slow.  "
1879
                    "You can skip the processing by setting METHOD=SKIP.",
1880
                    N_CRITICAL_PART_NUMBER);
1881
            }
1882
            firstTime = 0;
×
1883
        }
1884
    }
1885

1886
    /* This a nulti-step algorithm :
1887
       1) Sort polygons by descending areas
1888
       2) For each polygon of rank i, find its smallest enclosing polygon
1889
          among the polygons of rank [i-1 ... 0]. If there are no such polygon,
1890
          this is a top-level polygon. Otherwise, depending on if the enclosing
1891
          polygon is top-level or not, we can decide if we are top-level or not
1892
       3) Re-sort the polygons to retrieve their initial order (nicer for
1893
          some applications)
1894
       4) For each non top-level polygon (= inner ring), add it to its
1895
          outer ring
1896
       5) Add the top-level polygons to the multipolygon
1897

1898
       Complexity : O(nPolygonCount^2)
1899
    */
1900

1901
    /* Compute how each polygon relate to the other ones
1902
       To save a bit of computation we always begin the computation by a test
1903
       on the envelope. We also take into account the areas to avoid some
1904
       useless tests.  (A contains B implies envelop(A) contains envelop(B)
1905
       and area(A) > area(B)) In practice, we can hope that few full geometry
1906
       intersection of inclusion test is done:
1907
       * if the polygons are well separated geographically (a set of islands
1908
       for example), no full geometry intersection or inclusion test is done.
1909
       (the envelopes don't intersect each other)
1910

1911
       * if the polygons are 'lake inside an island inside a lake inside an
1912
       area' and that each polygon is much smaller than its enclosing one,
1913
       their bounding boxes are strictly contained into each other, and thus,
1914
       no full geometry intersection or inclusion test is done
1915
    */
1916

1917
    if (!bMixedUpGeometries)
14,696✔
1918
    {
1919
        // STEP 1: Sort polygons by descending area.
1920
        std::sort(asPolyEx.begin(), asPolyEx.end(),
748✔
1921
                  OGRGeometryFactoryCompareArea);
1922
    }
1923
    papoPolygons = nullptr;  // Just to use to avoid it afterwards.
14,696✔
1924

1925
    /* -------------------------------------------------------------------- */
1926
    /*      Compute relationships, if things seem well structured.          */
1927
    /* -------------------------------------------------------------------- */
1928

1929
    // The first (largest) polygon is necessarily top-level.
1930
    asPolyEx[0].bIsTopLevel = true;
14,696✔
1931
    asPolyEx[0].poEnclosingPolygon = nullptr;
14,696✔
1932

1933
    int nCountTopLevel = 1;
14,696✔
1934

1935
    // STEP 2.
1936
    for (int i = 1; !bMixedUpGeometries && bValidTopology &&
18,708✔
1937
                    i < static_cast<int>(asPolyEx.size());
2,380✔
1938
         i++)
1939
    {
1940
        if (method == METHOD_ONLY_CCW && asPolyEx[i].bIsCW)
1,632✔
1941
        {
1942
            nCountTopLevel++;
322✔
1943
            asPolyEx[i].bIsTopLevel = true;
322✔
1944
            asPolyEx[i].poEnclosingPolygon = nullptr;
322✔
1945
            continue;
322✔
1946
        }
1947

1948
        int j = i - 1;  // Used after for.
1,310✔
1949
        for (; bValidTopology && j >= 0; j--)
4,280✔
1950
        {
1951
            bool b_i_inside_j = false;
3,799✔
1952

1953
            if (method == METHOD_ONLY_CCW && asPolyEx[j].bIsCW == false)
3,799✔
1954
            {
1955
                // In that mode, i which is CCW if we reach here can only be
1956
                // included in a CW polygon.
1957
                continue;
810✔
1958
            }
1959

1960
            if (asPolyEx[j].sEnvelope.Contains(asPolyEx[i].sEnvelope))
2,989✔
1961
            {
1962
                if (bUseFastVersion)
835✔
1963
                {
1964
                    if (method == METHOD_ONLY_CCW && j == 0)
835✔
1965
                    {
1966
                        // We are testing if a CCW ring is in the biggest CW
1967
                        // ring It *must* be inside as this is the last
1968
                        // candidate, otherwise the winding order rules is
1969
                        // broken.
1970
                        b_i_inside_j = true;
231✔
1971
                    }
1972
                    else if (asPolyEx[i].bIsPolygon && asPolyEx[j].bIsPolygon &&
1,208✔
1973
                             asPolyEx[j]
604✔
1974
                                 .poExteriorRing->toLinearRing()
604✔
1975
                                 ->isPointOnRingBoundary(&asPolyEx[i].poAPoint,
604✔
1976
                                                         FALSE))
1977
                    {
1978
                        OGRLinearRing *poLR_i =
1979
                            asPolyEx[i].poExteriorRing->toLinearRing();
16✔
1980
                        OGRLinearRing *poLR_j =
1981
                            asPolyEx[j].poExteriorRing->toLinearRing();
16✔
1982

1983
                        // If the point of i is on the boundary of j, we will
1984
                        // iterate over the other points of i.
1985
                        const int nPoints = poLR_i->getNumPoints();
16✔
1986
                        int k = 1;  // Used after for.
16✔
1987
                        OGRPoint previousPoint = asPolyEx[i].poAPoint;
32✔
1988
                        for (; k < nPoints; k++)
31✔
1989
                        {
1990
                            OGRPoint point;
30✔
1991
                            poLR_i->getPoint(k, &point);
30✔
1992
                            if (point.getX() == previousPoint.getX() &&
32✔
1993
                                point.getY() == previousPoint.getY())
2✔
1994
                            {
1995
                                continue;
×
1996
                            }
1997
                            if (poLR_j->isPointOnRingBoundary(&point, FALSE))
30✔
1998
                            {
1999
                                // If it is on the boundary of j, iterate again.
2000
                            }
2001
                            else if (poLR_j->isPointInRing(&point, FALSE))
15✔
2002
                            {
2003
                                // If then point is strictly included in j, then
2004
                                // i is considered inside j.
2005
                                b_i_inside_j = true;
13✔
2006
                                break;
13✔
2007
                            }
2008
                            else
2009
                            {
2010
                                // If it is outside, then i cannot be inside j.
2011
                                break;
2✔
2012
                            }
2013
                            previousPoint = std::move(point);
15✔
2014
                        }
2015
                        if (!b_i_inside_j && k == nPoints && nPoints > 2)
16✔
2016
                        {
2017
                            // All points of i are on the boundary of j.
2018
                            // Take a point in the middle of a segment of i and
2019
                            // test it against j.
2020
                            poLR_i->getPoint(0, &previousPoint);
1✔
2021
                            for (k = 1; k < nPoints; k++)
2✔
2022
                            {
2023
                                OGRPoint point;
2✔
2024
                                poLR_i->getPoint(k, &point);
2✔
2025
                                if (point.getX() == previousPoint.getX() &&
2✔
2026
                                    point.getY() == previousPoint.getY())
×
2027
                                {
2028
                                    continue;
×
2029
                                }
2030
                                OGRPoint pointMiddle;
2✔
2031
                                pointMiddle.setX(
2✔
2032
                                    (point.getX() + previousPoint.getX()) / 2);
2✔
2033
                                pointMiddle.setY(
2✔
2034
                                    (point.getY() + previousPoint.getY()) / 2);
2✔
2035
                                if (poLR_j->isPointOnRingBoundary(&pointMiddle,
2✔
2036
                                                                  FALSE))
2✔
2037
                                {
2038
                                    // If it is on the boundary of j, iterate
2039
                                    // again.
2040
                                }
2041
                                else if (poLR_j->isPointInRing(&pointMiddle,
1✔
2042
                                                               FALSE))
1✔
2043
                                {
2044
                                    // If then point is strictly included in j,
2045
                                    // then i is considered inside j.
2046
                                    b_i_inside_j = true;
1✔
2047
                                    break;
1✔
2048
                                }
2049
                                else
2050
                                {
2051
                                    // If it is outside, then i cannot be inside
2052
                                    // j.
2053
                                    break;
×
2054
                                }
2055
                                previousPoint = std::move(point);
1✔
2056
                            }
2057
                        }
2058
                    }
2059
                    // Note that isPointInRing only test strict inclusion in the
2060
                    // ring.
2061
                    else if (asPolyEx[i].bIsPolygon && asPolyEx[j].bIsPolygon &&
1,176✔
2062
                             asPolyEx[j]
588✔
2063
                                 .poExteriorRing->toLinearRing()
588✔
2064
                                 ->isPointInRing(&asPolyEx[i].poAPoint, FALSE))
588✔
2065
                    {
2066
                        b_i_inside_j = true;
584✔
2067
                    }
2068
                }
2069
                else if (asPolyEx[j].poPolygon->Contains(asPolyEx[i].poPolygon))
×
2070
                {
2071
                    b_i_inside_j = true;
×
2072
                }
2073
            }
2074

2075
            if (b_i_inside_j)
2,989✔
2076
            {
2077
                if (asPolyEx[j].bIsTopLevel)
829✔
2078
                {
2079
                    // We are a lake.
2080
                    asPolyEx[i].bIsTopLevel = false;
828✔
2081
                    asPolyEx[i].poEnclosingPolygon = asPolyEx[j].poPolygon;
828✔
2082
                }
2083
                else
2084
                {
2085
                    // We are included in a something not toplevel (a lake),
2086
                    // so in OGCSF we are considered as toplevel too.
2087
                    nCountTopLevel++;
1✔
2088
                    asPolyEx[i].bIsTopLevel = true;
1✔
2089
                    asPolyEx[i].poEnclosingPolygon = nullptr;
1✔
2090
                }
2091
                break;
829✔
2092
            }
2093
            // Use Overlaps instead of Intersects to be more
2094
            // tolerant about touching polygons.
2095
            else if (bUseFastVersion ||
×
2096
                     !asPolyEx[i].sEnvelope.Intersects(asPolyEx[j].sEnvelope) ||
2,160✔
2097
                     !asPolyEx[i].poPolygon->Overlaps(asPolyEx[j].poPolygon))
×
2098
            {
2099
            }
2100
            else
2101
            {
2102
                // Bad... The polygons are intersecting but no one is
2103
                // contained inside the other one. This is a really broken
2104
                // case. We just make a multipolygon with the whole set of
2105
                // polygons.
2106
                bValidTopology = false;
×
2107
#ifdef DEBUG
2108
                char *wkt1 = nullptr;
×
2109
                char *wkt2 = nullptr;
×
2110
                asPolyEx[i].poPolygon->exportToWkt(&wkt1);
×
2111
                asPolyEx[j].poPolygon->exportToWkt(&wkt2);
×
2112
                CPLDebug("OGR",
×
2113
                         "Bad intersection for polygons %d and %d\n"
2114
                         "geom %d: %s\n"
2115
                         "geom %d: %s",
2116
                         static_cast<int>(i), j, static_cast<int>(i), wkt1, j,
2117
                         wkt2);
2118
                CPLFree(wkt1);
×
2119
                CPLFree(wkt2);
×
2120
#endif
2121
            }
2122
        }
2123

2124
        if (j < 0)
1,310✔
2125
        {
2126
            // We come here because we are not included in anything.
2127
            // We are toplevel.
2128
            nCountTopLevel++;
481✔
2129
            asPolyEx[i].bIsTopLevel = true;
481✔
2130
            asPolyEx[i].poEnclosingPolygon = nullptr;
481✔
2131
        }
2132
    }
2133

2134
    if (pbIsValidGeometry)
14,696✔
2135
        *pbIsValidGeometry = bValidTopology && !bMixedUpGeometries;
14,195✔
2136

2137
    /* --------------------------------------------------------------------- */
2138
    /*      Things broke down - just mark everything as top-level so it gets */
2139
    /*      turned into a multipolygon.                                      */
2140
    /* --------------------------------------------------------------------- */
2141
    if (!bValidTopology || bMixedUpGeometries)
14,696✔
2142
    {
2143
        for (auto &sPolyEx : asPolyEx)
71,558✔
2144
        {
2145
            sPolyEx.bIsTopLevel = true;
57,610✔
2146
        }
2147
        nCountTopLevel = static_cast<int>(asPolyEx.size());
13,948✔
2148
    }
2149

2150
    /* -------------------------------------------------------------------- */
2151
    /*      Try to turn into one or more polygons based on the ring         */
2152
    /*      relationships.                                                  */
2153
    /* -------------------------------------------------------------------- */
2154
    // STEP 3: Sort again in initial order.
2155
    std::sort(asPolyEx.begin(), asPolyEx.end(),
14,696✔
2156
              OGRGeometryFactoryCompareByIndex);
2157

2158
    // STEP 4: Add holes as rings of their enclosing polygon.
2159
    for (auto &sPolyEx : asPolyEx)
74,686✔
2160
    {
2161
        if (sPolyEx.bIsTopLevel == false)
59,990✔
2162
        {
2163
            sPolyEx.poEnclosingPolygon->addRingDirectly(
828✔
2164
                sPolyEx.poPolygon->stealExteriorRingCurve());
828✔
2165
            delete sPolyEx.poPolygon;
828✔
2166
        }
2167
        else if (nCountTopLevel == 1)
59,162✔
2168
        {
2169
            geom = sPolyEx.poPolygon;
91✔
2170
        }
2171
    }
2172

2173
    // STEP 5: Add toplevel polygons.
2174
    if (nCountTopLevel > 1)
14,696✔
2175
    {
2176
        OGRGeometryCollection *poGC =
2177
            bHasCurves ? new OGRMultiSurface() : new OGRMultiPolygon();
14,605✔
2178
        for (auto &sPolyEx : asPolyEx)
74,386✔
2179
        {
2180
            if (sPolyEx.bIsTopLevel)
59,781✔
2181
            {
2182
                poGC->addGeometryDirectly(sPolyEx.poPolygon);
59,071✔
2183
            }
2184
        }
2185
        geom = poGC;
14,605✔
2186
    }
2187

2188
    return geom;
14,696✔
2189
}
2190

2191
/************************************************************************/
2192
/*                           createFromGML()                            */
2193
/************************************************************************/
2194

2195
/**
2196
 * \brief Create geometry from GML.
2197
 *
2198
 * This method translates a fragment of GML containing only the geometry
2199
 * portion into a corresponding OGRGeometry.  There are many limitations
2200
 * on the forms of GML geometries supported by this parser, but they are
2201
 * too numerous to list here.
2202
 *
2203
 * The following GML2 elements are parsed : Point, LineString, Polygon,
2204
 * MultiPoint, MultiLineString, MultiPolygon, MultiGeometry.
2205
 *
2206
 * The following GML3 elements are parsed : Surface,
2207
 * MultiSurface, PolygonPatch, Triangle, Rectangle, Curve, MultiCurve,
2208
 * LineStringSegment, Arc, Circle, CompositeSurface, OrientableSurface, Solid,
2209
 * Tin, TriangulatedSurface.
2210
 *
2211
 * Arc and Circle elements are returned as curves by default. Stroking to
2212
 * linestrings can be done with
2213
 * OGR_G_ForceTo(hGeom, OGR_GT_GetLinear(OGR_G_GetGeometryType(hGeom)), NULL).
2214
 * A 4 degrees step is used by default, unless the user
2215
 * has overridden the value with the OGR_ARC_STEPSIZE configuration variable.
2216
 *
2217
 * The C function OGR_G_CreateFromGML() is the same as this method.
2218
 *
2219
 * @param pszData The GML fragment for the geometry.
2220
 *
2221
 * @return a geometry on success, or NULL on error.
2222
 *
2223
 * @see OGR_G_ForceTo()
2224
 * @see OGR_GT_GetLinear()
2225
 * @see OGR_G_GetGeometryType()
2226
 */
2227

2228
OGRGeometry *OGRGeometryFactory::createFromGML(const char *pszData)
×
2229

2230
{
2231
    OGRGeometryH hGeom;
2232

2233
    hGeom = OGR_G_CreateFromGML(pszData);
×
2234

2235
    return OGRGeometry::FromHandle(hGeom);
×
2236
}
2237

2238
/************************************************************************/
2239
/*                           createFromGEOS()                           */
2240
/************************************************************************/
2241

2242
/** Builds a OGRGeometry* from a GEOSGeom.
2243
 * @param hGEOSCtxt GEOS context
2244
 * @param geosGeom GEOS geometry
2245
 * @return a OGRGeometry*
2246
 */
2247
OGRGeometry *OGRGeometryFactory::createFromGEOS(
3,685✔
2248
    UNUSED_IF_NO_GEOS GEOSContextHandle_t hGEOSCtxt,
2249
    UNUSED_IF_NO_GEOS GEOSGeom geosGeom)
2250

2251
{
2252
#ifndef HAVE_GEOS
2253

2254
    CPLError(CE_Failure, CPLE_NotSupported, "GEOS support not enabled.");
2255
    return nullptr;
2256

2257
#else
2258

2259
    size_t nSize = 0;
3,685✔
2260
    unsigned char *pabyBuf = nullptr;
3,685✔
2261
    OGRGeometry *poGeometry = nullptr;
3,685✔
2262

2263
    // Special case as POINT EMPTY cannot be translated to WKB.
2264
    if (GEOSGeomTypeId_r(hGEOSCtxt, geosGeom) == GEOS_POINT &&
3,770✔
2265
        GEOSisEmpty_r(hGEOSCtxt, geosGeom))
85✔
2266
        return new OGRPoint();
14✔
2267

2268
    const int nCoordDim =
2269
        GEOSGeom_getCoordinateDimension_r(hGEOSCtxt, geosGeom);
3,670✔
2270
    GEOSWKBWriter *wkbwriter = GEOSWKBWriter_create_r(hGEOSCtxt);
3,671✔
2271
    GEOSWKBWriter_setOutputDimension_r(hGEOSCtxt, wkbwriter, nCoordDim);
3,671✔
2272
    pabyBuf = GEOSWKBWriter_write_r(hGEOSCtxt, wkbwriter, geosGeom, &nSize);
3,671✔
2273
    GEOSWKBWriter_destroy_r(hGEOSCtxt, wkbwriter);
3,671✔
2274

2275
    if (pabyBuf == nullptr || nSize == 0)
3,671✔
2276
    {
UNCOV
2277
        return nullptr;
×
2278
    }
2279

2280
    if (OGRGeometryFactory::createFromWkb(pabyBuf, nullptr, &poGeometry,
3,671✔
2281
                                          static_cast<int>(nSize)) !=
3,670✔
2282
        OGRERR_NONE)
2283
    {
2284
        poGeometry = nullptr;
×
2285
    }
2286

2287
    GEOSFree_r(hGEOSCtxt, pabyBuf);
3,670✔
2288

2289
    return poGeometry;
3,671✔
2290

2291
#endif  // HAVE_GEOS
2292
}
2293

2294
/************************************************************************/
2295
/*                              haveGEOS()                              */
2296
/************************************************************************/
2297

2298
/**
2299
 * \brief Test if GEOS enabled.
2300
 *
2301
 * This static method returns TRUE if GEOS support is built into OGR,
2302
 * otherwise it returns FALSE.
2303
 *
2304
 * @return TRUE if available, otherwise FALSE.
2305
 */
2306

2307
bool OGRGeometryFactory::haveGEOS()
33,220✔
2308

2309
{
2310
#ifndef HAVE_GEOS
2311
    return false;
2312
#else
2313
    return true;
33,220✔
2314
#endif
2315
}
2316

2317
/************************************************************************/
2318
/*                           createFromFgf()                            */
2319
/************************************************************************/
2320

2321
/**
2322
 * \brief Create a geometry object of the appropriate type from its FGF (FDO
2323
 * Geometry Format) binary representation.
2324
 *
2325
 * Also note that this is a static method, and that there
2326
 * is no need to instantiate an OGRGeometryFactory object.
2327
 *
2328
 * The C function OGR_G_CreateFromFgf() is the same as this method.
2329
 *
2330
 * @param pabyData pointer to the input BLOB data.
2331
 * @param poSR pointer to the spatial reference to be assigned to the
2332
 *             created geometry object.  This may be NULL.
2333
 * @param ppoReturn the newly created geometry object will be assigned to the
2334
 *                  indicated pointer on return.  This will be NULL in case
2335
 *                  of failure, but NULL might be a valid return for a NULL
2336
 * shape.
2337
 * @param nBytes the number of bytes available in pabyData.
2338
 * @param pnBytesConsumed if not NULL, it will be set to the number of bytes
2339
 * consumed (at most nBytes).
2340
 *
2341
 * @return OGRERR_NONE if all goes well, otherwise any of
2342
 * OGRERR_NOT_ENOUGH_DATA, OGRERR_UNSUPPORTED_GEOMETRY_TYPE, or
2343
 * OGRERR_CORRUPT_DATA may be returned.
2344
 */
2345

2346
OGRErr OGRGeometryFactory::createFromFgf(const void *pabyData,
291✔
2347
                                         OGRSpatialReference *poSR,
2348
                                         OGRGeometry **ppoReturn, int nBytes,
2349
                                         int *pnBytesConsumed)
2350

2351
{
2352
    return createFromFgfInternal(static_cast<const GByte *>(pabyData), poSR,
291✔
2353
                                 ppoReturn, nBytes, pnBytesConsumed, 0);
291✔
2354
}
2355

2356
/************************************************************************/
2357
/*                       createFromFgfInternal()                        */
2358
/************************************************************************/
2359

2360
OGRErr OGRGeometryFactory::createFromFgfInternal(
294✔
2361
    const unsigned char *pabyData, OGRSpatialReference *poSR,
2362
    OGRGeometry **ppoReturn, int nBytes, int *pnBytesConsumed, int nRecLevel)
2363
{
2364
    // Arbitrary value, but certainly large enough for reasonable usages.
2365
    if (nRecLevel == 32)
294✔
2366
    {
2367
        CPLError(CE_Failure, CPLE_AppDefined,
×
2368
                 "Too many recursion levels (%d) while parsing FGF geometry.",
2369
                 nRecLevel);
2370
        return OGRERR_CORRUPT_DATA;
×
2371
    }
2372

2373
    *ppoReturn = nullptr;
294✔
2374

2375
    if (nBytes < 4)
294✔
2376
        return OGRERR_NOT_ENOUGH_DATA;
109✔
2377

2378
    /* -------------------------------------------------------------------- */
2379
    /*      Decode the geometry type.                                       */
2380
    /* -------------------------------------------------------------------- */
2381
    GInt32 nGType = 0;
185✔
2382
    memcpy(&nGType, pabyData + 0, 4);
185✔
2383
    CPL_LSBPTR32(&nGType);
185✔
2384

2385
    if (nGType < 0 || nGType > 13)
185✔
2386
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
171✔
2387

2388
    /* -------------------------------------------------------------------- */
2389
    /*      Decode the dimensionality if appropriate.                       */
2390
    /* -------------------------------------------------------------------- */
2391
    int nTupleSize = 0;
14✔
2392
    GInt32 nGDim = 0;
14✔
2393

2394
    // TODO: Why is this a switch?
2395
    switch (nGType)
14✔
2396
    {
2397
        case 1:  // Point
9✔
2398
        case 2:  // LineString
2399
        case 3:  // Polygon
2400
            if (nBytes < 8)
9✔
2401
                return OGRERR_NOT_ENOUGH_DATA;
×
2402

2403
            memcpy(&nGDim, pabyData + 4, 4);
9✔
2404
            CPL_LSBPTR32(&nGDim);
9✔
2405

2406
            if (nGDim < 0 || nGDim > 3)
9✔
2407
                return OGRERR_CORRUPT_DATA;
×
2408

2409
            nTupleSize = 2;
9✔
2410
            if (nGDim & 0x01)  // Z
9✔
2411
                nTupleSize++;
1✔
2412
            if (nGDim & 0x02)  // M
9✔
2413
                nTupleSize++;
×
2414

2415
            break;
9✔
2416

2417
        default:
5✔
2418
            break;
5✔
2419
    }
2420

2421
    OGRGeometry *poGeom = nullptr;
14✔
2422

2423
    /* -------------------------------------------------------------------- */
2424
    /*      None                                                            */
2425
    /* -------------------------------------------------------------------- */
2426
    if (nGType == 0)
14✔
2427
    {
2428
        if (pnBytesConsumed)
×
2429
            *pnBytesConsumed = 4;
×
2430
    }
2431

2432
    /* -------------------------------------------------------------------- */
2433
    /*      Point                                                           */
2434
    /* -------------------------------------------------------------------- */
2435
    else if (nGType == 1)
14✔
2436
    {
2437
        if (nBytes < nTupleSize * 8 + 8)
3✔
2438
            return OGRERR_NOT_ENOUGH_DATA;
×
2439

2440
        double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
3✔
2441
        memcpy(adfTuple, pabyData + 8, nTupleSize * 8);
3✔
2442
#ifdef CPL_MSB
2443
        for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2444
            CPL_SWAP64PTR(adfTuple + iOrdinal);
2445
#endif
2446
        if (nTupleSize > 2)
3✔
2447
            poGeom = new OGRPoint(adfTuple[0], adfTuple[1], adfTuple[2]);
1✔
2448
        else
2449
            poGeom = new OGRPoint(adfTuple[0], adfTuple[1]);
2✔
2450

2451
        if (pnBytesConsumed)
3✔
2452
            *pnBytesConsumed = 8 + nTupleSize * 8;
1✔
2453
    }
2454

2455
    /* -------------------------------------------------------------------- */
2456
    /*      LineString                                                      */
2457
    /* -------------------------------------------------------------------- */
2458
    else if (nGType == 2)
11✔
2459
    {
2460
        if (nBytes < 12)
2✔
2461
            return OGRERR_NOT_ENOUGH_DATA;
×
2462

2463
        GInt32 nPointCount = 0;
2✔
2464
        memcpy(&nPointCount, pabyData + 8, 4);
2✔
2465
        CPL_LSBPTR32(&nPointCount);
2✔
2466

2467
        if (nPointCount < 0 || nPointCount > INT_MAX / (nTupleSize * 8))
2✔
2468
            return OGRERR_CORRUPT_DATA;
×
2469

2470
        if (nBytes - 12 < nTupleSize * 8 * nPointCount)
2✔
2471
            return OGRERR_NOT_ENOUGH_DATA;
×
2472

2473
        OGRLineString *poLS = new OGRLineString();
2✔
2474
        poGeom = poLS;
2✔
2475
        poLS->setNumPoints(nPointCount);
2✔
2476

2477
        for (int iPoint = 0; iPoint < nPointCount; iPoint++)
4✔
2478
        {
2479
            double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
2✔
2480
            memcpy(adfTuple, pabyData + 12 + 8 * nTupleSize * iPoint,
2✔
2481
                   nTupleSize * 8);
2✔
2482
#ifdef CPL_MSB
2483
            for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2484
                CPL_SWAP64PTR(adfTuple + iOrdinal);
2485
#endif
2486
            if (nTupleSize > 2)
2✔
2487
                poLS->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
×
2488
            else
2489
                poLS->setPoint(iPoint, adfTuple[0], adfTuple[1]);
2✔
2490
        }
2491

2492
        if (pnBytesConsumed)
2✔
2493
            *pnBytesConsumed = 12 + nTupleSize * 8 * nPointCount;
×
2494
    }
2495

2496
    /* -------------------------------------------------------------------- */
2497
    /*      Polygon                                                         */
2498
    /* -------------------------------------------------------------------- */
2499
    else if (nGType == 3)
9✔
2500
    {
2501
        if (nBytes < 12)
4✔
2502
            return OGRERR_NOT_ENOUGH_DATA;
×
2503

2504
        GInt32 nRingCount = 0;
4✔
2505
        memcpy(&nRingCount, pabyData + 8, 4);
4✔
2506
        CPL_LSBPTR32(&nRingCount);
4✔
2507

2508
        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
4✔
2509
            return OGRERR_CORRUPT_DATA;
×
2510

2511
        // Each ring takes at least 4 bytes.
2512
        if (nBytes - 12 < nRingCount * 4)
4✔
2513
            return OGRERR_NOT_ENOUGH_DATA;
×
2514

2515
        int nNextByte = 12;
4✔
2516

2517
        OGRPolygon *poPoly = new OGRPolygon();
4✔
2518
        poGeom = poPoly;
4✔
2519

2520
        for (int iRing = 0; iRing < nRingCount; iRing++)
10✔
2521
        {
2522
            if (nBytes - nNextByte < 4)
6✔
2523
            {
2524
                delete poGeom;
×
2525
                return OGRERR_NOT_ENOUGH_DATA;
×
2526
            }
2527

2528
            GInt32 nPointCount = 0;
6✔
2529
            memcpy(&nPointCount, pabyData + nNextByte, 4);
6✔
2530
            CPL_LSBPTR32(&nPointCount);
6✔
2531

2532
            if (nPointCount < 0 || nPointCount > INT_MAX / (nTupleSize * 8))
6✔
2533
            {
2534
                delete poGeom;
×
2535
                return OGRERR_CORRUPT_DATA;
×
2536
            }
2537

2538
            nNextByte += 4;
6✔
2539

2540
            if (nBytes - nNextByte < nTupleSize * 8 * nPointCount)
6✔
2541
            {
2542
                delete poGeom;
×
2543
                return OGRERR_NOT_ENOUGH_DATA;
×
2544
            }
2545

2546
            OGRLinearRing *poLR = new OGRLinearRing();
6✔
2547
            poLR->setNumPoints(nPointCount);
6✔
2548

2549
            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
12✔
2550
            {
2551
                double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
6✔
2552
                memcpy(adfTuple, pabyData + nNextByte, nTupleSize * 8);
6✔
2553
                nNextByte += nTupleSize * 8;
6✔
2554

2555
#ifdef CPL_MSB
2556
                for (int iOrdinal = 0; iOrdinal < nTupleSize; iOrdinal++)
2557
                    CPL_SWAP64PTR(adfTuple + iOrdinal);
2558
#endif
2559
                if (nTupleSize > 2)
6✔
2560
                    poLR->setPoint(iPoint, adfTuple[0], adfTuple[1],
×
2561
                                   adfTuple[2]);
2562
                else
2563
                    poLR->setPoint(iPoint, adfTuple[0], adfTuple[1]);
6✔
2564
            }
2565

2566
            poPoly->addRingDirectly(poLR);
6✔
2567
        }
2568

2569
        if (pnBytesConsumed)
4✔
2570
            *pnBytesConsumed = nNextByte;
2✔
2571
    }
2572

2573
    /* -------------------------------------------------------------------- */
2574
    /*      GeometryCollections of various kinds.                           */
2575
    /* -------------------------------------------------------------------- */
2576
    else if (nGType == 4      // MultiPoint
5✔
2577
             || nGType == 5   // MultiLineString
5✔
2578
             || nGType == 6   // MultiPolygon
5✔
2579
             || nGType == 7)  // MultiGeometry
3✔
2580
    {
2581
        if (nBytes < 8)
5✔
2582
            return OGRERR_NOT_ENOUGH_DATA;
3✔
2583

2584
        GInt32 nGeomCount = 0;
5✔
2585
        memcpy(&nGeomCount, pabyData + 4, 4);
5✔
2586
        CPL_LSBPTR32(&nGeomCount);
5✔
2587

2588
        if (nGeomCount < 0 || nGeomCount > INT_MAX / 4)
5✔
2589
            return OGRERR_CORRUPT_DATA;
×
2590

2591
        // Each geometry takes at least 4 bytes.
2592
        if (nBytes - 8 < 4 * nGeomCount)
5✔
2593
            return OGRERR_NOT_ENOUGH_DATA;
2✔
2594

2595
        OGRGeometryCollection *poGC = nullptr;
3✔
2596
        if (nGType == 4)
3✔
2597
            poGC = new OGRMultiPoint();
×
2598
        else if (nGType == 5)
3✔
2599
            poGC = new OGRMultiLineString();
×
2600
        else if (nGType == 6)
3✔
2601
            poGC = new OGRMultiPolygon();
1✔
2602
        else if (nGType == 7)
2✔
2603
            poGC = new OGRGeometryCollection();
2✔
2604

2605
        int nBytesUsed = 8;
3✔
2606

2607
        for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
5✔
2608
        {
2609
            int nThisGeomSize = 0;
3✔
2610
            OGRGeometry *poThisGeom = nullptr;
3✔
2611

2612
            const OGRErr eErr = createFromFgfInternal(
6✔
2613
                pabyData + nBytesUsed, poSR, &poThisGeom, nBytes - nBytesUsed,
3✔
2614
                &nThisGeomSize, nRecLevel + 1);
2615
            if (eErr != OGRERR_NONE)
3✔
2616
            {
2617
                delete poGC;
×
2618
                return eErr;
1✔
2619
            }
2620

2621
            nBytesUsed += nThisGeomSize;
3✔
2622
            if (poThisGeom != nullptr)
3✔
2623
            {
2624
                const OGRErr eErr2 = poGC->addGeometryDirectly(poThisGeom);
3✔
2625
                if (eErr2 != OGRERR_NONE)
3✔
2626
                {
2627
                    delete poGC;
1✔
2628
                    delete poThisGeom;
1✔
2629
                    return eErr2;
1✔
2630
                }
2631
            }
2632
        }
2633

2634
        poGeom = poGC;
2✔
2635
        if (pnBytesConsumed)
2✔
2636
            *pnBytesConsumed = nBytesUsed;
2✔
2637
    }
2638

2639
    /* -------------------------------------------------------------------- */
2640
    /*      Currently unsupported geometry.                                 */
2641
    /*                                                                      */
2642
    /*      We need to add 10/11/12/13 curve types in some fashion.         */
2643
    /* -------------------------------------------------------------------- */
2644
    else
2645
    {
2646
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
×
2647
    }
2648

2649
    /* -------------------------------------------------------------------- */
2650
    /*      Assign spatial reference system.                                */
2651
    /* -------------------------------------------------------------------- */
2652
    if (poGeom != nullptr && poSR)
11✔
2653
        poGeom->assignSpatialReference(poSR);
×
2654
    *ppoReturn = poGeom;
11✔
2655

2656
    return OGRERR_NONE;
11✔
2657
}
2658

2659
/************************************************************************/
2660
/*                        OGR_G_CreateFromFgf()                         */
2661
/************************************************************************/
2662

2663
/**
2664
 * \brief Create a geometry object of the appropriate type from its FGF
2665
 * (FDO Geometry Format) binary representation.
2666
 *
2667
 * See OGRGeometryFactory::createFromFgf() */
2668
OGRErr CPL_DLL OGR_G_CreateFromFgf(const void *pabyData,
×
2669
                                   OGRSpatialReferenceH hSRS,
2670
                                   OGRGeometryH *phGeometry, int nBytes,
2671
                                   int *pnBytesConsumed)
2672

2673
{
2674
    return OGRGeometryFactory::createFromFgf(
×
2675
        pabyData, OGRSpatialReference::FromHandle(hSRS),
2676
        reinterpret_cast<OGRGeometry **>(phGeometry), nBytes, pnBytesConsumed);
×
2677
}
2678

2679
/************************************************************************/
2680
/*                SplitLineStringAtDateline()                           */
2681
/************************************************************************/
2682

2683
static void SplitLineStringAtDateline(OGRGeometryCollection *poMulti,
8✔
2684
                                      const OGRLineString *poLS,
2685
                                      double dfDateLineOffset, double dfXOffset)
2686
{
2687
    const double dfLeftBorderX = 180 - dfDateLineOffset;
8✔
2688
    const double dfRightBorderX = -180 + dfDateLineOffset;
8✔
2689
    const double dfDiffSpace = 360 - dfDateLineOffset;
8✔
2690

2691
    const bool bIs3D = poLS->getCoordinateDimension() == 3;
8✔
2692
    OGRLineString *poNewLS = new OGRLineString();
8✔
2693
    poMulti->addGeometryDirectly(poNewLS);
8✔
2694
    for (int i = 0; i < poLS->getNumPoints(); i++)
35✔
2695
    {
2696
        const double dfX = poLS->getX(i) + dfXOffset;
27✔
2697
        if (i > 0 && fabs(dfX - (poLS->getX(i - 1) + dfXOffset)) > dfDiffSpace)
27✔
2698
        {
2699
            double dfX1 = poLS->getX(i - 1) + dfXOffset;
9✔
2700
            double dfY1 = poLS->getY(i - 1);
9✔
2701
            double dfZ1 = poLS->getY(i - 1);
9✔
2702
            double dfX2 = poLS->getX(i) + dfXOffset;
9✔
2703
            double dfY2 = poLS->getY(i);
9✔
2704
            double dfZ2 = poLS->getY(i);
9✔
2705

2706
            if (dfX1 > -180 && dfX1 < dfRightBorderX && dfX2 == 180 &&
8✔
2707
                i + 1 < poLS->getNumPoints() &&
×
2708
                poLS->getX(i + 1) + dfXOffset > -180 &&
17✔
2709
                poLS->getX(i + 1) + dfXOffset < dfRightBorderX)
×
2710
            {
2711
                if (bIs3D)
×
2712
                    poNewLS->addPoint(-180, poLS->getY(i), poLS->getZ(i));
×
2713
                else
2714
                    poNewLS->addPoint(-180, poLS->getY(i));
×
2715

2716
                i++;
×
2717

2718
                if (bIs3D)
×
2719
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i),
×
2720
                                      poLS->getZ(i));
2721
                else
2722
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i));
×
2723
                continue;
×
2724
            }
2725
            else if (dfX1 > dfLeftBorderX && dfX1 < 180 && dfX2 == -180 &&
4✔
2726
                     i + 1 < poLS->getNumPoints() &&
×
2727
                     poLS->getX(i + 1) + dfXOffset > dfLeftBorderX &&
13✔
2728
                     poLS->getX(i + 1) + dfXOffset < 180)
×
2729
            {
2730
                if (bIs3D)
×
2731
                    poNewLS->addPoint(180, poLS->getY(i), poLS->getZ(i));
×
2732
                else
2733
                    poNewLS->addPoint(180, poLS->getY(i));
×
2734

2735
                i++;
×
2736

2737
                if (bIs3D)
×
2738
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i),
×
2739
                                      poLS->getZ(i));
2740
                else
2741
                    poNewLS->addPoint(poLS->getX(i) + dfXOffset, poLS->getY(i));
×
2742
                continue;
×
2743
            }
2744

2745
            if (dfX1 < dfRightBorderX && dfX2 > dfLeftBorderX)
9✔
2746
            {
2747
                std::swap(dfX1, dfX2);
5✔
2748
                std::swap(dfY1, dfY2);
5✔
2749
                std::swap(dfZ1, dfZ2);
5✔
2750
            }
2751
            if (dfX1 > dfLeftBorderX && dfX2 < dfRightBorderX)
9✔
2752
                dfX2 += 360;
9✔
2753

2754
            if (dfX1 <= 180 && dfX2 >= 180 && dfX1 < dfX2)
9✔
2755
            {
2756
                const double dfRatio = (180 - dfX1) / (dfX2 - dfX1);
9✔
2757
                const double dfY = dfRatio * dfY2 + (1 - dfRatio) * dfY1;
9✔
2758
                const double dfZ = dfRatio * dfZ2 + (1 - dfRatio) * dfZ1;
9✔
2759
                double dfNewX =
2760
                    poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? 180 : -180;
9✔
2761
                if (poNewLS->getNumPoints() == 0 ||
18✔
2762
                    poNewLS->getX(poNewLS->getNumPoints() - 1) != dfNewX ||
18✔
2763
                    poNewLS->getY(poNewLS->getNumPoints() - 1) != dfY)
2✔
2764
                {
2765
                    if (bIs3D)
7✔
2766
                        poNewLS->addPoint(dfNewX, dfY, dfZ);
×
2767
                    else
2768
                        poNewLS->addPoint(dfNewX, dfY);
7✔
2769
                }
2770
                poNewLS = new OGRLineString();
9✔
2771
                if (bIs3D)
9✔
2772
                    poNewLS->addPoint(
×
2773
                        poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? -180
×
2774
                                                                      : 180,
2775
                        dfY, dfZ);
2776
                else
2777
                    poNewLS->addPoint(
9✔
2778
                        poLS->getX(i - 1) + dfXOffset > dfLeftBorderX ? -180
9✔
2779
                                                                      : 180,
2780
                        dfY);
2781
                poMulti->addGeometryDirectly(poNewLS);
9✔
2782
            }
2783
            else
2784
            {
2785
                poNewLS = new OGRLineString();
×
2786
                poMulti->addGeometryDirectly(poNewLS);
×
2787
            }
2788
        }
2789
        if (bIs3D)
27✔
2790
            poNewLS->addPoint(dfX, poLS->getY(i), poLS->getZ(i));
×
2791
        else
2792
            poNewLS->addPoint(dfX, poLS->getY(i));
27✔
2793
    }
2794
}
8✔
2795

2796
/************************************************************************/
2797
/*               FixPolygonCoordinatesAtDateLine()                      */
2798
/************************************************************************/
2799

2800
#ifdef HAVE_GEOS
2801
static void FixPolygonCoordinatesAtDateLine(OGRPolygon *poPoly,
5✔
2802
                                            double dfDateLineOffset)
2803
{
2804
    const double dfLeftBorderX = 180 - dfDateLineOffset;
5✔
2805
    const double dfRightBorderX = -180 + dfDateLineOffset;
5✔
2806
    const double dfDiffSpace = 360 - dfDateLineOffset;
5✔
2807

2808
    for (int iPart = 0; iPart < 1 + poPoly->getNumInteriorRings(); iPart++)
10✔
2809
    {
2810
        OGRLineString *poLS = (iPart == 0) ? poPoly->getExteriorRing()
5✔
2811
                                           : poPoly->getInteriorRing(iPart - 1);
5✔
2812
        bool bGoEast = false;
5✔
2813
        const bool bIs3D = poLS->getCoordinateDimension() == 3;
5✔
2814
        for (int i = 1; i < poLS->getNumPoints(); i++)
41✔
2815
        {
2816
            double dfX = poLS->getX(i);
36✔
2817
            const double dfPrevX = poLS->getX(i - 1);
36✔
2818
            const double dfDiffLong = fabs(dfX - dfPrevX);
36✔
2819
            if (dfDiffLong > dfDiffSpace)
36✔
2820
            {
2821
                if ((dfPrevX > dfLeftBorderX && dfX < dfRightBorderX) ||
21✔
2822
                    (dfX < 0 && bGoEast))
6✔
2823
                {
2824
                    dfX += 360;
18✔
2825
                    bGoEast = true;
18✔
2826
                    if (bIs3D)
18✔
2827
                        poLS->setPoint(i, dfX, poLS->getY(i), poLS->getZ(i));
×
2828
                    else
2829
                        poLS->setPoint(i, dfX, poLS->getY(i));
18✔
2830
                }
2831
                else if (dfPrevX < dfRightBorderX && dfX > dfLeftBorderX)
3✔
2832
                {
2833
                    for (int j = i - 1; j >= 0; j--)
10✔
2834
                    {
2835
                        dfX = poLS->getX(j);
7✔
2836
                        if (dfX < 0)
7✔
2837
                        {
2838
                            if (bIs3D)
7✔
2839
                                poLS->setPoint(j, dfX + 360, poLS->getY(j),
×
2840
                                               poLS->getZ(j));
2841
                            else
2842
                                poLS->setPoint(j, dfX + 360, poLS->getY(j));
7✔
2843
                        }
2844
                    }
2845
                    bGoEast = false;
3✔
2846
                }
2847
                else
2848
                {
2849
                    bGoEast = false;
×
2850
                }
2851
            }
2852
        }
2853
    }
2854
}
5✔
2855
#endif
2856

2857
/************************************************************************/
2858
/*                            AddOffsetToLon()                          */
2859
/************************************************************************/
2860

2861
static void AddOffsetToLon(OGRGeometry *poGeom, double dfOffset)
17✔
2862
{
2863
    switch (wkbFlatten(poGeom->getGeometryType()))
17✔
2864
    {
2865
        case wkbPolygon:
7✔
2866
        {
2867
            for (auto poSubGeom : *(poGeom->toPolygon()))
14✔
2868
            {
2869
                AddOffsetToLon(poSubGeom, dfOffset);
7✔
2870
            }
2871

2872
            break;
7✔
2873
        }
2874

2875
        case wkbMultiLineString:
×
2876
        case wkbMultiPolygon:
2877
        case wkbGeometryCollection:
2878
        {
2879
            for (auto poSubGeom : *(poGeom->toGeometryCollection()))
×
2880
            {
2881
                AddOffsetToLon(poSubGeom, dfOffset);
×
2882
            }
2883

2884
            break;
×
2885
        }
2886

2887
        case wkbLineString:
10✔
2888
        {
2889
            OGRLineString *poLineString = poGeom->toLineString();
10✔
2890
            const int nPointCount = poLineString->getNumPoints();
10✔
2891
            const int nCoordDim = poLineString->getCoordinateDimension();
10✔
2892
            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
63✔
2893
            {
2894
                if (nCoordDim == 2)
53✔
2895
                    poLineString->setPoint(
106✔
2896
                        iPoint, poLineString->getX(iPoint) + dfOffset,
53✔
2897
                        poLineString->getY(iPoint));
2898
                else
2899
                    poLineString->setPoint(
×
2900
                        iPoint, poLineString->getX(iPoint) + dfOffset,
×
2901
                        poLineString->getY(iPoint), poLineString->getZ(iPoint));
2902
            }
2903
            break;
10✔
2904
        }
2905

2906
        default:
×
2907
            break;
×
2908
    }
2909
}
17✔
2910

2911
/************************************************************************/
2912
/*                        AddSimpleGeomToMulti()                        */
2913
/************************************************************************/
2914

2915
#ifdef HAVE_GEOS
2916
static void AddSimpleGeomToMulti(OGRGeometryCollection *poMulti,
12✔
2917
                                 const OGRGeometry *poGeom)
2918
{
2919
    switch (wkbFlatten(poGeom->getGeometryType()))
12✔
2920
    {
2921
        case wkbPolygon:
12✔
2922
        case wkbLineString:
2923
            poMulti->addGeometry(poGeom);
12✔
2924
            break;
12✔
2925

2926
        case wkbMultiLineString:
×
2927
        case wkbMultiPolygon:
2928
        case wkbGeometryCollection:
2929
        {
2930
            for (const auto poSubGeom : *(poGeom->toGeometryCollection()))
×
2931
            {
2932
                AddSimpleGeomToMulti(poMulti, poSubGeom);
×
2933
            }
2934
            break;
×
2935
        }
2936

2937
        default:
×
2938
            break;
×
2939
    }
2940
}
12✔
2941
#endif  // #ifdef HAVE_GEOS
2942

2943
/************************************************************************/
2944
/*                       WrapPointDateLine()                            */
2945
/************************************************************************/
2946

2947
static void WrapPointDateLine(OGRPoint *poPoint)
14✔
2948
{
2949
    if (poPoint->getX() > 180)
14✔
2950
    {
2951
        poPoint->setX(fmod(poPoint->getX() + 180, 360) - 180);
2✔
2952
    }
2953
    else if (poPoint->getX() < -180)
12✔
2954
    {
2955
        poPoint->setX(-(fmod(-poPoint->getX() + 180, 360) - 180));
3✔
2956
    }
2957
}
14✔
2958

2959
/************************************************************************/
2960
/*                 CutGeometryOnDateLineAndAddToMulti()                 */
2961
/************************************************************************/
2962

2963
static void CutGeometryOnDateLineAndAddToMulti(OGRGeometryCollection *poMulti,
69✔
2964
                                               const OGRGeometry *poGeom,
2965
                                               double dfDateLineOffset)
2966
{
2967
    const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
69✔
2968
    switch (eGeomType)
69✔
2969
    {
2970
        case wkbPoint:
1✔
2971
        {
2972
            auto poPoint = poGeom->toPoint()->clone();
1✔
2973
            WrapPointDateLine(poPoint);
1✔
2974
            poMulti->addGeometryDirectly(poPoint);
1✔
2975
            break;
1✔
2976
        }
2977

2978
        case wkbPolygon:
54✔
2979
        case wkbLineString:
2980
        {
2981
            bool bSplitLineStringAtDateline = false;
54✔
2982
            OGREnvelope oEnvelope;
54✔
2983

2984
            poGeom->getEnvelope(&oEnvelope);
54✔
2985
            const bool bAroundMinus180 = (oEnvelope.MinX < -180.0);
54✔
2986

2987
            // Naive heuristics... Place to improve.
2988
#ifdef HAVE_GEOS
2989
            std::unique_ptr<OGRGeometry> poDupGeom;
54✔
2990
            bool bWrapDateline = false;
54✔
2991
#endif
2992

2993
            const double dfLeftBorderX = 180 - dfDateLineOffset;
54✔
2994
            const double dfRightBorderX = -180 + dfDateLineOffset;
54✔
2995
            const double dfDiffSpace = 360 - dfDateLineOffset;
54✔
2996

2997
            const double dfXOffset = (bAroundMinus180) ? 360.0 : 0.0;
54✔
2998
            if (oEnvelope.MinX < -180 || oEnvelope.MaxX > 180 ||
54✔
2999
                (oEnvelope.MinX + dfXOffset > dfLeftBorderX &&
52✔
3000
                 oEnvelope.MaxX + dfXOffset > 180))
12✔
3001
            {
3002
#ifndef HAVE_GEOS
3003
                CPLError(CE_Failure, CPLE_NotSupported,
3004
                         "GEOS support not enabled.");
3005
#else
3006
                bWrapDateline = true;
2✔
3007
#endif
3008
            }
3009
            else
3010
            {
3011
                auto poLS = eGeomType == wkbPolygon
3012
                                ? poGeom->toPolygon()->getExteriorRing()
52✔
3013
                                : poGeom->toLineString();
14✔
3014
                if (poLS)
52✔
3015
                {
3016
                    double dfMaxSmallDiffLong = 0;
52✔
3017
                    bool bHasBigDiff = false;
52✔
3018
                    // Detect big gaps in longitude.
3019
                    for (int i = 1; i < poLS->getNumPoints(); i++)
294✔
3020
                    {
3021
                        const double dfPrevX = poLS->getX(i - 1) + dfXOffset;
242✔
3022
                        const double dfX = poLS->getX(i) + dfXOffset;
242✔
3023
                        const double dfDiffLong = fabs(dfX - dfPrevX);
242✔
3024

3025
                        if (dfDiffLong > dfDiffSpace &&
242✔
3026
                            ((dfX > dfLeftBorderX &&
11✔
3027
                              dfPrevX < dfRightBorderX) ||
10✔
3028
                             (dfPrevX > dfLeftBorderX && dfX < dfRightBorderX)))
10✔
3029
                            bHasBigDiff = true;
21✔
3030
                        else if (dfDiffLong > dfMaxSmallDiffLong)
221✔
3031
                            dfMaxSmallDiffLong = dfDiffLong;
55✔
3032
                    }
3033
                    if (bHasBigDiff && dfMaxSmallDiffLong < dfDateLineOffset)
52✔
3034
                    {
3035
                        if (eGeomType == wkbLineString)
13✔
3036
                            bSplitLineStringAtDateline = true;
8✔
3037
                        else
3038
                        {
3039
#ifndef HAVE_GEOS
3040
                            CPLError(CE_Failure, CPLE_NotSupported,
3041
                                     "GEOS support not enabled.");
3042
#else
3043
                            poDupGeom.reset(poGeom->clone());
5✔
3044
                            FixPolygonCoordinatesAtDateLine(
5✔
3045
                                poDupGeom->toPolygon(), dfDateLineOffset);
3046

3047
                            OGREnvelope sEnvelope;
5✔
3048
                            poDupGeom->getEnvelope(&sEnvelope);
5✔
3049
                            bWrapDateline = sEnvelope.MinX != sEnvelope.MaxX;
5✔
3050
#endif
3051
                        }
3052
                    }
3053
                }
3054
            }
3055

3056
            if (bSplitLineStringAtDateline)
54✔
3057
            {
3058
                SplitLineStringAtDateline(poMulti, poGeom->toLineString(),
8✔
3059
                                          dfDateLineOffset,
3060
                                          (bAroundMinus180) ? 360.0 : 0.0);
3061
            }
3062
#ifdef HAVE_GEOS
3063
            else if (bWrapDateline)
46✔
3064
            {
3065
                const OGRGeometry *poWorkGeom =
3066
                    poDupGeom ? poDupGeom.get() : poGeom;
6✔
3067
                OGRGeometry *poRectangle1 = nullptr;
6✔
3068
                OGRGeometry *poRectangle2 = nullptr;
6✔
3069
                const char *pszWKT1 =
6✔
3070
                    !bAroundMinus180
3071
                        ? "POLYGON((-180 90,180 90,180 -90,-180 -90,-180 90))"
6✔
3072
                        : "POLYGON((180 90,-180 90,-180 -90,180 -90,180 90))";
3073
                const char *pszWKT2 =
6✔
3074
                    !bAroundMinus180
3075
                        ? "POLYGON((180 90,360 90,360 -90,180 -90,180 90))"
6✔
3076
                        : "POLYGON((-180 90,-360 90,-360 -90,-180 -90,-180 "
3077
                          "90))";
3078
                OGRGeometryFactory::createFromWkt(pszWKT1, nullptr,
6✔
3079
                                                  &poRectangle1);
3080
                OGRGeometryFactory::createFromWkt(pszWKT2, nullptr,
6✔
3081
                                                  &poRectangle2);
3082
                auto poGeom1 = std::unique_ptr<OGRGeometry>(
3083
                    poWorkGeom->Intersection(poRectangle1));
12✔
3084
                auto poGeom2 = std::unique_ptr<OGRGeometry>(
3085
                    poWorkGeom->Intersection(poRectangle2));
12✔
3086
                delete poRectangle1;
6✔
3087
                delete poRectangle2;
6✔
3088

3089
                if (poGeom1 != nullptr && poGeom2 != nullptr)
6✔
3090
                {
3091
                    AddSimpleGeomToMulti(poMulti, poGeom1.get());
6✔
3092
                    AddOffsetToLon(poGeom2.get(),
6✔
3093
                                   !bAroundMinus180 ? -360.0 : 360.0);
3094
                    AddSimpleGeomToMulti(poMulti, poGeom2.get());
6✔
3095
                }
3096
                else
3097
                {
3098
                    AddSimpleGeomToMulti(poMulti, poGeom);
×
3099
                }
3100
            }
3101
#endif
3102
            else
3103
            {
3104
                poMulti->addGeometry(poGeom);
40✔
3105
            }
3106
            break;
54✔
3107
        }
3108

3109
        case wkbMultiLineString:
14✔
3110
        case wkbMultiPolygon:
3111
        case wkbGeometryCollection:
3112
        {
3113
            for (const auto poSubGeom : *(poGeom->toGeometryCollection()))
45✔
3114
            {
3115
                CutGeometryOnDateLineAndAddToMulti(poMulti, poSubGeom,
31✔
3116
                                                   dfDateLineOffset);
3117
            }
3118
            break;
14✔
3119
        }
3120

3121
        default:
×
3122
            break;
×
3123
    }
3124
}
69✔
3125

3126
#ifdef HAVE_GEOS
3127

3128
/************************************************************************/
3129
/*                             RemovePoint()                            */
3130
/************************************************************************/
3131

3132
static void RemovePoint(OGRGeometry *poGeom, OGRPoint *poPoint)
9✔
3133
{
3134
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
9✔
3135
    switch (eType)
9✔
3136
    {
3137
        case wkbLineString:
4✔
3138
        {
3139
            OGRLineString *poLS = poGeom->toLineString();
4✔
3140
            const bool bIs3D = (poLS->getCoordinateDimension() == 3);
4✔
3141
            int j = 0;
4✔
3142
            for (int i = 0; i < poLS->getNumPoints(); i++)
32✔
3143
            {
3144
                if (poLS->getX(i) != poPoint->getX() ||
30✔
3145
                    poLS->getY(i) != poPoint->getY())
2✔
3146
                {
3147
                    if (i > j)
26✔
3148
                    {
3149
                        if (bIs3D)
4✔
3150
                        {
3151
                            poLS->setPoint(j, poLS->getX(i), poLS->getY(i),
×
3152
                                           poLS->getZ(i));
3153
                        }
3154
                        else
3155
                        {
3156
                            poLS->setPoint(j, poLS->getX(i), poLS->getY(i));
4✔
3157
                        }
3158
                    }
3159
                    j++;
26✔
3160
                }
3161
            }
3162
            poLS->setNumPoints(j);
4✔
3163
            break;
4✔
3164
        }
3165

3166
        case wkbPolygon:
4✔
3167
        {
3168
            OGRPolygon *poPoly = poGeom->toPolygon();
4✔
3169
            if (poPoly->getExteriorRing() != nullptr)
4✔
3170
            {
3171
                RemovePoint(poPoly->getExteriorRing(), poPoint);
4✔
3172
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
4✔
3173
                {
3174
                    RemovePoint(poPoly->getInteriorRing(i), poPoint);
×
3175
                }
3176
            }
3177
            break;
4✔
3178
        }
3179

3180
        case wkbMultiLineString:
1✔
3181
        case wkbMultiPolygon:
3182
        case wkbGeometryCollection:
3183
        {
3184
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
1✔
3185
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
3✔
3186
            {
3187
                RemovePoint(poGC->getGeometryRef(i), poPoint);
2✔
3188
            }
3189
            break;
1✔
3190
        }
3191

3192
        default:
×
3193
            break;
×
3194
    }
3195
}
9✔
3196

3197
/************************************************************************/
3198
/*                              GetDist()                               */
3199
/************************************************************************/
3200

3201
static double GetDist(double dfDeltaX, double dfDeltaY)
51✔
3202
{
3203
    return sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY);
51✔
3204
}
3205

3206
/************************************************************************/
3207
/*                             AlterPole()                              */
3208
/*                                                                      */
3209
/* Replace and point at the pole by points really close to the pole,    */
3210
/* but on the previous and later segments.                              */
3211
/************************************************************************/
3212

3213
static void AlterPole(OGRGeometry *poGeom, OGRPoint *poPole,
5✔
3214
                      bool bIsRing = false)
3215
{
3216
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
5✔
3217
    switch (eType)
5✔
3218
    {
3219
        case wkbLineString:
2✔
3220
        {
3221
            if (!bIsRing)
2✔
3222
                return;
×
3223
            OGRLineString *poLS = poGeom->toLineString();
2✔
3224
            const int nNumPoints = poLS->getNumPoints();
2✔
3225
            if (nNumPoints >= 4)
2✔
3226
            {
3227
                const bool bIs3D = (poLS->getCoordinateDimension() == 3);
2✔
3228
                std::vector<OGRRawPoint> aoPoints;
4✔
3229
                std::vector<double> adfZ;
4✔
3230
                bool bMustClose = false;
2✔
3231
                for (int i = 0; i < nNumPoints; i++)
10✔
3232
                {
3233
                    const double dfX = poLS->getX(i);
8✔
3234
                    const double dfY = poLS->getY(i);
8✔
3235
                    if (dfX == poPole->getX() && dfY == poPole->getY())
8✔
3236
                    {
3237
                        // Replace the pole by points really close to it
3238
                        if (i == 0)
2✔
3239
                            bMustClose = true;
×
3240
                        if (i == nNumPoints - 1)
2✔
3241
                            continue;
×
3242
                        const int iBefore = i > 0 ? i - 1 : nNumPoints - 2;
2✔
3243
                        double dfXBefore = poLS->getX(iBefore);
2✔
3244
                        double dfYBefore = poLS->getY(iBefore);
2✔
3245
                        double dfNorm =
3246
                            GetDist(dfXBefore - dfX, dfYBefore - dfY);
2✔
3247
                        double dfXInterp =
2✔
3248
                            dfX + (dfXBefore - dfX) / dfNorm * 1.0e-7;
2✔
3249
                        double dfYInterp =
2✔
3250
                            dfY + (dfYBefore - dfY) / dfNorm * 1.0e-7;
2✔
3251
                        OGRRawPoint oPoint;
2✔
3252
                        oPoint.x = dfXInterp;
2✔
3253
                        oPoint.y = dfYInterp;
2✔
3254
                        aoPoints.push_back(oPoint);
2✔
3255
                        adfZ.push_back(poLS->getZ(i));
2✔
3256

3257
                        const int iAfter = i + 1;
2✔
3258
                        double dfXAfter = poLS->getX(iAfter);
2✔
3259
                        double dfYAfter = poLS->getY(iAfter);
2✔
3260
                        dfNorm = GetDist(dfXAfter - dfX, dfYAfter - dfY);
2✔
3261
                        dfXInterp = dfX + (dfXAfter - dfX) / dfNorm * 1e-7;
2✔
3262
                        dfYInterp = dfY + (dfYAfter - dfY) / dfNorm * 1e-7;
2✔
3263
                        oPoint.x = dfXInterp;
2✔
3264
                        oPoint.y = dfYInterp;
2✔
3265
                        aoPoints.push_back(oPoint);
2✔
3266
                        adfZ.push_back(poLS->getZ(i));
2✔
3267
                    }
3268
                    else
3269
                    {
3270
                        OGRRawPoint oPoint;
6✔
3271
                        oPoint.x = dfX;
6✔
3272
                        oPoint.y = dfY;
6✔
3273
                        aoPoints.push_back(oPoint);
6✔
3274
                        adfZ.push_back(poLS->getZ(i));
6✔
3275
                    }
3276
                }
3277
                if (bMustClose)
2✔
3278
                {
3279
                    aoPoints.push_back(aoPoints[0]);
×
3280
                    adfZ.push_back(adfZ[0]);
×
3281
                }
3282

3283
                poLS->setPoints(static_cast<int>(aoPoints.size()),
4✔
3284
                                &(aoPoints[0]), bIs3D ? &adfZ[0] : nullptr);
2✔
3285
            }
3286
            break;
2✔
3287
        }
3288

3289
        case wkbPolygon:
2✔
3290
        {
3291
            OGRPolygon *poPoly = poGeom->toPolygon();
2✔
3292
            if (poPoly->getExteriorRing() != nullptr)
2✔
3293
            {
3294
                AlterPole(poPoly->getExteriorRing(), poPole, true);
2✔
3295
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
2✔
3296
                {
3297
                    AlterPole(poPoly->getInteriorRing(i), poPole, true);
×
3298
                }
3299
            }
3300
            break;
2✔
3301
        }
3302

3303
        case wkbMultiLineString:
1✔
3304
        case wkbMultiPolygon:
3305
        case wkbGeometryCollection:
3306
        {
3307
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
1✔
3308
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
2✔
3309
            {
3310
                AlterPole(poGC->getGeometryRef(i), poPole);
1✔
3311
            }
3312
            break;
1✔
3313
        }
3314

3315
        default:
×
3316
            break;
×
3317
    }
3318
}
3319

3320
/************************************************************************/
3321
/*                        IsPolarToGeographic()                         */
3322
/*                                                                      */
3323
/* Returns true if poCT transforms from a projection that includes one  */
3324
/* of the pole in a continuous way.                                     */
3325
/************************************************************************/
3326

3327
static bool IsPolarToGeographic(OGRCoordinateTransformation *poCT,
20✔
3328
                                OGRCoordinateTransformation *poRevCT,
3329
                                bool &bIsNorthPolarOut)
3330
{
3331
    bool bIsNorthPolar = false;
20✔
3332
    bool bIsSouthPolar = false;
20✔
3333
    double x = 0.0;
20✔
3334
    double y = 90.0;
20✔
3335

3336
    const bool bBackupEmitErrors = poCT->GetEmitErrors();
20✔
3337
    poRevCT->SetEmitErrors(false);
20✔
3338
    poCT->SetEmitErrors(false);
20✔
3339

3340
    if (poRevCT->Transform(1, &x, &y) &&
20✔
3341
        // Surprisingly, pole south projects correctly back &
3342
        // forth for antarctic polar stereographic.  Therefore, check that
3343
        // the projected value is not too big.
3344
        fabs(x) < 1e10 && fabs(y) < 1e10)
20✔
3345
    {
3346
        double x_tab[] = {x, x - 1e5, x + 1e5};
18✔
3347
        double y_tab[] = {y, y - 1e5, y + 1e5};
18✔
3348
        if (poCT->Transform(3, x_tab, y_tab) &&
18✔
3349
            fabs(y_tab[0] - (90.0)) < 1e-10 &&
18✔
3350
            fabs(x_tab[2] - x_tab[1]) > 170 &&
53✔
3351
            fabs(y_tab[2] - y_tab[1]) < 1e-10)
17✔
3352
        {
3353
            bIsNorthPolar = true;
17✔
3354
        }
3355
    }
3356

3357
    x = 0.0;
20✔
3358
    y = -90.0;
20✔
3359
    if (poRevCT->Transform(1, &x, &y) && fabs(x) < 1e10 && fabs(y) < 1e10)
20✔
3360
    {
3361
        double x_tab[] = {x, x - 1e5, x + 1e5};
15✔
3362
        double y_tab[] = {y, y - 1e5, y + 1e5};
15✔
3363
        if (poCT->Transform(3, x_tab, y_tab) &&
15✔
3364
            fabs(y_tab[0] - (-90.0)) < 1e-10 &&
15✔
3365
            fabs(x_tab[2] - x_tab[1]) > 170 &&
44✔
3366
            fabs(y_tab[2] - y_tab[1]) < 1e-10)
14✔
3367
        {
3368
            bIsSouthPolar = true;
14✔
3369
        }
3370
    }
3371

3372
    poCT->SetEmitErrors(bBackupEmitErrors);
20✔
3373

3374
    if (bIsNorthPolar && bIsSouthPolar)
20✔
3375
    {
3376
        bIsNorthPolar = false;
13✔
3377
        bIsSouthPolar = false;
13✔
3378
    }
3379

3380
    bIsNorthPolarOut = bIsNorthPolar;
20✔
3381
    return bIsNorthPolar || bIsSouthPolar;
20✔
3382
}
3383

3384
/************************************************************************/
3385
/*                 TransformBeforePolarToGeographic()                   */
3386
/*                                                                      */
3387
/* Transform the geometry (by intersection), so as to cut each geometry */
3388
/* that crosses the pole, in 2 parts. Do also tricks for geometries     */
3389
/* that just touch the pole.                                            */
3390
/************************************************************************/
3391

3392
static std::unique_ptr<OGRGeometry> TransformBeforePolarToGeographic(
6✔
3393
    OGRCoordinateTransformation *poRevCT, bool bIsNorthPolar,
3394
    std::unique_ptr<OGRGeometry> poDstGeom, bool &bNeedPostCorrectionOut)
3395
{
3396
    const int nSign = (bIsNorthPolar) ? 1 : -1;
6✔
3397

3398
    // Does the geometry fully contains the pole ? */
3399
    double dfXPole = 0.0;
6✔
3400
    double dfYPole = nSign * 90.0;
6✔
3401
    poRevCT->Transform(1, &dfXPole, &dfYPole);
6✔
3402
    OGRPoint oPole(dfXPole, dfYPole);
12✔
3403
    const bool bContainsPole = CPL_TO_BOOL(poDstGeom->Contains(&oPole));
6✔
3404

3405
    const double EPS = 1e-9;
6✔
3406

3407
    // Does the geometry touches the pole and intersects the antimeridian ?
3408
    double dfNearPoleAntiMeridianX = 180.0;
6✔
3409
    double dfNearPoleAntiMeridianY = nSign * (90.0 - EPS);
6✔
3410
    poRevCT->Transform(1, &dfNearPoleAntiMeridianX, &dfNearPoleAntiMeridianY);
6✔
3411
    OGRPoint oNearPoleAntimeridian(dfNearPoleAntiMeridianX,
3412
                                   dfNearPoleAntiMeridianY);
12✔
3413
    const bool bContainsNearPoleAntimeridian =
3414
        CPL_TO_BOOL(poDstGeom->Contains(&oNearPoleAntimeridian));
6✔
3415

3416
    // Does the geometry touches the pole (but not intersect the antimeridian) ?
3417
    const bool bRegularTouchesPole = !bContainsPole &&
10✔
3418
                                     !bContainsNearPoleAntimeridian &&
9✔
3419
                                     CPL_TO_BOOL(poDstGeom->Touches(&oPole));
3✔
3420

3421
    // Create a polygon of nearly a full hemisphere, but excluding the anti
3422
    // meridian and the pole.
3423
    OGRPolygon oCutter;
12✔
3424
    OGRLinearRing *poRing = new OGRLinearRing();
6✔
3425
    poRing->addPoint(180.0 - EPS, 0);
6✔
3426
    poRing->addPoint(180.0 - EPS, nSign * (90.0 - EPS));
6✔
3427
    // If the geometry doesn't contain the pole, then we add it to the cutter
3428
    // geometry, but will later remove it completely (geometry touching the
3429
    // pole but intersecting the antimeridian), or will replace it by 2
3430
    // close points (geometry touching the pole without intersecting the
3431
    // antimeridian)
3432
    if (!bContainsPole)
6✔
3433
        poRing->addPoint(180.0, nSign * 90);
4✔
3434
    poRing->addPoint(-180.0 + EPS, nSign * (90.0 - EPS));
6✔
3435
    poRing->addPoint(-180.0 + EPS, 0);
6✔
3436
    poRing->addPoint(180.0 - EPS, 0);
6✔
3437
    oCutter.addRingDirectly(poRing);
6✔
3438

3439
    if (oCutter.transform(poRevCT) == OGRERR_NONE &&
6✔
3440
        // Check that longitudes +/- 180 are continuous
3441
        // in the polar projection
3442
        fabs(poRing->getX(0) - poRing->getX(poRing->getNumPoints() - 2)) < 1 &&
10✔
3443
        (bContainsPole || bContainsNearPoleAntimeridian || bRegularTouchesPole))
4✔
3444
    {
3445
        if (bContainsPole || bContainsNearPoleAntimeridian)
5✔
3446
        {
3447
            auto poNewGeom =
3448
                std::unique_ptr<OGRGeometry>(poDstGeom->Difference(&oCutter));
6✔
3449
            if (poNewGeom)
3✔
3450
            {
3451
                if (bContainsNearPoleAntimeridian)
3✔
3452
                    RemovePoint(poNewGeom.get(), &oPole);
3✔
3453
                poDstGeom = std::move(poNewGeom);
3✔
3454
            }
3455
        }
3456

3457
        if (bRegularTouchesPole)
5✔
3458
        {
3459
            AlterPole(poDstGeom.get(), &oPole);
2✔
3460
        }
3461

3462
        bNeedPostCorrectionOut = true;
5✔
3463
    }
3464
    return poDstGeom;
12✔
3465
}
3466

3467
/************************************************************************/
3468
/*                   IsAntimeridianProjToGeographic()                   */
3469
/*                                                                      */
3470
/* Returns true if poCT transforms from a projection that includes the  */
3471
/* antimeridian in a continuous way.                                    */
3472
/************************************************************************/
3473

3474
static bool IsAntimeridianProjToGeographic(OGRCoordinateTransformation *poCT,
17✔
3475
                                           OGRCoordinateTransformation *poRevCT,
3476
                                           OGRGeometry *poDstGeometry)
3477
{
3478
    const bool bBackupEmitErrors = poCT->GetEmitErrors();
17✔
3479
    poRevCT->SetEmitErrors(false);
17✔
3480
    poCT->SetEmitErrors(false);
17✔
3481

3482
    // Find a reasonable latitude for the geometry
3483
    OGREnvelope sEnvelope;
17✔
3484
    poDstGeometry->getEnvelope(&sEnvelope);
17✔
3485
    OGRPoint pMean(sEnvelope.MinX, (sEnvelope.MinY + sEnvelope.MaxY) / 2);
34✔
3486
    if (pMean.transform(poCT) != OGRERR_NONE)
17✔
3487
    {
3488
        poCT->SetEmitErrors(bBackupEmitErrors);
×
3489
        return false;
×
3490
    }
3491
    const double dfMeanLat = pMean.getY();
17✔
3492

3493
    // Check that close points on each side of the antimeridian in (long, lat)
3494
    // project to close points in the source projection, and check that they
3495
    // roundtrip correctly.
3496
    const double EPS = 1.0e-8;
17✔
3497
    double x1 = 180 - EPS;
17✔
3498
    double y1 = dfMeanLat;
17✔
3499
    double x2 = -180 + EPS;
17✔
3500
    double y2 = dfMeanLat;
17✔
3501
    if (!poRevCT->Transform(1, &x1, &y1) || !poRevCT->Transform(1, &x2, &y2) ||
51✔
3502
        GetDist(x2 - x1, y2 - y1) > 1 || !poCT->Transform(1, &x1, &y1) ||
32✔
3503
        !poCT->Transform(1, &x2, &y2) ||
30✔
3504
        GetDist(x1 - (180 - EPS), y1 - dfMeanLat) > 2 * EPS ||
49✔
3505
        GetDist(x2 - (-180 + EPS), y2 - dfMeanLat) > 2 * EPS)
15✔
3506
    {
3507
        poCT->SetEmitErrors(bBackupEmitErrors);
2✔
3508
        return false;
2✔
3509
    }
3510

3511
    poCT->SetEmitErrors(bBackupEmitErrors);
15✔
3512

3513
    return true;
15✔
3514
}
3515

3516
/************************************************************************/
3517
/*                      CollectPointsOnAntimeridian()                   */
3518
/*                                                                      */
3519
/* Collect points that are the intersection of the lines of the geometry*/
3520
/* with the antimeridian.                                               */
3521
/************************************************************************/
3522

3523
static void CollectPointsOnAntimeridian(OGRGeometry *poGeom,
21✔
3524
                                        OGRCoordinateTransformation *poCT,
3525
                                        OGRCoordinateTransformation *poRevCT,
3526
                                        std::vector<OGRRawPoint> &aoPoints)
3527
{
3528
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
21✔
3529
    switch (eType)
21✔
3530
    {
3531
        case wkbLineString:
11✔
3532
        {
3533
            OGRLineString *poLS = poGeom->toLineString();
11✔
3534
            const int nNumPoints = poLS->getNumPoints();
11✔
3535
            for (int i = 0; i < nNumPoints - 1; i++)
44✔
3536
            {
3537
                const double dfX = poLS->getX(i);
33✔
3538
                const double dfY = poLS->getY(i);
33✔
3539
                const double dfX2 = poLS->getX(i + 1);
33✔
3540
                const double dfY2 = poLS->getY(i + 1);
33✔
3541
                double dfXTrans = dfX;
33✔
3542
                double dfYTrans = dfY;
33✔
3543
                double dfX2Trans = dfX2;
33✔
3544
                double dfY2Trans = dfY2;
33✔
3545
                poCT->Transform(1, &dfXTrans, &dfYTrans);
33✔
3546
                poCT->Transform(1, &dfX2Trans, &dfY2Trans);
33✔
3547
                // Are we crossing the antimeridian ? (detecting by inversion of
3548
                // sign of X)
3549
                if ((dfX2 - dfX) * (dfX2Trans - dfXTrans) < 0 ||
33✔
3550
                    (dfX == dfX2 && dfX2Trans * dfXTrans < 0 &&
14✔
3551
                     fabs(fabs(dfXTrans) - 180) < 10 &&
1✔
3552
                     fabs(fabs(dfX2Trans) - 180) < 10))
1✔
3553
                {
3554
                    double dfXStart = dfX;
17✔
3555
                    double dfYStart = dfY;
17✔
3556
                    double dfXEnd = dfX2;
17✔
3557
                    double dfYEnd = dfY2;
17✔
3558
                    double dfXStartTrans = dfXTrans;
17✔
3559
                    double dfXEndTrans = dfX2Trans;
17✔
3560
                    int iIter = 0;
17✔
3561
                    const double EPS = 1e-8;
17✔
3562
                    // Find point of the segment intersecting the antimeridian
3563
                    // by dichotomy
3564
                    for (;
453✔
3565
                         iIter < 50 && (fabs(fabs(dfXStartTrans) - 180) > EPS ||
470✔
3566
                                        fabs(fabs(dfXEndTrans) - 180) > EPS);
25✔
3567
                         ++iIter)
3568
                    {
3569
                        double dfXMid = (dfXStart + dfXEnd) / 2;
453✔
3570
                        double dfYMid = (dfYStart + dfYEnd) / 2;
453✔
3571
                        double dfXMidTrans = dfXMid;
453✔
3572
                        double dfYMidTrans = dfYMid;
453✔
3573
                        poCT->Transform(1, &dfXMidTrans, &dfYMidTrans);
453✔
3574
                        if ((dfXMid - dfXStart) *
453✔
3575
                                    (dfXMidTrans - dfXStartTrans) <
453✔
3576
                                0 ||
247✔
3577
                            (dfXMid == dfXStart &&
22✔
3578
                             dfXMidTrans * dfXStartTrans < 0))
22✔
3579
                        {
3580
                            dfXEnd = dfXMid;
214✔
3581
                            dfYEnd = dfYMid;
214✔
3582
                            dfXEndTrans = dfXMidTrans;
214✔
3583
                        }
3584
                        else
3585
                        {
3586
                            dfXStart = dfXMid;
239✔
3587
                            dfYStart = dfYMid;
239✔
3588
                            dfXStartTrans = dfXMidTrans;
239✔
3589
                        }
3590
                    }
3591
                    if (iIter < 50)
17✔
3592
                    {
3593
                        OGRRawPoint oPoint;
17✔
3594
                        oPoint.x = (dfXStart + dfXEnd) / 2;
17✔
3595
                        oPoint.y = (dfYStart + dfYEnd) / 2;
17✔
3596
                        poCT->Transform(1, &(oPoint.x), &(oPoint.y));
17✔
3597
                        oPoint.x = 180.0;
17✔
3598
                        aoPoints.push_back(oPoint);
17✔
3599
                    }
3600
                }
3601
            }
3602
            break;
11✔
3603
        }
3604

3605
        case wkbPolygon:
6✔
3606
        {
3607
            OGRPolygon *poPoly = poGeom->toPolygon();
6✔
3608
            if (poPoly->getExteriorRing() != nullptr)
6✔
3609
            {
3610
                CollectPointsOnAntimeridian(poPoly->getExteriorRing(), poCT,
6✔
3611
                                            poRevCT, aoPoints);
3612
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
6✔
3613
                {
3614
                    CollectPointsOnAntimeridian(poPoly->getInteriorRing(i),
×
3615
                                                poCT, poRevCT, aoPoints);
3616
                }
3617
            }
3618
            break;
6✔
3619
        }
3620

3621
        case wkbMultiLineString:
4✔
3622
        case wkbMultiPolygon:
3623
        case wkbGeometryCollection:
3624
        {
3625
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
4✔
3626
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
8✔
3627
            {
3628
                CollectPointsOnAntimeridian(poGC->getGeometryRef(i), poCT,
4✔
3629
                                            poRevCT, aoPoints);
3630
            }
3631
            break;
4✔
3632
        }
3633

3634
        default:
×
3635
            break;
×
3636
    }
3637
}
21✔
3638

3639
/************************************************************************/
3640
/*                         SortPointsByAscendingY()                     */
3641
/************************************************************************/
3642

3643
struct SortPointsByAscendingY
3644
{
3645
    bool operator()(const OGRRawPoint &a, const OGRRawPoint &b)
8✔
3646
    {
3647
        return a.y < b.y;
8✔
3648
    }
3649
};
3650

3651
/************************************************************************/
3652
/*              TransformBeforeAntimeridianToGeographic()               */
3653
/*                                                                      */
3654
/* Transform the geometry (by intersection), so as to cut each geometry */
3655
/* that crosses the antimeridian, in 2 parts.                           */
3656
/************************************************************************/
3657

3658
static std::unique_ptr<OGRGeometry> TransformBeforeAntimeridianToGeographic(
15✔
3659
    OGRCoordinateTransformation *poCT, OGRCoordinateTransformation *poRevCT,
3660
    std::unique_ptr<OGRGeometry> poDstGeom, bool &bNeedPostCorrectionOut)
3661
{
3662
    OGREnvelope sEnvelope;
15✔
3663
    poDstGeom->getEnvelope(&sEnvelope);
15✔
3664
    OGRPoint pMean(sEnvelope.MinX, (sEnvelope.MinY + sEnvelope.MaxY) / 2);
30✔
3665
    pMean.transform(poCT);
15✔
3666
    const double dfMeanLat = pMean.getY();
15✔
3667
    pMean.setX(180.0);
15✔
3668
    pMean.setY(dfMeanLat);
15✔
3669
    pMean.transform(poRevCT);
15✔
3670
    // Check if the antimeridian crosses the bbox of our geometry
3671
    if (!(pMean.getX() >= sEnvelope.MinX && pMean.getY() >= sEnvelope.MinY &&
28✔
3672
          pMean.getX() <= sEnvelope.MaxX && pMean.getY() <= sEnvelope.MaxY))
13✔
3673
    {
3674
        return poDstGeom;
4✔
3675
    }
3676

3677
    // Collect points that are the intersection of the lines of the geometry
3678
    // with the antimeridian
3679
    std::vector<OGRRawPoint> aoPoints;
22✔
3680
    CollectPointsOnAntimeridian(poDstGeom.get(), poCT, poRevCT, aoPoints);
11✔
3681
    if (aoPoints.empty())
11✔
3682
        return poDstGeom;
×
3683

3684
    SortPointsByAscendingY sortFunc;
3685
    std::sort(aoPoints.begin(), aoPoints.end(), sortFunc);
11✔
3686

3687
    const double EPS = 1e-9;
11✔
3688

3689
    // Build a very thin polygon cutting the antimeridian at our points
3690
    OGRLinearRing *poLR = new OGRLinearRing;
11✔
3691
    {
3692
        double x = 180.0 - EPS;
11✔
3693
        double y = aoPoints[0].y - EPS;
11✔
3694
        poRevCT->Transform(1, &x, &y);
11✔
3695
        poLR->addPoint(x, y);
11✔
3696
    }
3697
    for (const auto &oPoint : aoPoints)
28✔
3698
    {
3699
        double x = 180.0 - EPS;
17✔
3700
        double y = oPoint.y;
17✔
3701
        poRevCT->Transform(1, &x, &y);
17✔
3702
        poLR->addPoint(x, y);
17✔
3703
    }
3704
    {
3705
        double x = 180.0 - EPS;
11✔
3706
        double y = aoPoints.back().y + EPS;
11✔
3707
        poRevCT->Transform(1, &x, &y);
11✔
3708
        poLR->addPoint(x, y);
11✔
3709
    }
3710
    {
3711
        double x = 180.0 + EPS;
11✔
3712
        double y = aoPoints.back().y + EPS;
11✔
3713
        poRevCT->Transform(1, &x, &y);
11✔
3714
        poLR->addPoint(x, y);
11✔
3715
    }
3716
    for (size_t i = aoPoints.size(); i > 0;)
28✔
3717
    {
3718
        --i;
17✔
3719
        const OGRRawPoint &oPoint = aoPoints[i];
17✔
3720
        double x = 180.0 + EPS;
17✔
3721
        double y = oPoint.y;
17✔
3722
        poRevCT->Transform(1, &x, &y);
17✔
3723
        poLR->addPoint(x, y);
17✔
3724
    }
3725
    {
3726
        double x = 180.0 + EPS;
11✔
3727
        double y = aoPoints[0].y - EPS;
11✔
3728
        poRevCT->Transform(1, &x, &y);
11✔
3729
        poLR->addPoint(x, y);
11✔
3730
    }
3731
    poLR->closeRings();
11✔
3732

3733
    OGRPolygon oPolyToCut;
22✔
3734
    oPolyToCut.addRingDirectly(poLR);
11✔
3735

3736
#if DEBUG_VERBOSE
3737
    char *pszWKT = NULL;
3738
    oPolyToCut.exportToWkt(&pszWKT);
3739
    CPLDebug("OGR", "Geometry to cut: %s", pszWKT);
3740
    CPLFree(pszWKT);
3741
#endif
3742

3743
    // Get the geometry without the antimeridian
3744
    auto poInter =
3745
        std::unique_ptr<OGRGeometry>(poDstGeom->Difference(&oPolyToCut));
22✔
3746
    if (poInter != nullptr)
11✔
3747
    {
3748
        poDstGeom = std::move(poInter);
11✔
3749
        bNeedPostCorrectionOut = true;
11✔
3750
    }
3751

3752
    return poDstGeom;
11✔
3753
}
3754

3755
/************************************************************************/
3756
/*                 SnapCoordsCloseToLatLongBounds()                     */
3757
/*                                                                      */
3758
/* This function snaps points really close to the antimerdian or poles  */
3759
/* to their exact longitudes/latitudes.                                 */
3760
/************************************************************************/
3761

3762
static void SnapCoordsCloseToLatLongBounds(OGRGeometry *poGeom)
59✔
3763
{
3764
    const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
59✔
3765
    switch (eType)
59✔
3766
    {
3767
        case wkbLineString:
28✔
3768
        {
3769
            OGRLineString *poLS = poGeom->toLineString();
28✔
3770
            const double EPS = 1e-8;
28✔
3771
            for (int i = 0; i < poLS->getNumPoints(); i++)
165✔
3772
            {
3773
                OGRPoint p;
274✔
3774
                poLS->getPoint(i, &p);
137✔
3775
                if (fabs(p.getX() - 180.0) < EPS)
137✔
3776
                {
3777
                    p.setX(180.0);
36✔
3778
                    poLS->setPoint(i, &p);
36✔
3779
                }
3780
                else if (fabs(p.getX() - -180.0) < EPS)
101✔
3781
                {
3782
                    p.setX(-180.0);
31✔
3783
                    poLS->setPoint(i, &p);
31✔
3784
                }
3785

3786
                if (fabs(p.getY() - 90.0) < EPS)
137✔
3787
                {
3788
                    p.setY(90.0);
8✔
3789
                    poLS->setPoint(i, &p);
8✔
3790
                }
3791
                else if (fabs(p.getY() - -90.0) < EPS)
129✔
3792
                {
3793
                    p.setY(-90.0);
2✔
3794
                    poLS->setPoint(i, &p);
2✔
3795
                }
3796
            }
3797
            break;
28✔
3798
        }
3799

3800
        case wkbPolygon:
18✔
3801
        {
3802
            OGRPolygon *poPoly = poGeom->toPolygon();
18✔
3803
            if (poPoly->getExteriorRing() != nullptr)
18✔
3804
            {
3805
                SnapCoordsCloseToLatLongBounds(poPoly->getExteriorRing());
18✔
3806
                for (int i = 0; i < poPoly->getNumInteriorRings(); ++i)
18✔
3807
                {
3808
                    SnapCoordsCloseToLatLongBounds(poPoly->getInteriorRing(i));
×
3809
                }
3810
            }
3811
            break;
18✔
3812
        }
3813

3814
        case wkbMultiLineString:
13✔
3815
        case wkbMultiPolygon:
3816
        case wkbGeometryCollection:
3817
        {
3818
            OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
13✔
3819
            for (int i = 0; i < poGC->getNumGeometries(); ++i)
38✔
3820
            {
3821
                SnapCoordsCloseToLatLongBounds(poGC->getGeometryRef(i));
25✔
3822
            }
3823
            break;
13✔
3824
        }
3825

3826
        default:
×
3827
            break;
×
3828
    }
3829
}
59✔
3830

3831
#endif
3832

3833
/************************************************************************/
3834
/*                  TransformWithOptionsCache::Private                  */
3835
/************************************************************************/
3836

3837
struct OGRGeometryFactory::TransformWithOptionsCache::Private
3838
{
3839
    const OGRSpatialReference *poSourceCRS = nullptr;
3840
    const OGRSpatialReference *poTargetCRS = nullptr;
3841
    const OGRCoordinateTransformation *poCT = nullptr;
3842
    std::unique_ptr<OGRCoordinateTransformation> poRevCT{};
3843
    bool bIsPolar = false;
3844
    bool bIsNorthPolar = false;
3845

3846
    void clear()
48✔
3847
    {
3848
        poSourceCRS = nullptr;
48✔
3849
        poTargetCRS = nullptr;
48✔
3850
        poCT = nullptr;
48✔
3851
        poRevCT.reset();
48✔
3852
        bIsPolar = false;
48✔
3853
        bIsNorthPolar = false;
48✔
3854
    }
48✔
3855
};
3856

3857
/************************************************************************/
3858
/*                     TransformWithOptionsCache()                      */
3859
/************************************************************************/
3860

3861
OGRGeometryFactory::TransformWithOptionsCache::TransformWithOptionsCache()
1,108✔
3862
    : d(new Private())
1,108✔
3863
{
3864
}
1,108✔
3865

3866
/************************************************************************/
3867
/*                     ~TransformWithOptionsCache()                      */
3868
/************************************************************************/
3869

3870
OGRGeometryFactory::TransformWithOptionsCache::~TransformWithOptionsCache()
1,108✔
3871
{
3872
}
1,108✔
3873

3874
/************************************************************************/
3875
/*              isTransformWithOptionsRegularTransform()                */
3876
/************************************************************************/
3877

3878
//! @cond Doxygen_Suppress
3879
/*static */
3880
bool OGRGeometryFactory::isTransformWithOptionsRegularTransform(
61✔
3881
    [[maybe_unused]] const OGRSpatialReference *poSourceCRS,
3882
    [[maybe_unused]] const OGRSpatialReference *poTargetCRS,
3883
    CSLConstList papszOptions)
3884
{
3885
    if (papszOptions)
61✔
3886
        return false;
11✔
3887

3888
#ifdef HAVE_GEOS
3889
    if (poSourceCRS && poTargetCRS && poSourceCRS->IsProjected() &&
50✔
3890
        poTargetCRS->IsGeographic() &&
37✔
3891
        poTargetCRS->GetAxisMappingStrategy() == OAMS_TRADITIONAL_GIS_ORDER &&
131✔
3892
        // check that angular units is degree
3893
        std::fabs(poTargetCRS->GetAngularUnits(nullptr) -
31✔
3894
                  CPLAtof(SRS_UA_DEGREE_CONV)) <=
31✔
3895
            1e-8 * CPLAtof(SRS_UA_DEGREE_CONV))
31✔
3896
    {
3897
        double dfWestLong = 0.0;
31✔
3898
        double dfSouthLat = 0.0;
31✔
3899
        double dfEastLong = 0.0;
31✔
3900
        double dfNorthLat = 0.0;
31✔
3901
        if (poSourceCRS->GetAreaOfUse(&dfWestLong, &dfSouthLat, &dfEastLong,
31✔
3902
                                      &dfNorthLat, nullptr) &&
59✔
3903
            !(dfSouthLat == -90.0 || dfNorthLat == 90.0 ||
28✔
3904
              dfWestLong == -180.0 || dfEastLong == 180.0 ||
28✔
3905
              dfWestLong > dfEastLong))
21✔
3906
        {
3907
            // Not a global geographic CRS
3908
            return true;
21✔
3909
        }
3910
        return false;
10✔
3911
    }
3912
#endif
3913

3914
    return true;
19✔
3915
}
3916

3917
//! @endcond
3918

3919
/************************************************************************/
3920
/*                       transformWithOptions()                         */
3921
/************************************************************************/
3922

3923
/** Transform a geometry.
3924
 *
3925
 * This is an enhanced version of OGRGeometry::Transform().
3926
 *
3927
 * When reprojecting geometries from a Polar Stereographic projection or a
3928
 * projection naturally crossing the antimeridian (like UTM Zone 60) to a
3929
 * geographic CRS, it will cut geometries along the antimeridian. So a
3930
 * LineString might be returned as a MultiLineString.
3931
 *
3932
 * The WRAPDATELINE=YES option might be specified for circumstances to correct
3933
 * geometries that incorrectly go from a longitude on a side of the antimeridian
3934
 * to the other side, like a LINESTRING(-179 0,179 0) will be transformed to
3935
 * a MULTILINESTRING ((-179 0,-180 0),(180 0,179 0)). For that use case, hCT
3936
 * might be NULL.
3937
 *
3938
 * Supported options in papszOptions are:
3939
 * <ul>
3940
 * <li>WRAPDATELINE=YES</li>
3941
 * <li>DATELINEOFFSET=longitude_gap_in_degree. Defaults to 10.</li>
3942
 * </ul>
3943
 *
3944
 * This is the same as the C function OGR_GeomTransformer_Transform().
3945
 *
3946
 * @param poSrcGeom source geometry
3947
 * @param poCT coordinate transformation object, or NULL.
3948
 * @param papszOptions NULL terminated list of options, or NULL.
3949
 * @param cache Cache. May increase performance if persisted between invocations
3950
 * @return (new) transformed geometry.
3951
 */
3952
OGRGeometry *OGRGeometryFactory::transformWithOptions(
183✔
3953
    const OGRGeometry *poSrcGeom, OGRCoordinateTransformation *poCT,
3954
    char **papszOptions, CPL_UNUSED const TransformWithOptionsCache &cache)
3955
{
3956
    auto poDstGeom = std::unique_ptr<OGRGeometry>(poSrcGeom->clone());
366✔
3957
    if (poCT)
183✔
3958
    {
3959
#ifdef HAVE_GEOS
3960
        bool bNeedPostCorrection = false;
148✔
3961
        const auto poSourceCRS = poCT->GetSourceCS();
148✔
3962
        const auto poTargetCRS = poCT->GetTargetCS();
148✔
3963
        const auto eSrcGeomType = wkbFlatten(poSrcGeom->getGeometryType());
148✔
3964
        // Check if we are transforming from projected coordinates to
3965
        // geographic coordinates, with a chance that there might be polar or
3966
        // anti-meridian discontinuities. If so, create the inverse transform.
3967
        if (eSrcGeomType != wkbPoint && eSrcGeomType != wkbMultiPoint &&
276✔
3968
            (poSourceCRS != cache.d->poSourceCRS ||
128✔
3969
             poTargetCRS != cache.d->poTargetCRS || poCT != cache.d->poCT))
80✔
3970
        {
3971
            cache.d->clear();
48✔
3972
            cache.d->poSourceCRS = poSourceCRS;
48✔
3973
            cache.d->poTargetCRS = poTargetCRS;
48✔
3974
            cache.d->poCT = poCT;
48✔
3975
            if (poSourceCRS && poTargetCRS &&
96✔
3976
                !isTransformWithOptionsRegularTransform(
48✔
3977
                    poSourceCRS, poTargetCRS, papszOptions))
3978
            {
3979
                cache.d->poRevCT.reset(OGRCreateCoordinateTransformation(
20✔
3980
                    poTargetCRS, poSourceCRS));
3981
                cache.d->bIsNorthPolar = false;
20✔
3982
                cache.d->bIsPolar = false;
20✔
3983
                cache.d->poRevCT.reset(poCT->GetInverse());
20✔
3984
                if (cache.d->poRevCT &&
60✔
3985
                    IsPolarToGeographic(poCT, cache.d->poRevCT.get(),
20✔
3986
                                        cache.d->bIsNorthPolar))
40✔
3987
                {
3988
                    cache.d->bIsPolar = true;
5✔
3989
                }
3990
            }
3991
        }
3992

3993
        if (auto poRevCT = cache.d->poRevCT.get())
148✔
3994
        {
3995
            if (cache.d->bIsPolar)
23✔
3996
            {
3997
                poDstGeom = TransformBeforePolarToGeographic(
12✔
3998
                    poRevCT, cache.d->bIsNorthPolar, std::move(poDstGeom),
12✔
3999
                    bNeedPostCorrection);
6✔
4000
            }
4001
            else if (IsAntimeridianProjToGeographic(poCT, poRevCT,
17✔
4002
                                                    poDstGeom.get()))
4003
            {
4004
                poDstGeom = TransformBeforeAntimeridianToGeographic(
30✔
4005
                    poCT, poRevCT, std::move(poDstGeom), bNeedPostCorrection);
30✔
4006
            }
4007
        }
4008
#endif
4009
        OGRErr eErr = poDstGeom->transform(poCT);
148✔
4010
        if (eErr != OGRERR_NONE)
148✔
4011
        {
4012
            return nullptr;
×
4013
        }
4014
#ifdef HAVE_GEOS
4015
        if (bNeedPostCorrection)
148✔
4016
        {
4017
            SnapCoordsCloseToLatLongBounds(poDstGeom.get());
16✔
4018
        }
4019
#endif
4020
    }
4021

4022
    if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "WRAPDATELINE", "NO")))
183✔
4023
    {
4024
        if (poDstGeom->getSpatialReference() &&
101✔
4025
            !poDstGeom->getSpatialReference()->IsGeographic())
49✔
4026
        {
4027
            CPLErrorOnce(
×
4028
                CE_Warning, CPLE_AppDefined,
4029
                "WRAPDATELINE is without effect when reprojecting to a "
4030
                "non-geographic CRS");
4031
            return poDstGeom.release();
×
4032
        }
4033
        // TODO and we should probably also test that the axis order + data axis
4034
        // mapping is long-lat...
4035
        const OGRwkbGeometryType eType =
4036
            wkbFlatten(poDstGeom->getGeometryType());
52✔
4037
        if (eType == wkbPoint)
52✔
4038
        {
4039
            OGRPoint *poDstPoint = poDstGeom->toPoint();
9✔
4040
            WrapPointDateLine(poDstPoint);
9✔
4041
        }
4042
        else if (eType == wkbMultiPoint)
43✔
4043
        {
4044
            for (auto *poDstPoint : *(poDstGeom->toMultiPoint()))
5✔
4045
            {
4046
                WrapPointDateLine(poDstPoint);
4✔
4047
            }
4048
        }
4049
        else
4050
        {
4051
            OGREnvelope sEnvelope;
42✔
4052
            poDstGeom->getEnvelope(&sEnvelope);
42✔
4053
            if (sEnvelope.MinX >= -360.0 && sEnvelope.MaxX <= -180.0)
42✔
4054
                AddOffsetToLon(poDstGeom.get(), 360.0);
2✔
4055
            else if (sEnvelope.MinX >= 180.0 && sEnvelope.MaxX <= 360.0)
40✔
4056
                AddOffsetToLon(poDstGeom.get(), -360.0);
2✔
4057
            else
4058
            {
4059
                OGRwkbGeometryType eNewType;
4060
                if (eType == wkbPolygon || eType == wkbMultiPolygon)
38✔
4061
                    eNewType = wkbMultiPolygon;
27✔
4062
                else if (eType == wkbLineString || eType == wkbMultiLineString)
11✔
4063
                    eNewType = wkbMultiLineString;
10✔
4064
                else
4065
                    eNewType = wkbGeometryCollection;
1✔
4066

4067
                auto poMulti = std::unique_ptr<OGRGeometryCollection>(
4068
                    createGeometry(eNewType)->toGeometryCollection());
76✔
4069

4070
                double dfDateLineOffset = CPLAtofM(
38✔
4071
                    CSLFetchNameValueDef(papszOptions, "DATELINEOFFSET", "10"));
4072
                if (dfDateLineOffset <= 0.0 || dfDateLineOffset >= 360.0)
38✔
4073
                    dfDateLineOffset = 10.0;
×
4074

4075
                CutGeometryOnDateLineAndAddToMulti(
38✔
4076
                    poMulti.get(), poDstGeom.get(), dfDateLineOffset);
38✔
4077

4078
                if (poMulti->getNumGeometries() == 0)
38✔
4079
                {
4080
                    // do nothing
4081
                }
4082
                else if (poMulti->getNumGeometries() == 1 &&
39✔
4083
                         (eType == wkbPolygon || eType == wkbLineString))
1✔
4084
                {
4085
                    poDstGeom = poMulti->stealGeometry(0);
12✔
4086
                }
4087
                else
4088
                {
4089
                    poDstGeom = std::move(poMulti);
26✔
4090
                }
4091
            }
4092
        }
4093
    }
4094

4095
    return poDstGeom.release();
183✔
4096
}
4097

4098
/************************************************************************/
4099
/*                         OGRGeomTransformer()                         */
4100
/************************************************************************/
4101

4102
struct OGRGeomTransformer
4103
{
4104
    std::unique_ptr<OGRCoordinateTransformation> poCT{};
4105
    OGRGeometryFactory::TransformWithOptionsCache cache{};
4106
    CPLStringList aosOptions{};
4107

4108
    OGRGeomTransformer() = default;
6✔
4109
    OGRGeomTransformer(const OGRGeomTransformer &) = delete;
4110
    OGRGeomTransformer &operator=(const OGRGeomTransformer &) = delete;
4111
};
4112

4113
/************************************************************************/
4114
/*                     OGR_GeomTransformer_Create()                     */
4115
/************************************************************************/
4116

4117
/** Create a geometry transformer.
4118
 *
4119
 * This is a enhanced version of OGR_G_Transform().
4120
 *
4121
 * When reprojecting geometries from a Polar Stereographic projection or a
4122
 * projection naturally crossing the antimeridian (like UTM Zone 60) to a
4123
 * geographic CRS, it will cut geometries along the antimeridian. So a
4124
 * LineString might be returned as a MultiLineString.
4125
 *
4126
 * The WRAPDATELINE=YES option might be specified for circumstances to correct
4127
 * geometries that incorrectly go from a longitude on a side of the antimeridian
4128
 * to the other side, like a LINESTRING(-179 0,179 0) will be transformed to
4129
 * a MULTILINESTRING ((-179 0,-180 0),(180 0,179 0)). For that use case, hCT
4130
 * might be NULL.
4131
 *
4132
 * Supported options in papszOptions are:
4133
 * <ul>
4134
 * <li>WRAPDATELINE=YES</li>
4135
 * <li>DATELINEOFFSET=longitude_gap_in_degree. Defaults to 10.</li>
4136
 * </ul>
4137
 *
4138
 * This is the same as the C++ method OGRGeometryFactory::transformWithOptions().
4139

4140
 * @param hCT Coordinate transformation object (will be cloned) or NULL.
4141
 * @param papszOptions NULL terminated list of options, or NULL.
4142
 * @return transformer object to free with OGR_GeomTransformer_Destroy()
4143
 * @since GDAL 3.1
4144
 */
4145
OGRGeomTransformerH OGR_GeomTransformer_Create(OGRCoordinateTransformationH hCT,
6✔
4146
                                               CSLConstList papszOptions)
4147
{
4148
    OGRGeomTransformer *transformer = new OGRGeomTransformer;
6✔
4149
    if (hCT)
6✔
4150
    {
4151
        transformer->poCT.reset(
3✔
4152
            OGRCoordinateTransformation::FromHandle(hCT)->Clone());
3✔
4153
    }
4154
    transformer->aosOptions.Assign(CSLDuplicate(papszOptions));
6✔
4155
    return transformer;
6✔
4156
}
4157

4158
/************************************************************************/
4159
/*                     OGR_GeomTransformer_Transform()                  */
4160
/************************************************************************/
4161

4162
/** Transforms a geometry.
4163
 *
4164
 * @param hTransformer transformer object.
4165
 * @param hGeom Source geometry.
4166
 * @return a new geometry (or NULL) to destroy with OGR_G_DestroyGeometry()
4167
 * @since GDAL 3.1
4168
 */
4169
OGRGeometryH OGR_GeomTransformer_Transform(OGRGeomTransformerH hTransformer,
6✔
4170
                                           OGRGeometryH hGeom)
4171
{
4172
    VALIDATE_POINTER1(hTransformer, "OGR_GeomTransformer_Transform", nullptr);
6✔
4173
    VALIDATE_POINTER1(hGeom, "OGR_GeomTransformer_Transform", nullptr);
6✔
4174

4175
    return OGRGeometry::ToHandle(OGRGeometryFactory::transformWithOptions(
12✔
4176
        OGRGeometry::FromHandle(hGeom), hTransformer->poCT.get(),
6✔
4177
        hTransformer->aosOptions.List(), hTransformer->cache));
12✔
4178
}
4179

4180
/************************************************************************/
4181
/*                      OGR_GeomTransformer_Destroy()                   */
4182
/************************************************************************/
4183

4184
/** Destroy a geometry transformer allocated with OGR_GeomTransformer_Create()
4185
 *
4186
 * @param hTransformer transformer object.
4187
 * @since GDAL 3.1
4188
 */
4189
void OGR_GeomTransformer_Destroy(OGRGeomTransformerH hTransformer)
6✔
4190
{
4191
    delete hTransformer;
6✔
4192
}
6✔
4193

4194
/************************************************************************/
4195
/*                OGRGeometryFactory::GetDefaultArcStepSize()           */
4196
/************************************************************************/
4197

4198
/** Return the default value of the angular step used when stroking curves
4199
 * as lines. Defaults to 4 degrees.
4200
 * Can be modified by setting the OGR_ARC_STEPSIZE configuration option.
4201
 * Valid values are in [1e-2, 180] degree range.
4202
 * @since 3.11
4203
 */
4204

4205
/* static */
4206
double OGRGeometryFactory::GetDefaultArcStepSize()
4,376✔
4207
{
4208
    const double dfVal = CPLAtofM(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4"));
4,376✔
4209
    constexpr double MIN_VAL = 1e-2;
4,376✔
4210
    if (dfVal < MIN_VAL)
4,376✔
4211
    {
4212
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
1✔
4213
                     "Too small value for OGR_ARC_STEPSIZE. Clamping it to %f",
4214
                     MIN_VAL);
4215
        return MIN_VAL;
1✔
4216
    }
4217
    constexpr double MAX_VAL = 180;
4,375✔
4218
    if (dfVal > MAX_VAL)
4,375✔
4219
    {
4220
        CPLErrorOnce(CE_Warning, CPLE_AppDefined,
1✔
4221
                     "Too large value for OGR_ARC_STEPSIZE. Clamping it to %f",
4222
                     MAX_VAL);
4223
        return MAX_VAL;
1✔
4224
    }
4225
    return dfVal;
4,374✔
4226
}
4227

4228
/************************************************************************/
4229
/*                              DISTANCE()                              */
4230
/************************************************************************/
4231

4232
static inline double DISTANCE(double x1, double y1, double x2, double y2)
311,113✔
4233
{
4234
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
311,113✔
4235
}
4236

4237
/************************************************************************/
4238
/*                        approximateArcAngles()                        */
4239
/************************************************************************/
4240

4241
/**
4242
 * Stroke arc to linestring.
4243
 *
4244
 * Stroke an arc of a circle to a linestring based on a center
4245
 * point, radius, start angle and end angle, all angles in degrees.
4246
 *
4247
 * If the dfMaxAngleStepSizeDegrees is zero, then a default value will be
4248
 * used.  This is currently 4 degrees unless the user has overridden the
4249
 * value with the OGR_ARC_STEPSIZE configuration variable.
4250
 *
4251
 * If the OGR_ARC_MAX_GAP configuration variable is set, the straight-line
4252
 * distance between adjacent pairs of interpolated points will be limited to
4253
 * the specified distance. If the distance between a pair of points exceeds
4254
 * this maximum, additional points are interpolated between the two points.
4255
 *
4256
 * @see CPLSetConfigOption()
4257
 *
4258
 * @param dfCenterX center X
4259
 * @param dfCenterY center Y
4260
 * @param dfZ center Z
4261
 * @param dfPrimaryRadius X radius of ellipse.
4262
 * @param dfSecondaryRadius Y radius of ellipse.
4263
 * @param dfRotation rotation of the ellipse clockwise.
4264
 * @param dfStartAngle angle to first point on arc (clockwise of X-positive)
4265
 * @param dfEndAngle angle to last point on arc (clockwise of X-positive)
4266
 * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
4267
 * arc, zero to use the default setting.
4268
 * @param bUseMaxGap Optional: whether to honor OGR_ARC_MAX_GAP.
4269
 *
4270
 * @return OGRLineString geometry representing an approximation of the arc.
4271
 *
4272
 * @since OGR 1.8.0
4273
 */
4274

4275
OGRGeometry *OGRGeometryFactory::approximateArcAngles(
118✔
4276
    double dfCenterX, double dfCenterY, double dfZ, double dfPrimaryRadius,
4277
    double dfSecondaryRadius, double dfRotation, double dfStartAngle,
4278
    double dfEndAngle, double dfMaxAngleStepSizeDegrees,
4279
    const bool bUseMaxGap /* = false */)
4280

4281
{
4282
    OGRLineString *poLine = new OGRLineString();
118✔
4283
    const double dfRotationRadians = dfRotation * M_PI / 180.0;
118✔
4284

4285
    // Support default arc step setting.
4286
    if (dfMaxAngleStepSizeDegrees < 1e-6)
118✔
4287
    {
4288
        dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
117✔
4289
    }
4290

4291
    // Determine maximum interpolation gap. This is the largest straight-line
4292
    // distance allowed between pairs of interpolated points. Default zero,
4293
    // meaning no gap.
4294
    // coverity[tainted_data]
4295
    const double dfMaxInterpolationGap =
4296
        bUseMaxGap ? CPLAtofM(CPLGetConfigOption("OGR_ARC_MAX_GAP", "0")) : 0.0;
118✔
4297

4298
    // Is this a full circle?
4299
    const bool bIsFullCircle = fabs(dfEndAngle - dfStartAngle) == 360.0;
118✔
4300

4301
    // Switch direction.
4302
    dfStartAngle *= -1;
118✔
4303
    dfEndAngle *= -1;
118✔
4304

4305
    // Figure out the number of slices to make this into.
4306
    int nVertexCount =
4307
        std::max(2, static_cast<int>(ceil(fabs(dfEndAngle - dfStartAngle) /
236✔
4308
                                          dfMaxAngleStepSizeDegrees) +
118✔
4309
                                     1));
118✔
4310
    const double dfSlice = (dfEndAngle - dfStartAngle) / (nVertexCount - 1);
118✔
4311

4312
    // If it is a full circle we will work out the last point separately.
4313
    if (bIsFullCircle)
118✔
4314
    {
4315
        nVertexCount--;
52✔
4316
    }
4317

4318
    /* -------------------------------------------------------------------- */
4319
    /*      Compute the interpolated points.                                */
4320
    /* -------------------------------------------------------------------- */
4321
    double dfLastX = 0.0;
118✔
4322
    double dfLastY = 0.0;
118✔
4323
    int nTotalAddPoints = 0;
118✔
4324
    for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
7,071✔
4325
    {
4326
        const double dfAngleOnEllipse =
6,953✔
4327
            (dfStartAngle + iPoint * dfSlice) * M_PI / 180.0;
6,953✔
4328

4329
        // Compute position on the unrotated ellipse.
4330
        const double dfEllipseX = cos(dfAngleOnEllipse) * dfPrimaryRadius;
6,953✔
4331
        const double dfEllipseY = sin(dfAngleOnEllipse) * dfSecondaryRadius;
6,953✔
4332

4333
        // Is this point too far from the previous point?
4334
        if (iPoint && dfMaxInterpolationGap != 0.0)
6,953✔
4335
        {
4336
            const double dfDistFromLast =
4337
                DISTANCE(dfLastX, dfLastY, dfEllipseX, dfEllipseY);
1✔
4338

4339
            if (dfDistFromLast > dfMaxInterpolationGap)
1✔
4340
            {
4341
                const int nAddPoints =
1✔
4342
                    static_cast<int>(dfDistFromLast / dfMaxInterpolationGap);
1✔
4343
                const double dfAddSlice = dfSlice / (nAddPoints + 1);
1✔
4344

4345
                // Interpolate additional points
4346
                for (int iAddPoint = 0; iAddPoint < nAddPoints; iAddPoint++)
3✔
4347
                {
4348
                    const double dfAddAngleOnEllipse =
2✔
4349
                        (dfStartAngle + (iPoint - 1) * dfSlice +
2✔
4350
                         (iAddPoint + 1) * dfAddSlice) *
2✔
4351
                        (M_PI / 180.0);
4352

4353
                    poLine->setPoint(
2✔
4354
                        iPoint + nTotalAddPoints + iAddPoint,
2✔
4355
                        cos(dfAddAngleOnEllipse) * dfPrimaryRadius,
2✔
4356
                        sin(dfAddAngleOnEllipse) * dfSecondaryRadius, dfZ);
2✔
4357
                }
4358

4359
                nTotalAddPoints += nAddPoints;
1✔
4360
            }
4361
        }
4362

4363
        poLine->setPoint(iPoint + nTotalAddPoints, dfEllipseX, dfEllipseY, dfZ);
6,953✔
4364
        dfLastX = dfEllipseX;
6,953✔
4365
        dfLastY = dfEllipseY;
6,953✔
4366
    }
4367

4368
    /* -------------------------------------------------------------------- */
4369
    /*      Rotate and translate the ellipse.                               */
4370
    /* -------------------------------------------------------------------- */
4371
    nVertexCount = poLine->getNumPoints();
118✔
4372
    for (int iPoint = 0; iPoint < nVertexCount; iPoint++)
7,073✔
4373
    {
4374
        const double dfEllipseX = poLine->getX(iPoint);
6,955✔
4375
        const double dfEllipseY = poLine->getY(iPoint);
6,955✔
4376

4377
        // Rotate this position around the center of the ellipse.
4378
        const double dfArcX = dfCenterX + dfEllipseX * cos(dfRotationRadians) +
6,955✔
4379
                              dfEllipseY * sin(dfRotationRadians);
6,955✔
4380
        const double dfArcY = dfCenterY - dfEllipseX * sin(dfRotationRadians) +
6,955✔
4381
                              dfEllipseY * cos(dfRotationRadians);
6,955✔
4382

4383
        poLine->setPoint(iPoint, dfArcX, dfArcY, dfZ);
6,955✔
4384
    }
4385

4386
    /* -------------------------------------------------------------------- */
4387
    /*      If we're asked to make a full circle, ensure the start and      */
4388
    /*      end points coincide exactly, in spite of any rounding error.    */
4389
    /* -------------------------------------------------------------------- */
4390
    if (bIsFullCircle)
118✔
4391
    {
4392
        OGRPoint oPoint;
104✔
4393
        poLine->getPoint(0, &oPoint);
52✔
4394
        poLine->setPoint(nVertexCount, &oPoint);
52✔
4395
    }
4396

4397
    return poLine;
118✔
4398
}
4399

4400
/************************************************************************/
4401
/*                     OGR_G_ApproximateArcAngles()                     */
4402
/************************************************************************/
4403

4404
/**
4405
 * Stroke arc to linestring.
4406
 *
4407
 * Stroke an arc of a circle to a linestring based on a center
4408
 * point, radius, start angle and end angle, all angles in degrees.
4409
 *
4410
 * If the dfMaxAngleStepSizeDegrees is zero, then a default value will be
4411
 * used.  This is currently 4 degrees unless the user has overridden the
4412
 * value with the OGR_ARC_STEPSIZE configuration variable.
4413
 *
4414
 * @see CPLSetConfigOption()
4415
 *
4416
 * @param dfCenterX center X
4417
 * @param dfCenterY center Y
4418
 * @param dfZ center Z
4419
 * @param dfPrimaryRadius X radius of ellipse.
4420
 * @param dfSecondaryRadius Y radius of ellipse.
4421
 * @param dfRotation rotation of the ellipse clockwise.
4422
 * @param dfStartAngle angle to first point on arc (clockwise of X-positive)
4423
 * @param dfEndAngle angle to last point on arc (clockwise of X-positive)
4424
 * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
4425
 * arc, zero to use the default setting.
4426
 *
4427
 * @return OGRLineString geometry representing an approximation of the arc.
4428
 *
4429
 * @since OGR 1.8.0
4430
 */
4431

4432
OGRGeometryH CPL_DLL OGR_G_ApproximateArcAngles(
1✔
4433
    double dfCenterX, double dfCenterY, double dfZ, double dfPrimaryRadius,
4434
    double dfSecondaryRadius, double dfRotation, double dfStartAngle,
4435
    double dfEndAngle, double dfMaxAngleStepSizeDegrees)
4436

4437
{
4438
    return OGRGeometry::ToHandle(OGRGeometryFactory::approximateArcAngles(
1✔
4439
        dfCenterX, dfCenterY, dfZ, dfPrimaryRadius, dfSecondaryRadius,
4440
        dfRotation, dfStartAngle, dfEndAngle, dfMaxAngleStepSizeDegrees));
1✔
4441
}
4442

4443
/************************************************************************/
4444
/*                           forceToLineString()                        */
4445
/************************************************************************/
4446

4447
/**
4448
 * \brief Convert to line string.
4449
 *
4450
 * Tries to force the provided geometry to be a line string.  This nominally
4451
 * effects a change on multilinestrings.
4452
 * In GDAL 2.0, for polygons or curvepolygons that have a single exterior ring,
4453
 * it will return the ring. For circular strings or compound curves, it will
4454
 * return an approximated line string.
4455
 *
4456
 * The passed in geometry is
4457
 * consumed and a new one returned (or potentially the same one).
4458
 *
4459
 * @param poGeom the input geometry - ownership is passed to the method.
4460
 * @param bOnlyInOrder flag that, if set to FALSE, indicate that the order of
4461
 *                     points in a linestring might be reversed if it enables
4462
 *                     to match the extremity of another linestring. If set
4463
 *                     to TRUE, the start of a linestring must match the end
4464
 *                     of another linestring.
4465
 * @return new geometry.
4466
 */
4467

4468
OGRGeometry *OGRGeometryFactory::forceToLineString(OGRGeometry *poGeom,
177✔
4469
                                                   bool bOnlyInOrder)
4470

4471
{
4472
    if (poGeom == nullptr)
177✔
4473
        return nullptr;
2✔
4474

4475
    const OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
175✔
4476

4477
    /* -------------------------------------------------------------------- */
4478
    /*      If this is already a LineString, nothing to do                  */
4479
    /* -------------------------------------------------------------------- */
4480
    if (eGeomType == wkbLineString)
175✔
4481
    {
4482
        // Except if it is a linearring.
4483
        poGeom = OGRCurve::CastToLineString(poGeom->toCurve());
25✔
4484

4485
        return poGeom;
25✔
4486
    }
4487

4488
    /* -------------------------------------------------------------------- */
4489
    /*      If it is a polygon with a single ring, return it                 */
4490
    /* -------------------------------------------------------------------- */
4491
    if (eGeomType == wkbPolygon || eGeomType == wkbCurvePolygon)
150✔
4492
    {
4493
        OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
30✔
4494
        if (poCP->getNumInteriorRings() == 0)
30✔
4495
        {
4496
            OGRCurve *poRing = poCP->stealExteriorRingCurve();
28✔
4497
            delete poCP;
28✔
4498
            return forceToLineString(poRing);
28✔
4499
        }
4500
        return poGeom;
2✔
4501
    }
4502

4503
    /* -------------------------------------------------------------------- */
4504
    /*      If it is a curve line, call CurveToLine()                        */
4505
    /* -------------------------------------------------------------------- */
4506
    if (eGeomType == wkbCircularString || eGeomType == wkbCompoundCurve)
120✔
4507
    {
4508
        OGRGeometry *poNewGeom = poGeom->toCurve()->CurveToLine();
69✔
4509
        delete poGeom;
69✔
4510
        return poNewGeom;
69✔
4511
    }
4512

4513
    if (eGeomType != wkbGeometryCollection && eGeomType != wkbMultiLineString &&
51✔
4514
        eGeomType != wkbMultiCurve)
4515
        return poGeom;
20✔
4516

4517
    // Build an aggregated linestring from all the linestrings in the container.
4518
    OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
31✔
4519
    if (poGeom->hasCurveGeometry())
31✔
4520
    {
4521
        OGRGeometryCollection *poNewGC =
4522
            poGC->getLinearGeometry()->toGeometryCollection();
7✔
4523
        delete poGC;
7✔
4524
        poGC = poNewGC;
7✔
4525
    }
4526

4527
    if (poGC->getNumGeometries() == 0)
31✔
4528
    {
4529
        poGeom = new OGRLineString();
3✔
4530
        poGeom->assignSpatialReference(poGC->getSpatialReference());
3✔
4531
        delete poGC;
3✔
4532
        return poGeom;
3✔
4533
    }
4534

4535
    int iGeom0 = 0;
28✔
4536
    while (iGeom0 < poGC->getNumGeometries())
69✔
4537
    {
4538
        if (wkbFlatten(poGC->getGeometryRef(iGeom0)->getGeometryType()) !=
41✔
4539
            wkbLineString)
4540
        {
4541
            iGeom0++;
12✔
4542
            continue;
26✔
4543
        }
4544

4545
        OGRLineString *poLineString0 =
4546
            poGC->getGeometryRef(iGeom0)->toLineString();
29✔
4547
        if (poLineString0->getNumPoints() < 2)
29✔
4548
        {
4549
            iGeom0++;
14✔
4550
            continue;
14✔
4551
        }
4552

4553
        OGRPoint pointStart0;
30✔
4554
        poLineString0->StartPoint(&pointStart0);
15✔
4555
        OGRPoint pointEnd0;
30✔
4556
        poLineString0->EndPoint(&pointEnd0);
15✔
4557

4558
        int iGeom1 = iGeom0 + 1;  // Used after for.
15✔
4559
        for (; iGeom1 < poGC->getNumGeometries(); iGeom1++)
17✔
4560
        {
4561
            if (wkbFlatten(poGC->getGeometryRef(iGeom1)->getGeometryType()) !=
6✔
4562
                wkbLineString)
4563
                continue;
1✔
4564

4565
            OGRLineString *poLineString1 =
4566
                poGC->getGeometryRef(iGeom1)->toLineString();
6✔
4567
            if (poLineString1->getNumPoints() < 2)
6✔
4568
                continue;
1✔
4569

4570
            OGRPoint pointStart1;
5✔
4571
            poLineString1->StartPoint(&pointStart1);
5✔
4572
            OGRPoint pointEnd1;
5✔
4573
            poLineString1->EndPoint(&pointEnd1);
5✔
4574

4575
            if (!bOnlyInOrder && (pointEnd0.Equals(&pointEnd1) ||
5✔
4576
                                  pointStart0.Equals(&pointStart1)))
×
4577
            {
4578
                poLineString1->reversePoints();
×
4579
                poLineString1->StartPoint(&pointStart1);
×
4580
                poLineString1->EndPoint(&pointEnd1);
×
4581
            }
4582

4583
            if (pointEnd0.Equals(&pointStart1))
5✔
4584
            {
4585
                poLineString0->addSubLineString(poLineString1, 1);
4✔
4586
                poGC->removeGeometry(iGeom1);
4✔
4587
                break;
4✔
4588
            }
4589

4590
            if (pointEnd1.Equals(&pointStart0))
1✔
4591
            {
4592
                poLineString1->addSubLineString(poLineString0, 1);
×
4593
                poGC->removeGeometry(iGeom0);
×
4594
                break;
×
4595
            }
4596
        }
4597

4598
        if (iGeom1 == poGC->getNumGeometries())
15✔
4599
        {
4600
            iGeom0++;
14✔
4601
        }
4602
    }
4603

4604
    if (poGC->getNumGeometries() == 1)
28✔
4605
    {
4606
        OGRGeometry *poSingleGeom = poGC->getGeometryRef(0);
20✔
4607
        poGC->removeGeometry(0, FALSE);
20✔
4608
        delete poGC;
20✔
4609

4610
        return poSingleGeom;
20✔
4611
    }
4612

4613
    return poGC;
8✔
4614
}
4615

4616
/************************************************************************/
4617
/*                      OGR_G_ForceToLineString()                       */
4618
/************************************************************************/
4619

4620
/**
4621
 * \brief Convert to line string.
4622
 *
4623
 * This function is the same as the C++ method
4624
 * OGRGeometryFactory::forceToLineString().
4625
 *
4626
 * @param hGeom handle to the geometry to convert (ownership surrendered).
4627
 * @return the converted geometry (ownership to caller).
4628
 *
4629
 * @since GDAL/OGR 1.10.0
4630
 */
4631

4632
OGRGeometryH OGR_G_ForceToLineString(OGRGeometryH hGeom)
60✔
4633

4634
{
4635
    return OGRGeometry::ToHandle(
60✔
4636
        OGRGeometryFactory::forceToLineString(OGRGeometry::FromHandle(hGeom)));
60✔
4637
}
4638

4639
/************************************************************************/
4640
/*                           forceTo()                                  */
4641
/************************************************************************/
4642

4643
/**
4644
 * \brief Convert to another geometry type
4645
 *
4646
 * Tries to force the provided geometry to the specified geometry type.
4647
 *
4648
 * It can promote 'single' geometry type to their corresponding collection type
4649
 * (see OGR_GT_GetCollection()) or the reverse. non-linear geometry type to
4650
 * their corresponding linear geometry type (see OGR_GT_GetLinear()), by
4651
 * possibly approximating circular arcs they may contain.  Regarding conversion
4652
 * from linear geometry types to curve geometry types, only "wrapping" will be
4653
 * done. No attempt to retrieve potential circular arcs by de-approximating
4654
 * stroking will be done. For that, OGRGeometry::getCurveGeometry() can be used.
4655
 *
4656
 * The passed in geometry is consumed and a new one returned (or potentially the
4657
 * same one).
4658
 *
4659
 * Starting with GDAL 3.9, this method honours the dimensionality of eTargetType.
4660
 *
4661
 * @param poGeom the input geometry - ownership is passed to the method.
4662
 * @param eTargetType target output geometry type.
4663
 * @param papszOptions options as a null-terminated list of strings or NULL.
4664
 * @return new geometry, or nullptr in case of error.
4665
 *
4666
 * @since GDAL 2.0
4667
 */
4668

4669
OGRGeometry *OGRGeometryFactory::forceTo(OGRGeometry *poGeom,
5,034✔
4670
                                         OGRwkbGeometryType eTargetType,
4671
                                         const char *const *papszOptions)
4672
{
4673
    if (poGeom == nullptr)
5,034✔
4674
        return poGeom;
×
4675

4676
    const OGRwkbGeometryType eTargetTypeFlat = wkbFlatten(eTargetType);
5,034✔
4677
    if (eTargetTypeFlat == wkbUnknown)
5,034✔
4678
        return poGeom;
268✔
4679

4680
    if (poGeom->IsEmpty())
4,766✔
4681
    {
4682
        OGRGeometry *poRet = createGeometry(eTargetType);
277✔
4683
        if (poRet)
277✔
4684
        {
4685
            poRet->assignSpatialReference(poGeom->getSpatialReference());
277✔
4686
            poRet->set3D(OGR_GT_HasZ(eTargetType));
277✔
4687
            poRet->setMeasured(OGR_GT_HasM(eTargetType));
277✔
4688
        }
4689
        delete poGeom;
277✔
4690
        return poRet;
277✔
4691
    }
4692

4693
    OGRwkbGeometryType eType = poGeom->getGeometryType();
4,489✔
4694
    OGRwkbGeometryType eTypeFlat = wkbFlatten(eType);
4,489✔
4695

4696
    if (eTargetTypeFlat != eTargetType && (eType == eTypeFlat))
4,489✔
4697
    {
4698
        auto poGeomNew = forceTo(poGeom, eTargetTypeFlat, papszOptions);
66✔
4699
        if (poGeomNew)
66✔
4700
        {
4701
            poGeomNew->set3D(OGR_GT_HasZ(eTargetType));
66✔
4702
            poGeomNew->setMeasured(OGR_GT_HasM(eTargetType));
66✔
4703
        }
4704
        return poGeomNew;
66✔
4705
    }
4706

4707
    if (eTypeFlat == eTargetTypeFlat)
4,423✔
4708
    {
4709
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
535✔
4710
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
535✔
4711
        return poGeom;
535✔
4712
    }
4713

4714
    eType = eTypeFlat;
3,888✔
4715

4716
    if (OGR_GT_IsSubClassOf(eType, wkbPolyhedralSurface) &&
5,609✔
4717
        (eTargetTypeFlat == wkbMultiSurface ||
1,721✔
4718
         eTargetTypeFlat == wkbGeometryCollection))
4719
    {
4720
        OGRwkbGeometryType eTempGeomType = wkbMultiPolygon;
853✔
4721
        if (OGR_GT_HasZ(eTargetType))
853✔
4722
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
849✔
4723
        if (OGR_GT_HasM(eTargetType))
853✔
4724
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
×
4725
        return forceTo(forceTo(poGeom, eTempGeomType, papszOptions),
853✔
4726
                       eTargetType, papszOptions);
853✔
4727
    }
4728

4729
    if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
3,035✔
4730
        eTargetTypeFlat == wkbGeometryCollection)
4731
    {
4732
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
920✔
4733
        auto poRet = OGRGeometryCollection::CastToGeometryCollection(poGC);
920✔
4734
        poRet->set3D(OGR_GT_HasZ(eTargetType));
920✔
4735
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
920✔
4736
        return poRet;
920✔
4737
    }
4738

4739
    if (eType == wkbTriangle && eTargetTypeFlat == wkbPolyhedralSurface)
2,115✔
4740
    {
4741
        OGRPolyhedralSurface *poPS = new OGRPolyhedralSurface();
1✔
4742
        poPS->assignSpatialReference(poGeom->getSpatialReference());
1✔
4743
        poPS->addGeometryDirectly(OGRTriangle::CastToPolygon(poGeom));
1✔
4744
        poPS->set3D(OGR_GT_HasZ(eTargetType));
1✔
4745
        poPS->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4746
        return poPS;
1✔
4747
    }
4748
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbPolyhedralSurface)
2,114✔
4749
    {
4750
        OGRPolyhedralSurface *poPS = new OGRPolyhedralSurface();
3✔
4751
        poPS->assignSpatialReference(poGeom->getSpatialReference());
3✔
4752
        poPS->addGeometryDirectly(poGeom);
3✔
4753
        poPS->set3D(OGR_GT_HasZ(eTargetType));
3✔
4754
        poPS->setMeasured(OGR_GT_HasM(eTargetType));
3✔
4755
        return poPS;
3✔
4756
    }
4757
    else if (eType == wkbMultiPolygon &&
2,111✔
4758
             eTargetTypeFlat == wkbPolyhedralSurface)
4759
    {
4760
        OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
2✔
4761
        OGRPolyhedralSurface *poPS = new OGRPolyhedralSurface();
2✔
4762
        for (int i = 0; i < poMP->getNumGeometries(); ++i)
4✔
4763
        {
4764
            poPS->addGeometry(poMP->getGeometryRef(i));
2✔
4765
        }
4766
        delete poGeom;
2✔
4767
        poPS->set3D(OGR_GT_HasZ(eTargetType));
2✔
4768
        poPS->setMeasured(OGR_GT_HasM(eTargetType));
2✔
4769
        return poPS;
2✔
4770
    }
4771
    else if (eType == wkbTIN && eTargetTypeFlat == wkbPolyhedralSurface)
2,109✔
4772
    {
4773
        poGeom = OGRTriangulatedSurface::CastToPolyhedralSurface(
1✔
4774
            poGeom->toTriangulatedSurface());
4775
    }
4776
    else if (eType == wkbCurvePolygon &&
2,108✔
4777
             eTargetTypeFlat == wkbPolyhedralSurface)
4778
    {
4779
        OGRwkbGeometryType eTempGeomType = wkbPolygon;
1✔
4780
        if (OGR_GT_HasZ(eTargetType))
1✔
4781
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
×
4782
        if (OGR_GT_HasM(eTargetType))
1✔
4783
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
×
4784
        return forceTo(forceTo(poGeom, eTempGeomType, papszOptions),
1✔
4785
                       eTargetType, papszOptions);
1✔
4786
    }
4787
    else if (eType == wkbMultiSurface &&
2,107✔
4788
             eTargetTypeFlat == wkbPolyhedralSurface)
4789
    {
4790
        OGRwkbGeometryType eTempGeomType = wkbMultiPolygon;
1✔
4791
        if (OGR_GT_HasZ(eTargetType))
1✔
4792
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
×
4793
        if (OGR_GT_HasM(eTargetType))
1✔
4794
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
×
4795
        return forceTo(forceTo(poGeom, eTempGeomType, papszOptions),
1✔
4796
                       eTargetType, papszOptions);
1✔
4797
    }
4798

4799
    else if (eType == wkbTriangle && eTargetTypeFlat == wkbTIN)
2,106✔
4800
    {
4801
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
1✔
4802
        poTS->assignSpatialReference(poGeom->getSpatialReference());
1✔
4803
        poTS->addGeometryDirectly(poGeom);
1✔
4804
        poTS->set3D(OGR_GT_HasZ(eTargetType));
1✔
4805
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4806
        return poTS;
1✔
4807
    }
4808
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbTIN)
2,105✔
4809
    {
4810
        OGRPolygon *poPoly = poGeom->toPolygon();
4✔
4811
        OGRLinearRing *poLR = poPoly->getExteriorRing();
4✔
4812
        if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
7✔
4813
              poPoly->getNumInteriorRings() == 0))
3✔
4814
        {
4815
            return poGeom;
1✔
4816
        }
4817
        OGRErr eErr = OGRERR_NONE;
3✔
4818
        OGRTriangle *poTriangle = new OGRTriangle(*poPoly, eErr);
3✔
4819
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
3✔
4820
        poTS->assignSpatialReference(poGeom->getSpatialReference());
3✔
4821
        poTS->addGeometryDirectly(poTriangle);
3✔
4822
        delete poGeom;
3✔
4823
        poTS->set3D(OGR_GT_HasZ(eTargetType));
3✔
4824
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
3✔
4825
        return poTS;
3✔
4826
    }
4827
    else if (eType == wkbMultiPolygon && eTargetTypeFlat == wkbTIN)
2,101✔
4828
    {
4829
        OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
1✔
4830
        for (const auto poPoly : *poMP)
2✔
4831
        {
4832
            const OGRLinearRing *poLR = poPoly->getExteriorRing();
1✔
4833
            if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
2✔
4834
                  poPoly->getNumInteriorRings() == 0))
1✔
4835
            {
4836
                return poGeom;
×
4837
            }
4838
        }
4839
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
1✔
4840
        poTS->assignSpatialReference(poGeom->getSpatialReference());
1✔
4841
        for (const auto poPoly : *poMP)
2✔
4842
        {
4843
            OGRErr eErr = OGRERR_NONE;
1✔
4844
            poTS->addGeometryDirectly(new OGRTriangle(*poPoly, eErr));
1✔
4845
        }
4846
        delete poGeom;
1✔
4847
        poTS->set3D(OGR_GT_HasZ(eTargetType));
1✔
4848
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4849
        return poTS;
1✔
4850
    }
4851
    else if (eType == wkbPolyhedralSurface && eTargetTypeFlat == wkbTIN)
2,100✔
4852
    {
4853
        OGRPolyhedralSurface *poPS = poGeom->toPolyhedralSurface();
2✔
4854
        for (const auto poPoly : *poPS)
3✔
4855
        {
4856
            const OGRLinearRing *poLR = poPoly->getExteriorRing();
2✔
4857
            if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
3✔
4858
                  poPoly->getNumInteriorRings() == 0))
1✔
4859
            {
4860
                poGeom->set3D(OGR_GT_HasZ(eTargetType));
1✔
4861
                poGeom->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4862
                return poGeom;
1✔
4863
            }
4864
        }
4865
        OGRTriangulatedSurface *poTS = new OGRTriangulatedSurface();
1✔
4866
        poTS->assignSpatialReference(poGeom->getSpatialReference());
1✔
4867
        for (const auto poPoly : *poPS)
2✔
4868
        {
4869
            OGRErr eErr = OGRERR_NONE;
1✔
4870
            poTS->addGeometryDirectly(new OGRTriangle(*poPoly, eErr));
1✔
4871
        }
4872
        delete poGeom;
1✔
4873
        poTS->set3D(OGR_GT_HasZ(eTargetType));
1✔
4874
        poTS->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4875
        return poTS;
1✔
4876
    }
4877

4878
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbTriangle)
2,098✔
4879
    {
4880
        OGRPolygon *poPoly = poGeom->toPolygon();
7✔
4881
        OGRLinearRing *poLR = poPoly->getExteriorRing();
7✔
4882
        if (!(poLR != nullptr && poLR->getNumPoints() == 4 &&
13✔
4883
              poPoly->getNumInteriorRings() == 0))
6✔
4884
        {
4885
            poGeom->set3D(OGR_GT_HasZ(eTargetType));
1✔
4886
            poGeom->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4887
            return poGeom;
1✔
4888
        }
4889
        OGRErr eErr = OGRERR_NONE;
6✔
4890
        OGRTriangle *poTriangle = new OGRTriangle(*poPoly, eErr);
6✔
4891
        delete poGeom;
6✔
4892
        poTriangle->set3D(OGR_GT_HasZ(eTargetType));
6✔
4893
        poTriangle->setMeasured(OGR_GT_HasM(eTargetType));
6✔
4894
        return poTriangle;
6✔
4895
    }
4896

4897
    if (eTargetTypeFlat == wkbTriangle || eTargetTypeFlat == wkbTIN ||
2,092✔
4898
        eTargetTypeFlat == wkbPolyhedralSurface)
4899
    {
4900
        OGRwkbGeometryType eTempGeomType = wkbPolygon;
9✔
4901
        if (OGR_GT_HasZ(eTargetType))
9✔
4902
            eTempGeomType = OGR_GT_SetZ(eTempGeomType);
×
4903
        if (OGR_GT_HasM(eTargetType))
9✔
4904
            eTempGeomType = OGR_GT_SetM(eTempGeomType);
1✔
4905
        OGRGeometry *poPoly = forceTo(poGeom, eTempGeomType, papszOptions);
9✔
4906
        if (poPoly == poGeom)
9✔
4907
            return poGeom;
×
4908
        return forceTo(poPoly, eTargetType, papszOptions);
9✔
4909
    }
4910

4911
    if (eType == wkbTriangle && eTargetTypeFlat == wkbGeometryCollection)
2,083✔
4912
    {
4913
        OGRGeometryCollection *poGC = new OGRGeometryCollection();
1✔
4914
        poGC->assignSpatialReference(poGeom->getSpatialReference());
1✔
4915
        poGC->addGeometryDirectly(poGeom);
1✔
4916
        poGC->set3D(OGR_GT_HasZ(eTargetType));
1✔
4917
        poGC->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4918
        return poGC;
1✔
4919
    }
4920

4921
    // Promote single to multi.
4922
    if (!OGR_GT_IsSubClassOf(eType, wkbGeometryCollection) &&
3,878✔
4923
        OGR_GT_IsSubClassOf(OGR_GT_GetCollection(eType), eTargetType))
1,796✔
4924
    {
4925
        OGRGeometry *poRet = createGeometry(eTargetType);
497✔
4926
        if (poRet == nullptr)
497✔
4927
        {
4928
            delete poGeom;
×
4929
            return nullptr;
×
4930
        }
4931
        poRet->assignSpatialReference(poGeom->getSpatialReference());
497✔
4932
        if (eType == wkbLineString)
497✔
4933
            poGeom = OGRCurve::CastToLineString(poGeom->toCurve());
43✔
4934
        poRet->toGeometryCollection()->addGeometryDirectly(poGeom);
497✔
4935
        poRet->set3D(OGR_GT_HasZ(eTargetType));
497✔
4936
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
497✔
4937
        return poRet;
497✔
4938
    }
4939

4940
    const bool bIsCurve = CPL_TO_BOOL(OGR_GT_IsCurve(eType));
1,585✔
4941
    if (bIsCurve && eTargetTypeFlat == wkbCompoundCurve)
1,585✔
4942
    {
4943
        auto poRet = OGRCurve::CastToCompoundCurve(poGeom->toCurve());
32✔
4944
        if (poRet)
32✔
4945
        {
4946
            poRet->set3D(OGR_GT_HasZ(eTargetType));
30✔
4947
            poRet->setMeasured(OGR_GT_HasM(eTargetType));
30✔
4948
        }
4949
        return poRet;
32✔
4950
    }
4951
    else if (bIsCurve && eTargetTypeFlat == wkbCurvePolygon)
1,553✔
4952
    {
4953
        OGRCurve *poCurve = poGeom->toCurve();
26✔
4954
        if (poCurve->getNumPoints() >= 3 && poCurve->get_IsClosed())
26✔
4955
        {
4956
            OGRCurvePolygon *poCP = new OGRCurvePolygon();
18✔
4957
            if (poCP->addRingDirectly(poCurve) == OGRERR_NONE)
18✔
4958
            {
4959
                poCP->assignSpatialReference(poGeom->getSpatialReference());
18✔
4960
                poCP->set3D(OGR_GT_HasZ(eTargetType));
18✔
4961
                poCP->setMeasured(OGR_GT_HasM(eTargetType));
18✔
4962
                return poCP;
18✔
4963
            }
4964
            else
4965
            {
4966
                delete poCP;
×
4967
            }
4968
        }
8✔
4969
    }
4970
    else if (eType == wkbLineString &&
1,604✔
4971
             OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface))
77✔
4972
    {
4973
        OGRGeometry *poTmp = forceTo(poGeom, wkbPolygon, papszOptions);
23✔
4974
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
23✔
4975
            return forceTo(poTmp, eTargetType, papszOptions);
15✔
4976
    }
4977
    else if (bIsCurve && eTargetTypeFlat == wkbMultiSurface)
1,504✔
4978
    {
4979
        OGRGeometry *poTmp = forceTo(poGeom, wkbCurvePolygon, papszOptions);
10✔
4980
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
10✔
4981
            return forceTo(poTmp, eTargetType, papszOptions);
10✔
4982
    }
4983
    else if (bIsCurve && eTargetTypeFlat == wkbMultiPolygon)
1,494✔
4984
    {
4985
        OGRGeometry *poTmp = forceTo(poGeom, wkbPolygon, papszOptions);
13✔
4986
        if (wkbFlatten(poTmp->getGeometryType()) != eType)
13✔
4987
            return forceTo(poTmp, eTargetType, papszOptions);
13✔
4988
    }
4989
    else if (eType == wkbTriangle && eTargetTypeFlat == wkbCurvePolygon)
1,481✔
4990
    {
4991
        auto poRet = OGRSurface::CastToCurvePolygon(
1✔
4992
            OGRTriangle::CastToPolygon(poGeom)->toSurface());
4993
        poRet->set3D(OGR_GT_HasZ(eTargetType));
1✔
4994
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
1✔
4995
        return poRet;
1✔
4996
    }
4997
    else if (eType == wkbPolygon && eTargetTypeFlat == wkbCurvePolygon)
1,480✔
4998
    {
4999
        auto poRet = OGRSurface::CastToCurvePolygon(poGeom->toPolygon());
19✔
5000
        poRet->set3D(OGR_GT_HasZ(eTargetType));
19✔
5001
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
19✔
5002
        return poRet;
19✔
5003
    }
5004
    else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
1,461✔
5005
             eTargetTypeFlat == wkbCompoundCurve)
5006
    {
5007
        OGRCurvePolygon *poPoly = poGeom->toCurvePolygon();
15✔
5008
        if (poPoly->getNumInteriorRings() == 0)
15✔
5009
        {
5010
            OGRCurve *poRet = poPoly->stealExteriorRingCurve();
14✔
5011
            if (poRet)
14✔
5012
                poRet->assignSpatialReference(poGeom->getSpatialReference());
14✔
5013
            delete poPoly;
14✔
5014
            return forceTo(poRet, eTargetType, papszOptions);
14✔
5015
        }
5016
    }
5017
    else if (eType == wkbMultiPolygon && eTargetTypeFlat == wkbMultiSurface)
1,446✔
5018
    {
5019
        auto poRet =
5020
            OGRMultiPolygon::CastToMultiSurface(poGeom->toMultiPolygon());
14✔
5021
        poRet->set3D(OGR_GT_HasZ(eTargetType));
14✔
5022
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
14✔
5023
        return poRet;
14✔
5024
    }
5025
    else if (eType == wkbMultiLineString && eTargetTypeFlat == wkbMultiCurve)
1,432✔
5026
    {
5027
        auto poRet =
5028
            OGRMultiLineString::CastToMultiCurve(poGeom->toMultiLineString());
9✔
5029
        poRet->set3D(OGR_GT_HasZ(eTargetType));
9✔
5030
        poRet->setMeasured(OGR_GT_HasM(eTargetType));
9✔
5031
        return poRet;
9✔
5032
    }
5033
    else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1,423✔
5034
    {
5035
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
263✔
5036
        if (poGC->getNumGeometries() == 1)
263✔
5037
        {
5038
            OGRGeometry *poSubGeom = poGC->getGeometryRef(0);
170✔
5039
            if (poSubGeom)
170✔
5040
            {
5041
                poSubGeom->assignSpatialReference(
170✔
5042
                    poGeom->getSpatialReference());
170✔
5043
                poGC->removeGeometry(0, FALSE);
170✔
5044
                OGRGeometry *poRet =
5045
                    forceTo(poSubGeom->clone(), eTargetType, papszOptions);
170✔
5046
                if (OGR_GT_IsSubClassOf(wkbFlatten(poRet->getGeometryType()),
170✔
5047
                                        eTargetType))
170✔
5048
                {
5049
                    delete poGC;
135✔
5050
                    delete poSubGeom;
135✔
5051
                    return poRet;
135✔
5052
                }
5053
                poGC->addGeometryDirectly(poSubGeom);
35✔
5054
                poRet->set3D(OGR_GT_HasZ(eTargetType));
35✔
5055
                poRet->setMeasured(OGR_GT_HasM(eTargetType));
35✔
5056
                delete poRet;
35✔
5057
            }
5058
        }
5059
    }
5060
    else if (OGR_GT_IsSubClassOf(eType, wkbCurvePolygon) &&
1,274✔
5061
             (OGR_GT_IsSubClassOf(eTargetType, wkbMultiSurface) ||
114✔
5062
              OGR_GT_IsSubClassOf(eTargetType, wkbMultiCurve)))
102✔
5063
    {
5064
        OGRCurvePolygon *poCP = poGeom->toCurvePolygon();
43✔
5065
        if (poCP->getNumInteriorRings() == 0)
43✔
5066
        {
5067
            OGRCurve *poRing = poCP->getExteriorRingCurve();
41✔
5068
            poRing->assignSpatialReference(poGeom->getSpatialReference());
41✔
5069
            OGRwkbGeometryType eRingType = poRing->getGeometryType();
41✔
5070
            OGRGeometry *poRingDup = poRing->clone();
41✔
5071
            OGRGeometry *poRet = forceTo(poRingDup, eTargetType, papszOptions);
41✔
5072
            if (poRet->getGeometryType() != eRingType &&
57✔
5073
                !(eTypeFlat == wkbPolygon &&
16✔
5074
                  eTargetTypeFlat == wkbMultiLineString))
5075
            {
5076
                delete poCP;
29✔
5077
                return poRet;
29✔
5078
            }
5079
            else
5080
            {
5081
                delete poRet;
12✔
5082
            }
5083
        }
5084
    }
5085

5086
    if (eTargetTypeFlat == wkbLineString)
1,280✔
5087
    {
5088
        poGeom = forceToLineString(poGeom);
89✔
5089
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
89✔
5090
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
89✔
5091
    }
5092
    else if (eTargetTypeFlat == wkbPolygon)
1,191✔
5093
    {
5094
        poGeom = forceToPolygon(poGeom);
99✔
5095
        if (poGeom)
99✔
5096
        {
5097
            poGeom->set3D(OGR_GT_HasZ(eTargetType));
99✔
5098
            poGeom->setMeasured(OGR_GT_HasM(eTargetType));
99✔
5099
        }
5100
    }
5101
    else if (eTargetTypeFlat == wkbMultiPolygon)
1,092✔
5102
    {
5103
        poGeom = forceToMultiPolygon(poGeom);
912✔
5104
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
912✔
5105
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
912✔
5106
    }
5107
    else if (eTargetTypeFlat == wkbMultiLineString)
180✔
5108
    {
5109
        poGeom = forceToMultiLineString(poGeom);
37✔
5110
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
37✔
5111
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
37✔
5112
    }
5113
    else if (eTargetTypeFlat == wkbMultiPoint)
143✔
5114
    {
5115
        poGeom = forceToMultiPoint(poGeom);
22✔
5116
        poGeom->set3D(OGR_GT_HasZ(eTargetType));
22✔
5117
        poGeom->setMeasured(OGR_GT_HasM(eTargetType));
22✔
5118
    }
5119

5120
    return poGeom;
1,280✔
5121
}
5122

5123
/************************************************************************/
5124
/*                          OGR_G_ForceTo()                             */
5125
/************************************************************************/
5126

5127
/**
5128
 * \brief Convert to another geometry type
5129
 *
5130
 * This function is the same as the C++ method OGRGeometryFactory::forceTo().
5131
 *
5132
 * @param hGeom the input geometry - ownership is passed to the method.
5133
 * @param eTargetType target output geometry type.
5134
 * @param papszOptions options as a null-terminated list of strings or NULL.
5135
 * @return new geometry.
5136
 *
5137
 * @since GDAL 2.0
5138
 */
5139

5140
OGRGeometryH OGR_G_ForceTo(OGRGeometryH hGeom, OGRwkbGeometryType eTargetType,
848✔
5141
                           char **papszOptions)
5142

5143
{
5144
    return OGRGeometry::ToHandle(OGRGeometryFactory::forceTo(
848✔
5145
        OGRGeometry::FromHandle(hGeom), eTargetType, papszOptions));
848✔
5146
}
5147

5148
/************************************************************************/
5149
/*                         GetCurveParameters()                          */
5150
/************************************************************************/
5151

5152
/**
5153
 * \brief Returns the parameter of an arc circle.
5154
 *
5155
 * Angles are return in radians, with trigonometic convention (counter clock
5156
 * wise)
5157
 *
5158
 * @param x0 x of first point
5159
 * @param y0 y of first point
5160
 * @param x1 x of intermediate point
5161
 * @param y1 y of intermediate point
5162
 * @param x2 x of final point
5163
 * @param y2 y of final point
5164
 * @param R radius (output)
5165
 * @param cx x of arc center (output)
5166
 * @param cy y of arc center (output)
5167
 * @param alpha0 angle between center and first point, in radians (output)
5168
 * @param alpha1 angle between center and intermediate point, in radians
5169
 * (output)
5170
 * @param alpha2 angle between center and final point, in radians (output)
5171
 * @return TRUE if the points are not aligned and define an arc circle.
5172
 *
5173
 * @since GDAL 2.0
5174
 */
5175

5176
int OGRGeometryFactory::GetCurveParameters(double x0, double y0, double x1,
186,993✔
5177
                                           double y1, double x2, double y2,
5178
                                           double &R, double &cx, double &cy,
5179
                                           double &alpha0, double &alpha1,
5180
                                           double &alpha2)
5181
{
5182
    if (std::isnan(x0) || std::isnan(y0) || std::isnan(x1) || std::isnan(y1) ||
560,979✔
5183
        std::isnan(x2) || std::isnan(y2))
560,979✔
5184
    {
5185
        return FALSE;
×
5186
    }
5187

5188
    // Circle.
5189
    if (x0 == x2 && y0 == y2)
186,993✔
5190
    {
5191
        if (x0 != x1 || y0 != y1)
149✔
5192
        {
5193
            cx = (x0 + x1) / 2;
148✔
5194
            cy = (y0 + y1) / 2;
148✔
5195
            R = DISTANCE(cx, cy, x0, y0);
148✔
5196
            // Arbitrarily pick counter-clock-wise order (like PostGIS does).
5197
            alpha0 = atan2(y0 - cy, x0 - cx);
148✔
5198
            alpha1 = alpha0 + M_PI;
148✔
5199
            alpha2 = alpha0 + 2 * M_PI;
148✔
5200
            return TRUE;
148✔
5201
        }
5202
        else
5203
        {
5204
            return FALSE;
1✔
5205
        }
5206
    }
5207

5208
    double dx01 = x1 - x0;
186,844✔
5209
    double dy01 = y1 - y0;
186,844✔
5210
    double dx12 = x2 - x1;
186,844✔
5211
    double dy12 = y2 - y1;
186,844✔
5212

5213
    // Normalize above values so as to make sure we don't end up with
5214
    // computing a difference of too big values.
5215
    double dfScale = fabs(dx01);
186,844✔
5216
    if (fabs(dy01) > dfScale)
186,844✔
5217
        dfScale = fabs(dy01);
92,975✔
5218
    if (fabs(dx12) > dfScale)
186,844✔
5219
        dfScale = fabs(dx12);
46,833✔
5220
    if (fabs(dy12) > dfScale)
186,844✔
5221
        dfScale = fabs(dy12);
46,472✔
5222
    const double dfInvScale = 1.0 / dfScale;
186,844✔
5223
    dx01 *= dfInvScale;
186,844✔
5224
    dy01 *= dfInvScale;
186,844✔
5225
    dx12 *= dfInvScale;
186,844✔
5226
    dy12 *= dfInvScale;
186,844✔
5227

5228
    const double det = dx01 * dy12 - dx12 * dy01;
186,844✔
5229
    if (fabs(det) < 1.0e-8 || std::isnan(det))
186,844✔
5230
    {
5231
        return FALSE;
124✔
5232
    }
5233
    const double x01_mid = (x0 + x1) * dfInvScale;
186,720✔
5234
    const double x12_mid = (x1 + x2) * dfInvScale;
186,720✔
5235
    const double y01_mid = (y0 + y1) * dfInvScale;
186,720✔
5236
    const double y12_mid = (y1 + y2) * dfInvScale;
186,720✔
5237
    const double c01 = dx01 * x01_mid + dy01 * y01_mid;
186,720✔
5238
    const double c12 = dx12 * x12_mid + dy12 * y12_mid;
186,720✔
5239
    cx = 0.5 * dfScale * (c01 * dy12 - c12 * dy01) / det;
186,720✔
5240
    cy = 0.5 * dfScale * (-c01 * dx12 + c12 * dx01) / det;
186,720✔
5241

5242
    alpha0 = atan2((y0 - cy) * dfInvScale, (x0 - cx) * dfInvScale);
186,720✔
5243
    alpha1 = atan2((y1 - cy) * dfInvScale, (x1 - cx) * dfInvScale);
186,720✔
5244
    alpha2 = atan2((y2 - cy) * dfInvScale, (x2 - cx) * dfInvScale);
186,720✔
5245
    R = DISTANCE(cx, cy, x0, y0);
186,720✔
5246

5247
    // If det is negative, the orientation if clockwise.
5248
    if (det < 0)
186,720✔
5249
    {
5250
        if (alpha1 > alpha0)
92,632✔
5251
            alpha1 -= 2 * M_PI;
1,276✔
5252
        if (alpha2 > alpha1)
92,632✔
5253
            alpha2 -= 2 * M_PI;
3,314✔
5254
    }
5255
    else
5256
    {
5257
        if (alpha1 < alpha0)
94,088✔
5258
            alpha1 += 2 * M_PI;
1,363✔
5259
        if (alpha2 < alpha1)
94,088✔
5260
            alpha2 += 2 * M_PI;
3,287✔
5261
    }
5262

5263
    CPLAssert((alpha0 <= alpha1 && alpha1 <= alpha2) ||
186,720✔
5264
              (alpha0 >= alpha1 && alpha1 >= alpha2));
5265

5266
    return TRUE;
186,720✔
5267
}
5268

5269
/************************************************************************/
5270
/*                      OGRGeometryFactoryStrokeArc()                   */
5271
/************************************************************************/
5272

5273
static void OGRGeometryFactoryStrokeArc(OGRLineString *poLine, double cx,
4,322✔
5274
                                        double cy, double R, double z0,
5275
                                        double z1, int bHasZ, double alpha0,
5276
                                        double alpha1, double dfStep,
5277
                                        int bStealthConstraints)
5278
{
5279
    const int nSign = dfStep > 0 ? 1 : -1;
4,322✔
5280

5281
    // Constant angle between all points, so as to not depend on winding order.
5282
    const double dfNumSteps = fabs((alpha1 - alpha0) / dfStep) + 0.5;
4,322✔
5283
    if (dfNumSteps >= std::numeric_limits<int>::max() ||
4,322✔
5284
        dfNumSteps <= std::numeric_limits<int>::min() || std::isnan(dfNumSteps))
4,322✔
5285
    {
5286
        CPLError(CE_Warning, CPLE_AppDefined,
×
5287
                 "OGRGeometryFactoryStrokeArc: bogus steps: "
5288
                 "%lf %lf %lf %lf",
5289
                 alpha0, alpha1, dfStep, dfNumSteps);
5290
        return;
×
5291
    }
5292

5293
    int nSteps = static_cast<int>(dfNumSteps);
4,322✔
5294
    if (bStealthConstraints)
4,322✔
5295
    {
5296
        // We need at least 6 intermediate vertex, and if more additional
5297
        // multiples of 2.
5298
        if (nSteps < 1 + 6)
4,138✔
5299
            nSteps = 1 + 6;
95✔
5300
        else
5301
            nSteps = 1 + 6 + 2 * ((nSteps - (1 + 6) + (2 - 1)) / 2);
4,043✔
5302
    }
5303
    else if (nSteps < 4)
184✔
5304
    {
5305
        nSteps = 4;
180✔
5306
    }
5307
    dfStep = nSign * fabs((alpha1 - alpha0) / nSteps);
4,322✔
5308
    double alpha = alpha0 + dfStep;
4,322✔
5309

5310
    for (; (alpha - alpha1) * nSign < -1e-8; alpha += dfStep)
230,704✔
5311
    {
5312
        const double dfX = cx + R * cos(alpha);
226,382✔
5313
        const double dfY = cy + R * sin(alpha);
226,382✔
5314
        if (bHasZ)
226,382✔
5315
        {
5316
            const double z =
9,104✔
5317
                z0 + (z1 - z0) * (alpha - alpha0) / (alpha1 - alpha0);
9,104✔
5318
            poLine->addPoint(dfX, dfY, z);
9,104✔
5319
        }
5320
        else
5321
        {
5322
            poLine->addPoint(dfX, dfY);
217,278✔
5323
        }
5324
    }
5325
}
5326

5327
/************************************************************************/
5328
/*                         OGRGF_SetHiddenValue()                       */
5329
/************************************************************************/
5330

5331
// TODO(schwehr): Cleanup these static constants.
5332
constexpr int HIDDEN_ALPHA_WIDTH = 32;
5333
constexpr GUInt32 HIDDEN_ALPHA_SCALE =
5334
    static_cast<GUInt32>((static_cast<GUIntBig>(1) << HIDDEN_ALPHA_WIDTH) - 2);
5335
constexpr int HIDDEN_ALPHA_HALF_WIDTH = (HIDDEN_ALPHA_WIDTH / 2);
5336
constexpr int HIDDEN_ALPHA_HALF_MASK = (1 << HIDDEN_ALPHA_HALF_WIDTH) - 1;
5337

5338
// Encode 16-bit nValue in the 8-lsb of dfX and dfY.
5339

5340
#ifdef CPL_LSB
5341
constexpr int DOUBLE_LSB_OFFSET = 0;
5342
#else
5343
constexpr int DOUBLE_LSB_OFFSET = 7;
5344
#endif
5345

5346
static void OGRGF_SetHiddenValue(GUInt16 nValue, double &dfX, double &dfY)
226,248✔
5347
{
5348
    GByte abyData[8] = {};
226,248✔
5349

5350
    memcpy(abyData, &dfX, sizeof(double));
226,248✔
5351
    abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue & 0xFF);
226,248✔
5352
    memcpy(&dfX, abyData, sizeof(double));
226,248✔
5353

5354
    memcpy(abyData, &dfY, sizeof(double));
226,248✔
5355
    abyData[DOUBLE_LSB_OFFSET] = static_cast<GByte>(nValue >> 8);
226,248✔
5356
    memcpy(&dfY, abyData, sizeof(double));
226,248✔
5357
}
226,248✔
5358

5359
/************************************************************************/
5360
/*                         OGRGF_GetHiddenValue()                       */
5361
/************************************************************************/
5362

5363
// Decode 16-bit nValue from the 8-lsb of dfX and dfY.
5364
static GUInt16 OGRGF_GetHiddenValue(double dfX, double dfY)
181,848✔
5365
{
5366
    GByte abyData[8] = {};
181,848✔
5367
    memcpy(abyData, &dfX, sizeof(double));
181,848✔
5368
    GUInt16 nValue = abyData[DOUBLE_LSB_OFFSET];
181,848✔
5369
    memcpy(abyData, &dfY, sizeof(double));
181,848✔
5370
    nValue |= (abyData[DOUBLE_LSB_OFFSET] << 8);
181,848✔
5371

5372
    return nValue;
181,848✔
5373
}
5374

5375
/************************************************************************/
5376
/*                     OGRGF_NeedSwithArcOrder()                        */
5377
/************************************************************************/
5378

5379
// We need to define a full ordering between starting point and ending point
5380
// whatever it is.
5381
static bool OGRGF_NeedSwithArcOrder(double x0, double y0, double x2, double y2)
9,476✔
5382
{
5383
    return x0 < x2 || (x0 == x2 && y0 < y2);
9,476✔
5384
}
5385

5386
/************************************************************************/
5387
/*                         curveToLineString()                          */
5388
/************************************************************************/
5389

5390
/* clang-format off */
5391
/**
5392
 * \brief Converts an arc circle into an approximate line string
5393
 *
5394
 * The arc circle is defined by a first point, an intermediate point and a
5395
 * final point.
5396
 *
5397
 * The provided dfMaxAngleStepSizeDegrees is a hint. The discretization
5398
 * algorithm may pick a slightly different value.
5399
 *
5400
 * So as to avoid gaps when rendering curve polygons that share common arcs,
5401
 * this method is guaranteed to return a line with reversed vertex if called
5402
 * with inverted first and final point, and identical intermediate point.
5403
 *
5404
 * @param x0 x of first point
5405
 * @param y0 y of first point
5406
 * @param z0 z of first point
5407
 * @param x1 x of intermediate point
5408
 * @param y1 y of intermediate point
5409
 * @param z1 z of intermediate point
5410
 * @param x2 x of final point
5411
 * @param y2 y of final point
5412
 * @param z2 z of final point
5413
 * @param bHasZ TRUE if z must be taken into account
5414
 * @param dfMaxAngleStepSizeDegrees the largest step in degrees along the
5415
 * arc, zero to use the default setting.
5416
 * @param papszOptions options as a null-terminated list of strings or NULL.
5417
 * Recognized options:
5418
 * <ul>
5419
 * <li>ADD_INTERMEDIATE_POINT=STEALTH/YES/NO (Default to STEALTH).
5420
 *         Determine if and how the intermediate point must be output in the
5421
 *         linestring.  If set to STEALTH, no explicit intermediate point is
5422
 *         added but its properties are encoded in low significant bits of
5423
 *         intermediate points and OGRGeometryFactory::curveFromLineString() can
5424
 *         decode them.  This is the best compromise for round-tripping in OGR
5425
 *         and better results with PostGIS
5426
 *         <a href="http://postgis.org/docs/ST_LineToCurve.html">ST_LineToCurve()</a>.
5427
 *         If set to YES, the intermediate point is explicitly added to the
5428
 *         linestring. If set to NO, the intermediate point is not explicitly
5429
 *         added.
5430
 * </li>
5431
 * </ul>
5432
 *
5433
 * @return the converted geometry (ownership to caller).
5434
 *
5435
 * @since GDAL 2.0
5436
 */
5437
/* clang-format on */
5438

5439
OGRLineString *OGRGeometryFactory::curveToLineString(
6,397✔
5440
    double x0, double y0, double z0, double x1, double y1, double z1, double x2,
5441
    double y2, double z2, int bHasZ, double dfMaxAngleStepSizeDegrees,
5442
    const char *const *papszOptions)
5443
{
5444
    // So as to make sure the same curve followed in both direction results
5445
    // in perfectly(=binary identical) symmetrical points.
5446
    if (OGRGF_NeedSwithArcOrder(x0, y0, x2, y2))
6,397✔
5447
    {
5448
        OGRLineString *poLS =
5449
            curveToLineString(x2, y2, z2, x1, y1, z1, x0, y0, z0, bHasZ,
2,166✔
5450
                              dfMaxAngleStepSizeDegrees, papszOptions);
5451
        poLS->reversePoints();
2,166✔
5452
        return poLS;
2,166✔
5453
    }
5454

5455
    double R = 0.0;
4,231✔
5456
    double cx = 0.0;
4,231✔
5457
    double cy = 0.0;
4,231✔
5458
    double alpha0 = 0.0;
4,231✔
5459
    double alpha1 = 0.0;
4,231✔
5460
    double alpha2 = 0.0;
4,231✔
5461

5462
    OGRLineString *poLine = new OGRLineString();
4,231✔
5463
    bool bIsArc = true;
4,231✔
5464
    if (!GetCurveParameters(x0, y0, x1, y1, x2, y2, R, cx, cy, alpha0, alpha1,
4,231✔
5465
                            alpha2))
5466
    {
5467
        bIsArc = false;
90✔
5468
        cx = 0.0;
90✔
5469
        cy = 0.0;
90✔
5470
        R = 0.0;
90✔
5471
        alpha0 = 0.0;
90✔
5472
        alpha1 = 0.0;
90✔
5473
        alpha2 = 0.0;
90✔
5474
    }
5475

5476
    const int nSign = alpha1 >= alpha0 ? 1 : -1;
4,231✔
5477

5478
    // support default arc step setting.
5479
    if (dfMaxAngleStepSizeDegrees < 1e-6)
4,231✔
5480
    {
5481
        dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize();
4,212✔
5482
    }
5483

5484
    double dfStep = dfMaxAngleStepSizeDegrees / 180 * M_PI;
4,231✔
5485
    if (dfStep <= 0.01 / 180 * M_PI)
4,231✔
5486
    {
5487
        CPLDebug("OGR", "Too small arc step size: limiting to 0.01 degree.");
×
5488
        dfStep = 0.01 / 180 * M_PI;
×
5489
    }
5490

5491
    dfStep *= nSign;
4,231✔
5492

5493
    if (bHasZ)
4,231✔
5494
        poLine->addPoint(x0, y0, z0);
252✔
5495
    else
5496
        poLine->addPoint(x0, y0);
3,979✔
5497

5498
    bool bAddIntermediatePoint = false;
4,231✔
5499
    bool bStealth = true;
4,231✔
5500
    for (const char *const *papszIter = papszOptions; papszIter && *papszIter;
4,237✔
5501
         papszIter++)
5502
    {
5503
        char *pszKey = nullptr;
6✔
5504
        const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
6✔
5505
        if (pszKey != nullptr && EQUAL(pszKey, "ADD_INTERMEDIATE_POINT"))
6✔
5506
        {
5507
            if (EQUAL(pszValue, "YES") || EQUAL(pszValue, "TRUE") ||
4✔
5508
                EQUAL(pszValue, "ON"))
3✔
5509
            {
5510
                bAddIntermediatePoint = true;
1✔
5511
                bStealth = false;
1✔
5512
            }
5513
            else if (EQUAL(pszValue, "NO") || EQUAL(pszValue, "FALSE") ||
3✔
5514
                     EQUAL(pszValue, "OFF"))
1✔
5515
            {
5516
                bAddIntermediatePoint = false;
2✔
5517
                bStealth = false;
2✔
5518
            }
5519
            else if (EQUAL(pszValue, "STEALTH"))
5520
            {
5521
                // default.
5522
            }
5523
        }
5524
        else
5525
        {
5526
            CPLError(CE_Warning, CPLE_NotSupported, "Unsupported option: %s",
2✔
5527
                     *papszIter);
5528
        }
5529
        CPLFree(pszKey);
6✔
5530
    }
5531

5532
    if (!bIsArc || bAddIntermediatePoint)
4,231✔
5533
    {
5534
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z1, bHasZ, alpha0,
91✔
5535
                                    alpha1, dfStep, FALSE);
5536

5537
        if (bHasZ)
91✔
5538
            poLine->addPoint(x1, y1, z1);
25✔
5539
        else
5540
            poLine->addPoint(x1, y1);
66✔
5541

5542
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z1, z2, bHasZ, alpha1,
91✔
5543
                                    alpha2, dfStep, FALSE);
5544
    }
5545
    else
5546
    {
5547
        OGRGeometryFactoryStrokeArc(poLine, cx, cy, R, z0, z2, bHasZ, alpha0,
4,140✔
5548
                                    alpha2, dfStep, bStealth);
5549

5550
        if (bStealth && poLine->getNumPoints() > 6)
4,140✔
5551
        {
5552
            // 'Hide' the angle of the intermediate point in the 8
5553
            // low-significant bits of the x, y of the first 2 computed points
5554
            // (so 32 bits), then put 0xFF, and on the last couple points put
5555
            // again the angle but in reverse order, so that overall the
5556
            // low-significant bits of all the points are symmetrical w.r.t the
5557
            // mid-point.
5558
            const double dfRatio = (alpha1 - alpha0) / (alpha2 - alpha0);
4,138✔
5559
            double dfAlphaRatio = 0.5 + HIDDEN_ALPHA_SCALE * dfRatio;
4,138✔
5560
            if (dfAlphaRatio < 0.0)
4,138✔
5561
            {
5562
                CPLError(CE_Warning, CPLE_AppDefined, "AlphaRation < 0: %lf",
×
5563
                         dfAlphaRatio);
5564
                dfAlphaRatio *= -1;
×
5565
            }
5566
            else if (dfAlphaRatio >= std::numeric_limits<GUInt32>::max() ||
8,276✔
5567
                     std::isnan(dfAlphaRatio))
4,138✔
5568
            {
5569
                CPLError(CE_Warning, CPLE_AppDefined,
×
5570
                         "AlphaRatio too large: %lf", dfAlphaRatio);
5571
                dfAlphaRatio = std::numeric_limits<GUInt32>::max();
×
5572
            }
5573
            const GUInt32 nAlphaRatio = static_cast<GUInt32>(dfAlphaRatio);
4,138✔
5574
            const GUInt16 nAlphaRatioLow = nAlphaRatio & HIDDEN_ALPHA_HALF_MASK;
4,138✔
5575
            const GUInt16 nAlphaRatioHigh =
4,138✔
5576
                nAlphaRatio >> HIDDEN_ALPHA_HALF_WIDTH;
4,138✔
5577
            // printf("alpha0=%f, alpha1=%f, alpha2=%f, dfRatio=%f, "/*ok*/
5578
            //        "nAlphaRatio = %u\n",
5579
            //        alpha0, alpha1, alpha2, dfRatio, nAlphaRatio);
5580

5581
            CPLAssert(((poLine->getNumPoints() - 1 - 6) % 2) == 0);
4,138✔
5582

5583
            for (int i = 1; i + 1 < poLine->getNumPoints(); i += 2)
117,262✔
5584
            {
5585
                GUInt16 nVal = 0xFFFF;
113,124✔
5586

5587
                double dfX = poLine->getX(i);
113,124✔
5588
                double dfY = poLine->getY(i);
113,124✔
5589
                if (i == 1)
113,124✔
5590
                    nVal = nAlphaRatioLow;
4,138✔
5591
                else if (i == poLine->getNumPoints() - 2)
108,986✔
5592
                    nVal = nAlphaRatioHigh;
4,138✔
5593
                OGRGF_SetHiddenValue(nVal, dfX, dfY);
113,124✔
5594
                poLine->setPoint(i, dfX, dfY);
113,124✔
5595

5596
                dfX = poLine->getX(i + 1);
113,124✔
5597
                dfY = poLine->getY(i + 1);
113,124✔
5598
                if (i == 1)
113,124✔
5599
                    nVal = nAlphaRatioHigh;
4,138✔
5600
                else if (i == poLine->getNumPoints() - 2)
108,986✔
5601
                    nVal = nAlphaRatioLow;
4,138✔
5602
                OGRGF_SetHiddenValue(nVal, dfX, dfY);
113,124✔
5603
                poLine->setPoint(i + 1, dfX, dfY);
113,124✔
5604
            }
5605
        }
5606
    }
5607

5608
    if (bHasZ)
4,231✔
5609
        poLine->addPoint(x2, y2, z2);
252✔
5610
    else
5611
        poLine->addPoint(x2, y2);
3,979✔
5612

5613
    return poLine;
4,231✔
5614
}
5615

5616
/************************************************************************/
5617
/*                         OGRGF_FixAngle()                             */
5618
/************************************************************************/
5619

5620
// Fix dfAngle by offsets of 2 PI so that it lies between dfAngleStart and
5621
// dfAngleStop, whatever their respective order.
5622
static double OGRGF_FixAngle(double dfAngleStart, double dfAngleStop,
180,668✔
5623
                             double dfAngle)
5624
{
5625
    if (dfAngleStart < dfAngleStop)
180,668✔
5626
    {
5627
        while (dfAngle <= dfAngleStart + 1e-8)
126,255✔
5628
            dfAngle += 2 * M_PI;
35,186✔
5629
    }
5630
    else
5631
    {
5632
        while (dfAngle >= dfAngleStart - 1e-8)
123,681✔
5633
            dfAngle -= 2 * M_PI;
34,082✔
5634
    }
5635
    return dfAngle;
180,668✔
5636
}
5637

5638
/************************************************************************/
5639
/*                         OGRGF_DetectArc()                            */
5640
/************************************************************************/
5641

5642
// #define VERBOSE_DEBUG_CURVEFROMLINESTRING
5643

5644
static inline bool IS_ALMOST_INTEGER(double x)
12,259✔
5645
{
5646
    const double val = fabs(x - floor(x + 0.5));
12,259✔
5647
    return val < 1.0e-8;
12,259✔
5648
}
5649

5650
static int OGRGF_DetectArc(const OGRLineString *poLS, int i,
3,480✔
5651
                           OGRCompoundCurve *&poCC, OGRCircularString *&poCS,
5652
                           OGRLineString *&poLSNew)
5653
{
5654
    if (i + 3 >= poLS->getNumPoints())
3,480✔
5655
        return -1;
305✔
5656

5657
    OGRPoint p0;
6,350✔
5658
    OGRPoint p1;
6,350✔
5659
    OGRPoint p2;
6,350✔
5660
    poLS->getPoint(i, &p0);
3,175✔
5661
    poLS->getPoint(i + 1, &p1);
3,175✔
5662
    poLS->getPoint(i + 2, &p2);
3,175✔
5663
    double R_1 = 0.0;
3,175✔
5664
    double cx_1 = 0.0;
3,175✔
5665
    double cy_1 = 0.0;
3,175✔
5666
    double alpha0_1 = 0.0;
3,175✔
5667
    double alpha1_1 = 0.0;
3,175✔
5668
    double alpha2_1 = 0.0;
3,175✔
5669
    if (!(OGRGeometryFactory::GetCurveParameters(
6,343✔
5670
              p0.getX(), p0.getY(), p1.getX(), p1.getY(), p2.getX(), p2.getY(),
5671
              R_1, cx_1, cy_1, alpha0_1, alpha1_1, alpha2_1) &&
5672
          fabs(alpha2_1 - alpha0_1) < 2.0 * 20.0 / 180.0 * M_PI))
3,168✔
5673
    {
5674
        return -1;
24✔
5675
    }
5676

5677
    const double dfDeltaAlpha10 = alpha1_1 - alpha0_1;
3,151✔
5678
    const double dfDeltaAlpha21 = alpha2_1 - alpha1_1;
3,151✔
5679
    const double dfMaxDeltaAlpha =
5680
        std::max(fabs(dfDeltaAlpha10), fabs(dfDeltaAlpha21));
3,151✔
5681
    GUInt32 nAlphaRatioRef =
5682
        OGRGF_GetHiddenValue(p1.getX(), p1.getY()) |
3,151✔
5683
        (OGRGF_GetHiddenValue(p2.getX(), p2.getY()) << HIDDEN_ALPHA_HALF_WIDTH);
3,151✔
5684
    bool bFoundFFFFFFFFPattern = false;
3,151✔
5685
    bool bFoundReversedAlphaRatioRef = false;
3,151✔
5686
    bool bValidAlphaRatio = nAlphaRatioRef > 0 && nAlphaRatioRef < 0xFFFFFFFF;
3,151✔
5687
    int nCountValidAlphaRatio = 1;
3,151✔
5688

5689
    double dfScale = std::max(1.0, R_1);
3,151✔
5690
    dfScale = std::max(dfScale, fabs(cx_1));
3,151✔
5691
    dfScale = std::max(dfScale, fabs(cy_1));
3,151✔
5692
    dfScale = pow(10.0, ceil(log10(dfScale)));
3,151✔
5693
    const double dfInvScale = 1.0 / dfScale;
3,151✔
5694

5695
    const int bInitialConstantStep =
3,151✔
5696
        (fabs(dfDeltaAlpha10 - dfDeltaAlpha21) / dfMaxDeltaAlpha) < 1.0e-4;
3,151✔
5697
    const double dfDeltaEpsilon =
3,151✔
5698
        bInitialConstantStep ? dfMaxDeltaAlpha * 1e-4 : dfMaxDeltaAlpha / 10;
3,151✔
5699

5700
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5701
    printf("----------------------------\n");             /*ok*/
5702
    printf("Curve beginning at offset i = %d\n", i);      /*ok*/
5703
    printf("Initial alpha ratio = %u\n", nAlphaRatioRef); /*ok*/
5704
    /*ok*/ printf("Initial R = %.16g, cx = %.16g, cy = %.16g\n", R_1, cx_1,
5705
                  cy_1);
5706
    printf("dfScale = %f\n", dfScale);   /*ok*/
5707
    printf("bInitialConstantStep = %d, " /*ok*/
5708
           "fabs(dfDeltaAlpha10 - dfDeltaAlpha21)=%.8g, "
5709
           "dfMaxDeltaAlpha = %.8f, "
5710
           "dfDeltaEpsilon = %.8f (%.8f)\n",
5711
           bInitialConstantStep, fabs(dfDeltaAlpha10 - dfDeltaAlpha21),
5712
           dfMaxDeltaAlpha, dfDeltaEpsilon, 1.0 / 180.0 * M_PI);
5713
#endif
5714
    int iMidPoint = -1;
3,151✔
5715
    double dfLastValidAlpha = alpha2_1;
3,151✔
5716

5717
    double dfLastLogRelDiff = 0;
3,151✔
5718

5719
    OGRPoint p3;
6,302✔
5720
    int j = i + 1;  // Used after for.
3,151✔
5721
    for (; j + 2 < poLS->getNumPoints(); j++)
182,221✔
5722
    {
5723
        poLS->getPoint(j, &p1);
179,169✔
5724
        poLS->getPoint(j + 1, &p2);
179,169✔
5725
        poLS->getPoint(j + 2, &p3);
179,169✔
5726
        double R_2 = 0.0;
179,169✔
5727
        double cx_2 = 0.0;
179,169✔
5728
        double cy_2 = 0.0;
179,169✔
5729
        double alpha0_2 = 0.0;
179,169✔
5730
        double alpha1_2 = 0.0;
179,169✔
5731
        double alpha2_2 = 0.0;
179,169✔
5732
        // Check that the new candidate arc shares the same
5733
        // radius, center and winding order.
5734
        if (!(OGRGeometryFactory::GetCurveParameters(
179,169✔
5735
                p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(),
5736
                p3.getY(), R_2, cx_2, cy_2, alpha0_2, alpha1_2, alpha2_2)))
5737
        {
5738
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5739
            printf("End of curve at j=%d\n : straight line", j); /*ok*/
5740
#endif
5741
            break;
99✔
5742
        }
5743

5744
        const double dfRelDiffR = fabs(R_1 - R_2) * dfInvScale;
179,161✔
5745
        const double dfRelDiffCx = fabs(cx_1 - cx_2) * dfInvScale;
179,161✔
5746
        const double dfRelDiffCy = fabs(cy_1 - cy_2) * dfInvScale;
179,161✔
5747

5748
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5749
        printf("j=%d: R = %.16g, cx = %.16g, cy = %.16g, " /*ok*/
5750
               "rel_diff_R=%.8g rel_diff_cx=%.8g rel_diff_cy=%.8g\n",
5751
               j, R_2, cx_2, cy_2, dfRelDiffR, dfRelDiffCx, dfRelDiffCy);
5752
#endif
5753

5754
        if (dfRelDiffR > 1.0e-7 || dfRelDiffCx > 1.0e-7 ||
179,161✔
5755
            dfRelDiffCy > 1.0e-7 ||
179,092✔
5756
            dfDeltaAlpha10 * (alpha1_2 - alpha0_2) < 0.0)
179,092✔
5757
        {
5758
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5759
            printf("End of curve at j=%d\n", j); /*ok*/
5760
#endif
5761
            break;
5762
        }
5763

5764
        if (dfRelDiffR > 0.0 && dfRelDiffCx > 0.0 && dfRelDiffCy > 0.0)
179,092✔
5765
        {
5766
            const double dfLogRelDiff = std::min(
5767
                std::min(fabs(log10(dfRelDiffR)), fabs(log10(dfRelDiffCx))),
358,152✔
5768
                fabs(log10(dfRelDiffCy)));
179,076✔
5769
            // printf("dfLogRelDiff = %f, dfLastLogRelDiff=%f, "/*ok*/
5770
            //        "dfLogRelDiff - dfLastLogRelDiff=%f\n",
5771
            //         dfLogRelDiff, dfLastLogRelDiff,
5772
            //         dfLogRelDiff - dfLastLogRelDiff);
5773
            if (dfLogRelDiff > 0.0 && dfLastLogRelDiff >= 8.0 &&
179,076✔
5774
                dfLogRelDiff <= 8.0 && dfLogRelDiff < dfLastLogRelDiff - 2.0)
2✔
5775
            {
5776
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5777
                printf("End of curve at j=%d. Significant different in " /*ok*/
5778
                       "relative error w.r.t previous points\n",
5779
                       j);
5780
#endif
5781
                break;
2✔
5782
            }
5783
            dfLastLogRelDiff = dfLogRelDiff;
179,074✔
5784
        }
5785

5786
        const double dfStep10 = fabs(alpha1_2 - alpha0_2);
179,090✔
5787
        const double dfStep21 = fabs(alpha2_2 - alpha1_2);
179,090✔
5788
        // Check that the angle step is consistent with the original step.
5789
        if (!(dfStep10 < 2.0 * dfMaxDeltaAlpha &&
179,090✔
5790
              dfStep21 < 2.0 * dfMaxDeltaAlpha))
179,090✔
5791
        {
5792
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5793
            printf("End of curve at j=%d: dfStep10=%f, dfStep21=%f, " /*ok*/
5794
                   "2*dfMaxDeltaAlpha=%f\n",
5795
                   j, dfStep10, dfStep21, 2 * dfMaxDeltaAlpha);
5796
#endif
5797
            break;
5798
        }
5799

5800
        if (bValidAlphaRatio && j > i + 1 && (i % 2) != (j % 2))
179,089✔
5801
        {
5802
            const GUInt32 nAlphaRatioReversed =
5803
                (OGRGF_GetHiddenValue(p1.getX(), p1.getY())
87,773✔
5804
                 << HIDDEN_ALPHA_HALF_WIDTH) |
175,546✔
5805
                (OGRGF_GetHiddenValue(p2.getX(), p2.getY()));
87,773✔
5806
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5807
            printf("j=%d, nAlphaRatioReversed = %u\n", /*ok*/
5808
                   j, nAlphaRatioReversed);
5809
#endif
5810
            if (!bFoundFFFFFFFFPattern && nAlphaRatioReversed == 0xFFFFFFFF)
87,773✔
5811
            {
5812
                bFoundFFFFFFFFPattern = true;
3,079✔
5813
                nCountValidAlphaRatio++;
3,079✔
5814
            }
5815
            else if (bFoundFFFFFFFFPattern && !bFoundReversedAlphaRatioRef &&
84,694✔
5816
                     nAlphaRatioReversed == 0xFFFFFFFF)
5817
            {
5818
                nCountValidAlphaRatio++;
81,588✔
5819
            }
5820
            else if (bFoundFFFFFFFFPattern && !bFoundReversedAlphaRatioRef &&
3,106✔
5821
                     nAlphaRatioReversed == nAlphaRatioRef)
5822
            {
5823
                bFoundReversedAlphaRatioRef = true;
3,079✔
5824
                nCountValidAlphaRatio++;
3,079✔
5825
            }
5826
            else
5827
            {
5828
                if (bInitialConstantStep &&
27✔
5829
                    fabs(dfLastValidAlpha - alpha0_1) >= M_PI &&
26✔
5830
                    nCountValidAlphaRatio > 10)
5831
                {
5832
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5833
                    printf("End of curve at j=%d: " /*ok*/
5834
                           "fabs(dfLastValidAlpha - alpha0_1)=%f, "
5835
                           "nCountValidAlphaRatio=%d\n",
5836
                           j, fabs(dfLastValidAlpha - alpha0_1),
5837
                           nCountValidAlphaRatio);
5838
#endif
5839
                    if (dfLastValidAlpha - alpha0_1 > 0)
19✔
5840
                    {
5841
                        while (dfLastValidAlpha - alpha0_1 - dfMaxDeltaAlpha -
21✔
5842
                                   M_PI >
14✔
5843
                               -dfMaxDeltaAlpha / 10)
14✔
5844
                        {
5845
                            dfLastValidAlpha -= dfMaxDeltaAlpha;
7✔
5846
                            j--;
7✔
5847
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5848
                            printf(/*ok*/
5849
                                   "--> corrected as fabs(dfLastValidAlpha - "
5850
                                   "alpha0_1)=%f, j=%d\n",
5851
                                   fabs(dfLastValidAlpha - alpha0_1), j);
5852
#endif
5853
                        }
5854
                    }
5855
                    else
5856
                    {
5857
                        while (dfLastValidAlpha - alpha0_1 + dfMaxDeltaAlpha +
36✔
5858
                                   M_PI <
24✔
5859
                               dfMaxDeltaAlpha / 10)
24✔
5860
                        {
5861
                            dfLastValidAlpha += dfMaxDeltaAlpha;
12✔
5862
                            j--;
12✔
5863
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5864
                            printf(/*ok*/
5865
                                   "--> corrected as fabs(dfLastValidAlpha - "
5866
                                   "alpha0_1)=%f, j=%d\n",
5867
                                   fabs(dfLastValidAlpha - alpha0_1), j);
5868
#endif
5869
                        }
5870
                    }
5871
                    poLS->getPoint(j + 1, &p2);
19✔
5872
                    break;
19✔
5873
                }
5874

5875
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5876
                printf("j=%d, nAlphaRatioReversed = %u --> inconsistent " /*ok*/
5877
                       "values across arc. Don't use it\n",
5878
                       j, nAlphaRatioReversed);
5879
#endif
5880
                bValidAlphaRatio = false;
8✔
5881
            }
5882
        }
5883

5884
        // Correct current end angle, consistently with start angle.
5885
        dfLastValidAlpha = OGRGF_FixAngle(alpha0_1, alpha1_1, alpha2_2);
179,070✔
5886

5887
        // Try to detect the precise intermediate point of the
5888
        // arc circle by detecting irregular angle step
5889
        // This is OK if we don't detect the right point or fail
5890
        // to detect it.
5891
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5892
        printf("j=%d A(0,1)-maxDelta=%.8f A(1,2)-maxDelta=%.8f " /*ok*/
5893
               "x1=%.8f y1=%.8f x2=%.8f y2=%.8f x3=%.8f y3=%.8f\n",
5894
               j, fabs(dfStep10 - dfMaxDeltaAlpha),
5895
               fabs(dfStep21 - dfMaxDeltaAlpha), p1.getX(), p1.getY(),
5896
               p2.getX(), p2.getY(), p3.getX(), p3.getY());
5897
#endif
5898
        if (j > i + 1 && iMidPoint < 0 && dfDeltaEpsilon < 1.0 / 180.0 * M_PI)
179,070✔
5899
        {
5900
            if (fabs(dfStep10 - dfMaxDeltaAlpha) > dfDeltaEpsilon)
175,587✔
5901
                iMidPoint = j + ((bInitialConstantStep) ? 0 : 1);
8✔
5902
            else if (fabs(dfStep21 - dfMaxDeltaAlpha) > dfDeltaEpsilon)
175,579✔
5903
                iMidPoint = j + ((bInitialConstantStep) ? 1 : 2);
4✔
5904
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5905
            if (iMidPoint >= 0)
5906
            {
5907
                OGRPoint pMid;
5908
                poLS->getPoint(iMidPoint, &pMid);
5909
                printf("Midpoint detected at j = %d, iMidPoint = %d, " /*ok*/
5910
                       "x=%.8f y=%.8f\n",
5911
                       j, iMidPoint, pMid.getX(), pMid.getY());
5912
            }
5913
#endif
5914
        }
5915
    }
5916

5917
    // Take a minimum threshold of consecutive points
5918
    // on the arc to avoid false positives.
5919
    if (j < i + 3)
3,151✔
5920
        return -1;
61✔
5921

5922
    bValidAlphaRatio &= bFoundFFFFFFFFPattern && bFoundReversedAlphaRatioRef;
3,090✔
5923

5924
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5925
    printf("bValidAlphaRatio=%d bFoundFFFFFFFFPattern=%d, " /*ok*/
5926
           "bFoundReversedAlphaRatioRef=%d\n",
5927
           static_cast<int>(bValidAlphaRatio),
5928
           static_cast<int>(bFoundFFFFFFFFPattern),
5929
           static_cast<int>(bFoundReversedAlphaRatioRef));
5930
    printf("alpha0_1=%f dfLastValidAlpha=%f\n", /*ok*/
5931
           alpha0_1, dfLastValidAlpha);
5932
#endif
5933

5934
    if (poLSNew != nullptr)
3,090✔
5935
    {
5936
        double dfScale2 = std::max(1.0, fabs(p0.getX()));
11✔
5937
        dfScale2 = std::max(dfScale2, fabs(p0.getY()));
11✔
5938
        // Not strictly necessary, but helps having 'clean' lines without
5939
        // duplicated points.
5940
        constexpr double dfToleranceEps =
11✔
5941
            OGRCompoundCurve::DEFAULT_TOLERANCE_EPSILON;
5942
        if (fabs(poLSNew->getX(poLSNew->getNumPoints() - 1) - p0.getX()) >
11✔
5943
                dfToleranceEps * dfScale2 ||
12✔
5944
            fabs(poLSNew->getY(poLSNew->getNumPoints() - 1) - p0.getY()) >
1✔
5945
                dfToleranceEps * dfScale2)
1✔
5946
            poLSNew->addPoint(&p0);
10✔
5947
        if (poLSNew->getNumPoints() >= 2)
11✔
5948
        {
5949
            if (poCC == nullptr)
10✔
5950
                poCC = new OGRCompoundCurve();
3✔
5951
            poCC->addCurveDirectly(poLSNew);
10✔
5952
        }
5953
        else
5954
            delete poLSNew;
1✔
5955
        poLSNew = nullptr;
11✔
5956
    }
5957

5958
    if (poCS == nullptr)
3,090✔
5959
    {
5960
        poCS = new OGRCircularString();
3,066✔
5961
        poCS->addPoint(&p0);
3,066✔
5962
    }
5963

5964
    OGRPoint *poFinalPoint = (j + 2 >= poLS->getNumPoints()) ? &p3 : &p2;
3,090✔
5965

5966
    double dfXMid = 0.0;
3,090✔
5967
    double dfYMid = 0.0;
3,090✔
5968
    double dfZMid = 0.0;
3,090✔
5969
    if (bValidAlphaRatio)
3,090✔
5970
    {
5971
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5972
        printf("Using alpha ratio...\n"); /*ok*/
5973
#endif
5974
        double dfAlphaMid = 0.0;
3,079✔
5975
        if (OGRGF_NeedSwithArcOrder(p0.getX(), p0.getY(), poFinalPoint->getX(),
3,079✔
5976
                                    poFinalPoint->getY()))
5977
        {
5978
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
5979
            printf("Switching angles\n"); /*ok*/
5980
#endif
5981
            dfAlphaMid = dfLastValidAlpha + nAlphaRatioRef *
1,550✔
5982
                                                (alpha0_1 - dfLastValidAlpha) /
1,550✔
5983
                                                HIDDEN_ALPHA_SCALE;
5984
            dfAlphaMid = OGRGF_FixAngle(alpha0_1, dfLastValidAlpha, dfAlphaMid);
1,550✔
5985
        }
5986
        else
5987
        {
5988
            dfAlphaMid = alpha0_1 + nAlphaRatioRef *
1,529✔
5989
                                        (dfLastValidAlpha - alpha0_1) /
1,529✔
5990
                                        HIDDEN_ALPHA_SCALE;
5991
        }
5992

5993
        dfXMid = cx_1 + R_1 * cos(dfAlphaMid);
3,079✔
5994
        dfYMid = cy_1 + R_1 * sin(dfAlphaMid);
3,079✔
5995

5996
        if (poLS->getCoordinateDimension() == 3)
3,079✔
5997
        {
5998
            double dfLastAlpha = 0.0;
2✔
5999
            double dfLastZ = 0.0;
2✔
6000
            int k = i;  // Used after for.
2✔
6001
            for (; k < j + 2; k++)
48✔
6002
            {
6003
                OGRPoint p;
48✔
6004
                poLS->getPoint(k, &p);
48✔
6005
                double dfAlpha = atan2(p.getY() - cy_1, p.getX() - cx_1);
48✔
6006
                dfAlpha = OGRGF_FixAngle(alpha0_1, dfLastValidAlpha, dfAlpha);
48✔
6007
                if (k > i &&
48✔
6008
                    ((dfAlpha < dfLastValidAlpha && dfAlphaMid < dfAlpha) ||
46✔
6009
                     (dfAlpha > dfLastValidAlpha && dfAlphaMid > dfAlpha)))
23✔
6010
                {
6011
                    const double dfRatio =
2✔
6012
                        (dfAlphaMid - dfLastAlpha) / (dfAlpha - dfLastAlpha);
2✔
6013
                    dfZMid = (1 - dfRatio) * dfLastZ + dfRatio * p.getZ();
2✔
6014
                    break;
2✔
6015
                }
6016
                dfLastAlpha = dfAlpha;
46✔
6017
                dfLastZ = p.getZ();
46✔
6018
            }
6019
            if (k == j + 2)
2✔
6020
                dfZMid = dfLastZ;
×
6021
            if (IS_ALMOST_INTEGER(dfZMid))
2✔
6022
                dfZMid = static_cast<int>(floor(dfZMid + 0.5));
2✔
6023
        }
6024

6025
        // A few rounding strategies in case the mid point was at "exact"
6026
        // coordinates.
6027
        if (R_1 > 1e-5)
3,079✔
6028
        {
6029
            const bool bStartEndInteger =
6030
                IS_ALMOST_INTEGER(p0.getX()) && IS_ALMOST_INTEGER(p0.getY()) &&
9,197✔
6031
                IS_ALMOST_INTEGER(poFinalPoint->getX()) &&
9,197✔
6032
                IS_ALMOST_INTEGER(poFinalPoint->getY());
3,060✔
6033
            if (bStartEndInteger &&
3,073✔
6034
                fabs(dfXMid - floor(dfXMid + 0.5)) / dfScale < 1e-4 &&
3,060✔
6035
                fabs(dfYMid - floor(dfYMid + 0.5)) / dfScale < 1e-4)
3,041✔
6036
            {
6037
                dfXMid = static_cast<int>(floor(dfXMid + 0.5));
3,041✔
6038
                dfYMid = static_cast<int>(floor(dfYMid + 0.5));
3,041✔
6039
                // Sometimes rounding to closest is not best approach
6040
                // Try neighbouring integers to look for the one that
6041
                // minimize the error w.r.t to the arc center
6042
                // But only do that if the radius is greater than
6043
                // the magnitude of the delta that we will try!
6044
                double dfBestRError =
6045
                    fabs(R_1 - DISTANCE(dfXMid, dfYMid, cx_1, cy_1));
3,041✔
6046
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6047
                printf("initial_error=%f\n", dfBestRError); /*ok*/
6048
#endif
6049
                int iBestX = 0;
3,041✔
6050
                int iBestY = 0;
3,041✔
6051
                if (dfBestRError > 0.001 && R_1 > 2)
3,041✔
6052
                {
6053
                    int nSearchRadius = 1;
3✔
6054
                    // Extend the search radius if the arc circle radius
6055
                    // is much higher than the coordinate values.
6056
                    double dfMaxCoords =
6057
                        std::max(fabs(p0.getX()), fabs(p0.getY()));
3✔
6058
                    dfMaxCoords = std::max(dfMaxCoords, poFinalPoint->getX());
3✔
6059
                    dfMaxCoords = std::max(dfMaxCoords, poFinalPoint->getY());
3✔
6060
                    dfMaxCoords = std::max(dfMaxCoords, dfXMid);
3✔
6061
                    dfMaxCoords = std::max(dfMaxCoords, dfYMid);
3✔
6062
                    if (R_1 > dfMaxCoords * 1000)
3✔
6063
                        nSearchRadius = 100;
3✔
6064
                    else if (R_1 > dfMaxCoords * 10)
×
6065
                        nSearchRadius = 10;
×
6066
                    for (int iY = -nSearchRadius; iY <= nSearchRadius; iY++)
606✔
6067
                    {
6068
                        for (int iX = -nSearchRadius; iX <= nSearchRadius; iX++)
121,806✔
6069
                        {
6070
                            const double dfCandidateX = dfXMid + iX;
121,203✔
6071
                            const double dfCandidateY = dfYMid + iY;
121,203✔
6072
                            if (fabs(dfCandidateX - p0.getX()) < 1e-8 &&
121,203✔
6073
                                fabs(dfCandidateY - p0.getY()) < 1e-8)
×
6074
                                continue;
×
6075
                            if (fabs(dfCandidateX - poFinalPoint->getX()) <
121,203✔
6076
                                    1e-8 &&
121,203✔
6077
                                fabs(dfCandidateY - poFinalPoint->getY()) <
×
6078
                                    1e-8)
6079
                                continue;
×
6080
                            const double dfRError =
6081
                                fabs(R_1 - DISTANCE(dfCandidateX, dfCandidateY,
121,203✔
6082
                                                    cx_1, cy_1));
121,203✔
6083
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6084
                            printf("x=%d y=%d error=%f besterror=%f\n", /*ok*/
6085
                                   static_cast<int>(dfXMid + iX),
6086
                                   static_cast<int>(dfYMid + iY), dfRError,
6087
                                   dfBestRError);
6088
#endif
6089
                            if (dfRError < dfBestRError)
121,203✔
6090
                            {
6091
                                iBestX = iX;
20✔
6092
                                iBestY = iY;
20✔
6093
                                dfBestRError = dfRError;
20✔
6094
                            }
6095
                        }
6096
                    }
6097
                }
6098
                dfXMid += iBestX;
3,041✔
6099
                dfYMid += iBestY;
3,041✔
6100
            }
6101
            else
6102
            {
6103
                // Limit the number of significant figures in decimal
6104
                // representation.
6105
                if (fabs(dfXMid) < 100000000.0)
32✔
6106
                {
6107
                    dfXMid =
32✔
6108
                        static_cast<GIntBig>(floor(dfXMid * 100000000 + 0.5)) /
32✔
6109
                        100000000.0;
6110
                }
6111
                if (fabs(dfYMid) < 100000000.0)
32✔
6112
                {
6113
                    dfYMid =
32✔
6114
                        static_cast<GIntBig>(floor(dfYMid * 100000000 + 0.5)) /
32✔
6115
                        100000000.0;
6116
                }
6117
            }
6118
        }
6119

6120
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6121
        printf("dfAlphaMid=%f, x_mid = %f, y_mid = %f\n", /*ok*/
6122
               dfLastValidAlpha, dfXMid, dfYMid);
6123
#endif
6124
    }
6125

6126
    // If this is a full circle of a non-polygonal zone, we must
6127
    // use a 5-point representation to keep the winding order.
6128
    if (p0.Equals(poFinalPoint) &&
3,101✔
6129
        !EQUAL(poLS->getGeometryName(), "LINEARRING"))
11✔
6130
    {
6131
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6132
        printf("Full circle of a non-polygonal zone\n"); /*ok*/
6133
#endif
6134
        poLS->getPoint((i + j + 2) / 4, &p1);
1✔
6135
        poCS->addPoint(&p1);
1✔
6136
        if (bValidAlphaRatio)
1✔
6137
        {
6138
            p1.setX(dfXMid);
1✔
6139
            p1.setY(dfYMid);
1✔
6140
            if (poLS->getCoordinateDimension() == 3)
1✔
6141
                p1.setZ(dfZMid);
×
6142
        }
6143
        else
6144
        {
6145
            poLS->getPoint((i + j + 1) / 2, &p1);
×
6146
        }
6147
        poCS->addPoint(&p1);
1✔
6148
        poLS->getPoint(3 * (i + j + 2) / 4, &p1);
1✔
6149
        poCS->addPoint(&p1);
1✔
6150
    }
6151

6152
    else if (bValidAlphaRatio)
3,089✔
6153
    {
6154
        p1.setX(dfXMid);
3,078✔
6155
        p1.setY(dfYMid);
3,078✔
6156
        if (poLS->getCoordinateDimension() == 3)
3,078✔
6157
            p1.setZ(dfZMid);
2✔
6158
        poCS->addPoint(&p1);
3,078✔
6159
    }
6160

6161
    // If we have found a candidate for a precise intermediate
6162
    // point, use it.
6163
    else if (iMidPoint >= 1 && iMidPoint < j)
11✔
6164
    {
6165
        poLS->getPoint(iMidPoint, &p1);
3✔
6166
        poCS->addPoint(&p1);
3✔
6167
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6168
        printf("Using detected midpoint...\n");                   /*ok*/
6169
        printf("x_mid = %f, y_mid = %f\n", p1.getX(), p1.getY()); /*ok*/
6170
#endif
6171
    }
6172
    // Otherwise pick up the mid point between both extremities.
6173
    else
6174
    {
6175
        poLS->getPoint((i + j + 1) / 2, &p1);
8✔
6176
        poCS->addPoint(&p1);
8✔
6177
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6178
        printf("Pickup 'random' midpoint at index=%d...\n", /*ok*/
6179
               (i + j + 1) / 2);
6180
        printf("x_mid = %f, y_mid = %f\n", p1.getX(), p1.getY()); /*ok*/
6181
#endif
6182
    }
6183
    poCS->addPoint(poFinalPoint);
3,090✔
6184

6185
#ifdef VERBOSE_DEBUG_CURVEFROMLINESTRING
6186
    printf("----------------------------\n"); /*ok*/
6187
#endif
6188

6189
    if (j + 2 >= poLS->getNumPoints())
3,090✔
6190
        return -2;
3,052✔
6191
    return j + 1;
38✔
6192
}
6193

6194
/************************************************************************/
6195
/*                        curveFromLineString()                         */
6196
/************************************************************************/
6197

6198
/**
6199
 * \brief Try to convert a linestring approximating curves into a curve.
6200
 *
6201
 * This method can return a COMPOUNDCURVE, a CIRCULARSTRING or a LINESTRING.
6202
 *
6203
 * This method is the reverse of curveFromLineString().
6204
 *
6205
 * @param poLS handle to the geometry to convert.
6206
 * @param papszOptions options as a null-terminated list of strings.
6207
 *                     Unused for now. Must be set to NULL.
6208
 *
6209
 * @return the converted geometry (ownership to caller).
6210
 *
6211
 * @since GDAL 2.0
6212
 */
6213

6214
OGRCurve *OGRGeometryFactory::curveFromLineString(
3,202✔
6215
    const OGRLineString *poLS, CPL_UNUSED const char *const *papszOptions)
6216
{
6217
    OGRCompoundCurve *poCC = nullptr;
3,202✔
6218
    OGRCircularString *poCS = nullptr;
3,202✔
6219
    OGRLineString *poLSNew = nullptr;
3,202✔
6220
    const int nLSNumPoints = poLS->getNumPoints();
3,202✔
6221
    const bool bIsClosed = nLSNumPoints >= 4 && poLS->get_IsClosed();
3,202✔
6222
    for (int i = 0; i < nLSNumPoints; /* nothing */)
3,630✔
6223
    {
6224
        const int iNewI = OGRGF_DetectArc(poLS, i, poCC, poCS, poLSNew);
3,480✔
6225
        if (iNewI == -2)
3,480✔
6226
            break;
3,052✔
6227
        if (iNewI >= 0)
428✔
6228
        {
6229
            i = iNewI;
38✔
6230
            continue;
38✔
6231
        }
6232

6233
        if (poCS != nullptr)
390✔
6234
        {
6235
            if (poCC == nullptr)
14✔
6236
                poCC = new OGRCompoundCurve();
5✔
6237
            poCC->addCurveDirectly(poCS);
14✔
6238
            poCS = nullptr;
14✔
6239
        }
6240

6241
        OGRPoint p;
390✔
6242
        poLS->getPoint(i, &p);
390✔
6243
        if (poLSNew == nullptr)
390✔
6244
        {
6245
            poLSNew = new OGRLineString();
160✔
6246
            poLSNew->addPoint(&p);
160✔
6247
        }
6248
        // Not strictly necessary, but helps having 'clean' lines without
6249
        // duplicated points.
6250
        else
6251
        {
6252
            double dfScale = std::max(1.0, fabs(p.getX()));
230✔
6253
            dfScale = std::max(dfScale, fabs(p.getY()));
230✔
6254
            if (bIsClosed && i == nLSNumPoints - 1)
230✔
6255
                dfScale = 0;
7✔
6256
            constexpr double dfToleranceEps =
230✔
6257
                OGRCompoundCurve::DEFAULT_TOLERANCE_EPSILON;
6258
            if (fabs(poLSNew->getX(poLSNew->getNumPoints() - 1) - p.getX()) >
230✔
6259
                    dfToleranceEps * dfScale ||
239✔
6260
                fabs(poLSNew->getY(poLSNew->getNumPoints() - 1) - p.getY()) >
9✔
6261
                    dfToleranceEps * dfScale)
9✔
6262
            {
6263
                poLSNew->addPoint(&p);
229✔
6264
            }
6265
        }
6266

6267
        i++;
390✔
6268
    }
6269

6270
    OGRCurve *poRet = nullptr;
3,202✔
6271

6272
    if (poLSNew != nullptr && poLSNew->getNumPoints() < 2)
3,202✔
6273
    {
6274
        delete poLSNew;
1✔
6275
        poLSNew = nullptr;
1✔
6276
        if (poCC != nullptr)
1✔
6277
        {
6278
            if (poCC->getNumCurves() == 1)
1✔
6279
            {
6280
                poRet = poCC->stealCurve(0);
1✔
6281
                delete poCC;
1✔
6282
                poCC = nullptr;
1✔
6283
            }
6284
            else
6285
                poRet = poCC;
×
6286
        }
6287
        else
6288
            poRet = poLS->clone();
×
6289
    }
6290
    else if (poCC != nullptr)
3,201✔
6291
    {
6292
        if (poLSNew)
7✔
6293
            poCC->addCurveDirectly(poLSNew);
6✔
6294
        else
6295
            poCC->addCurveDirectly(poCS);
1✔
6296
        poRet = poCC;
7✔
6297
    }
6298
    else if (poLSNew != nullptr)
3,194✔
6299
        poRet = poLSNew;
142✔
6300
    else if (poCS != nullptr)
3,052✔
6301
        poRet = poCS;
3,051✔
6302
    else
6303
        poRet = poLS->clone();
1✔
6304

6305
    poRet->assignSpatialReference(poLS->getSpatialReference());
3,202✔
6306

6307
    return poRet;
3,202✔
6308
}
6309

6310
/************************************************************************/
6311
/*                   createFromGeoJson( const char* )                   */
6312
/************************************************************************/
6313

6314
/**
6315
 * @brief Create geometry from GeoJson fragment.
6316
 * @param pszJsonString The GeoJSON fragment for the geometry.
6317
 * @param nSize (new in GDAL 3.4) Optional length of the string
6318
 *              if it is not null-terminated
6319
 * @return a geometry on success, or NULL on error.
6320
 * @since GDAL 2.3
6321
 */
6322
OGRGeometry *OGRGeometryFactory::createFromGeoJson(const char *pszJsonString,
5✔
6323
                                                   int nSize)
6324
{
6325
    CPLJSONDocument oDocument;
10✔
6326
    if (!oDocument.LoadMemory(reinterpret_cast<const GByte *>(pszJsonString),
5✔
6327
                              nSize))
6328
    {
6329
        return nullptr;
3✔
6330
    }
6331

6332
    return createFromGeoJson(oDocument.GetRoot());
2✔
6333
}
6334

6335
/************************************************************************/
6336
/*              createFromGeoJson( const CPLJSONObject& )               */
6337
/************************************************************************/
6338

6339
/**
6340
 * @brief Create geometry from GeoJson fragment.
6341
 * @param oJsonObject The JSONObject class describes the GeoJSON geometry.
6342
 * @return a geometry on success, or NULL on error.
6343
 * @since GDAL 2.3
6344
 */
6345
OGRGeometry *
6346
OGRGeometryFactory::createFromGeoJson(const CPLJSONObject &oJsonObject)
2✔
6347
{
6348
    if (!oJsonObject.IsValid())
2✔
6349
    {
6350
        return nullptr;
×
6351
    }
6352

6353
    // TODO: Move from GeoJSON driver functions create geometry here, and
6354
    // replace json-c specific json_object to CPLJSONObject
6355
    return OGRGeoJSONReadGeometry(
2✔
6356
        static_cast<json_object *>(oJsonObject.GetInternalHandle()));
4✔
6357
}
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