Coverage Report

Created: 2025-06-09 08:44

/src/gdal/ogr/ogrgeometrycollection.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  The OGRGeometryCollection class.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 1999, Frank Warmerdam
9
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "ogr_geometry.h"
16
17
#include <cstddef>
18
#include <cstring>
19
#include <limits>
20
#include <new>
21
22
#include "cpl_conv.h"
23
#include "cpl_error.h"
24
#include "cpl_string.h"
25
#include "cpl_vsi.h"
26
#include "ogr_api.h"
27
#include "ogr_core.h"
28
#include "ogr_p.h"
29
#include "ogr_spatialref.h"
30
31
/************************************************************************/
32
/*         OGRGeometryCollection( const OGRGeometryCollection& )        */
33
/************************************************************************/
34
35
/**
36
 * \brief Copy constructor.
37
 *
38
 * Note: before GDAL 2.1, only the default implementation of the constructor
39
 * existed, which could be unsafe to use.
40
 *
41
 * @since GDAL 2.1
42
 */
43
44
OGRGeometryCollection::OGRGeometryCollection(const OGRGeometryCollection &other)
45
0
    : OGRGeometry(other)
46
0
{
47
    // Do not use addGeometry() as it is virtual.
48
0
    papoGeoms = static_cast<OGRGeometry **>(
49
0
        VSI_CALLOC_VERBOSE(sizeof(OGRGeometry *), other.nGeomCount));
50
0
    if (papoGeoms)
51
0
    {
52
0
        nGeomCount = other.nGeomCount;
53
0
        for (int i = 0; i < other.nGeomCount; i++)
54
0
        {
55
0
            papoGeoms[i] = other.papoGeoms[i]->clone();
56
0
        }
57
0
    }
58
0
}
59
60
/************************************************************************/
61
/*            OGRGeometryCollection( OGRGeometryCollection&& )          */
62
/************************************************************************/
63
64
/**
65
 * \brief Move constructor.
66
 *
67
 * @since GDAL 3.11
68
 */
69
70
// cppcheck-suppress-begin accessMoved
71
OGRGeometryCollection::OGRGeometryCollection(OGRGeometryCollection &&other)
72
0
    : OGRGeometry(std::move(other)), nGeomCount(other.nGeomCount),
73
0
      papoGeoms(other.papoGeoms)
74
0
{
75
0
    other.nGeomCount = 0;
76
0
    other.papoGeoms = nullptr;
77
0
}
78
79
// cppcheck-suppress-end accessMoved
80
81
/************************************************************************/
82
/*                       ~OGRGeometryCollection()                       */
83
/************************************************************************/
84
85
OGRGeometryCollection::~OGRGeometryCollection()
86
87
0
{
88
0
    OGRGeometryCollection::empty();
89
0
}
90
91
/************************************************************************/
92
/*               operator=( const OGRGeometryCollection&)               */
93
/************************************************************************/
94
95
/**
96
 * \brief Assignment operator.
97
 *
98
 * Note: before GDAL 2.1, only the default implementation of the operator
99
 * existed, which could be unsafe to use.
100
 *
101
 * @since GDAL 2.1
102
 */
103
104
OGRGeometryCollection &
105
OGRGeometryCollection::operator=(const OGRGeometryCollection &other)
106
0
{
107
0
    if (this != &other)
108
0
    {
109
0
        OGRGeometry::operator=(other);
110
111
0
        for (const auto *poOtherSubGeom : other)
112
0
        {
113
0
            if (!isCompatibleSubType(poOtherSubGeom->getGeometryType()))
114
0
            {
115
0
                CPLError(CE_Failure, CPLE_AppDefined,
116
0
                         "Illegal use of OGRGeometryCollection::operator=(): "
117
0
                         "trying to assign an incompatible sub-geometry");
118
0
                return *this;
119
0
            }
120
0
        }
121
122
0
        papoGeoms = static_cast<OGRGeometry **>(
123
0
            VSI_CALLOC_VERBOSE(sizeof(OGRGeometry *), other.nGeomCount));
124
0
        if (papoGeoms)
125
0
        {
126
0
            nGeomCount = other.nGeomCount;
127
0
            for (int i = 0; i < other.nGeomCount; i++)
128
0
            {
129
0
                papoGeoms[i] = other.papoGeoms[i]->clone();
130
0
            }
131
0
        }
132
0
    }
133
0
    return *this;
134
0
}
135
136
/************************************************************************/
137
/*                  operator=( OGRGeometryCollection&&)                 */
138
/************************************************************************/
139
140
/**
141
 * \brief Move assignment operator.
142
 *
143
 * @since GDAL 3.11
144
 */
145
146
OGRGeometryCollection &
147
OGRGeometryCollection::operator=(OGRGeometryCollection &&other)
148
0
{
149
0
    if (this != &other)
150
0
    {
151
0
        empty();
152
153
0
        OGRGeometry::operator=(std::move(other));
154
0
        std::swap(nGeomCount, other.nGeomCount);
155
0
        std::swap(papoGeoms, other.papoGeoms);
156
0
    }
157
0
    return *this;
158
0
}
159
160
/************************************************************************/
161
/*                               empty()                                */
162
/************************************************************************/
163
164
void OGRGeometryCollection::empty()
165
166
0
{
167
0
    if (papoGeoms != nullptr)
168
0
    {
169
0
        for (auto &poSubGeom : *this)
170
0
        {
171
0
            delete poSubGeom;
172
0
        }
173
0
        CPLFree(papoGeoms);
174
0
    }
175
176
0
    nGeomCount = 0;
177
0
    papoGeoms = nullptr;
178
0
}
179
180
/************************************************************************/
181
/*                               clone()                                */
182
/************************************************************************/
183
184
OGRGeometryCollection *OGRGeometryCollection::clone() const
185
186
0
{
187
0
    auto ret = new (std::nothrow) OGRGeometryCollection(*this);
188
0
    if (ret)
189
0
    {
190
0
        if (ret->WkbSize() != WkbSize())
191
0
        {
192
0
            delete ret;
193
0
            ret = nullptr;
194
0
        }
195
0
    }
196
0
    return ret;
197
0
}
198
199
/************************************************************************/
200
/*                          getGeometryType()                           */
201
/************************************************************************/
202
203
OGRwkbGeometryType OGRGeometryCollection::getGeometryType() const
204
205
0
{
206
0
    if ((flags & OGR_G_3D) && (flags & OGR_G_MEASURED))
207
0
        return wkbGeometryCollectionZM;
208
0
    else if (flags & OGR_G_MEASURED)
209
0
        return wkbGeometryCollectionM;
210
0
    else if (flags & OGR_G_3D)
211
0
        return wkbGeometryCollection25D;
212
0
    else
213
0
        return wkbGeometryCollection;
214
0
}
215
216
/************************************************************************/
217
/*                            getDimension()                            */
218
/************************************************************************/
219
220
int OGRGeometryCollection::getDimension() const
221
222
0
{
223
0
    int nDimension = 0;
224
    // FIXME? Not sure if it is really appropriate to take the max in case
225
    // of geometries of different dimension.
226
0
    for (const auto &poSubGeom : *this)
227
0
    {
228
0
        int nSubGeomDimension = poSubGeom->getDimension();
229
0
        if (nSubGeomDimension > nDimension)
230
0
        {
231
0
            nDimension = nSubGeomDimension;
232
0
            if (nDimension == 2)
233
0
                break;
234
0
        }
235
0
    }
236
0
    return nDimension;
237
0
}
238
239
/************************************************************************/
240
/*                            flattenTo2D()                             */
241
/************************************************************************/
242
243
void OGRGeometryCollection::flattenTo2D()
244
245
0
{
246
0
    for (auto &poSubGeom : *this)
247
0
    {
248
0
        poSubGeom->flattenTo2D();
249
0
    }
250
251
0
    flags &= ~OGR_G_3D;
252
0
    flags &= ~OGR_G_MEASURED;
253
0
}
254
255
/************************************************************************/
256
/*                          getGeometryName()                           */
257
/************************************************************************/
258
259
const char *OGRGeometryCollection::getGeometryName() const
260
261
0
{
262
0
    return "GEOMETRYCOLLECTION";
263
0
}
264
265
/************************************************************************/
266
/*                          getNumGeometries()                          */
267
/************************************************************************/
268
269
/**
270
 * \brief Fetch number of geometries in container.
271
 *
272
 * This method relates to the SFCOM IGeometryCollect::get_NumGeometries()
273
 * method.
274
 *
275
 * @return count of children geometries.  May be zero.
276
 */
277
278
int OGRGeometryCollection::getNumGeometries() const
279
280
0
{
281
0
    return nGeomCount;
282
0
}
283
284
/************************************************************************/
285
/*                           getGeometryRef()                           */
286
/************************************************************************/
287
288
/**
289
 * \brief Fetch geometry from container.
290
 *
291
 * This method returns a pointer to a geometry within the container.  The
292
 * returned geometry remains owned by the container, and should not be
293
 * modified.  The pointer is only valid until the next change to the
294
 * geometry container.  Use IGeometry::clone() to make a copy.
295
 *
296
 * This method relates to the SFCOM IGeometryCollection::get_Geometry() method.
297
 *
298
 * @param i the index of the geometry to fetch, between 0 and
299
 *          getNumGeometries() - 1.
300
 * @return pointer to requested geometry.
301
 */
302
303
OGRGeometry *OGRGeometryCollection::getGeometryRef(int i)
304
305
0
{
306
0
    if (i < 0 || i >= nGeomCount)
307
0
        return nullptr;
308
309
0
    return papoGeoms[i];
310
0
}
311
312
/**
313
 * \brief Fetch geometry from container.
314
 *
315
 * This method returns a pointer to a geometry within the container.  The
316
 * returned geometry remains owned by the container, and should not be
317
 * modified.  The pointer is only valid until the next change to the
318
 * geometry container.  Use IGeometry::clone() to make a copy.
319
 *
320
 * This method relates to the SFCOM IGeometryCollection::get_Geometry() method.
321
 *
322
 * @param i the index of the geometry to fetch, between 0 and
323
 *          getNumGeometries() - 1.
324
 * @return pointer to requested geometry.
325
 */
326
327
const OGRGeometry *OGRGeometryCollection::getGeometryRef(int i) const
328
329
0
{
330
0
    if (i < 0 || i >= nGeomCount)
331
0
        return nullptr;
332
333
0
    return papoGeoms[i];
334
0
}
335
336
/************************************************************************/
337
/*                            addGeometry()                             */
338
/*                                                                      */
339
/*      Add a new geometry to a collection.  Subclasses should          */
340
/*      override this to verify the type of the new geometry, and       */
341
/*      then call this method to actually add it.                       */
342
/************************************************************************/
343
344
/**
345
 * \brief Add a geometry to the container.
346
 *
347
 * Some subclasses of OGRGeometryCollection restrict the types of geometry
348
 * that can be added, and may return an error.  The passed geometry is cloned
349
 * to make an internal copy.
350
 *
351
 * There is no SFCOM analog to this method.
352
 *
353
 * This method is the same as the C function OGR_G_AddGeometry().
354
 *
355
 * @param poNewGeom geometry to add to the container.
356
 *
357
 * @return OGRERR_NONE if successful, or OGRERR_UNSUPPORTED_GEOMETRY_TYPE if
358
 * the geometry type is illegal for the type of geometry container.
359
 */
360
361
OGRErr OGRGeometryCollection::addGeometry(const OGRGeometry *poNewGeom)
362
363
0
{
364
0
    OGRGeometry *poClone = poNewGeom->clone();
365
0
    if (poClone == nullptr)
366
0
        return OGRERR_FAILURE;
367
368
0
    const OGRErr eErr = addGeometryDirectly(poClone);
369
0
    if (eErr != OGRERR_NONE)
370
0
        delete poClone;
371
372
0
    return eErr;
373
0
}
374
375
/************************************************************************/
376
/*                        addGeometryDirectly()                         */
377
/*                                                                      */
378
/*      Add a new geometry to a collection.  Subclasses should          */
379
/*      override this to verify the type of the new geometry, and       */
380
/*      then call this method to actually add it.                       */
381
/************************************************************************/
382
383
/**
384
 * \brief Add a geometry directly to the container.
385
 *
386
 * Some subclasses of OGRGeometryCollection restrict the types of geometry
387
 * that can be added, and may return an error.  Ownership of the passed
388
 * geometry is taken by the container rather than cloning as addGeometry()
389
 * does, but only if the method is successful. If the method fails, ownership
390
 * still belongs to the caller.
391
 *
392
 * This method is the same as the C function OGR_G_AddGeometryDirectly().
393
 *
394
 * There is no SFCOM analog to this method.
395
 *
396
 * @param poNewGeom geometry to add to the container.
397
 *
398
 * @return OGRERR_NONE if successful, or OGRERR_UNSUPPORTED_GEOMETRY_TYPE if
399
 * the geometry type is illegal for the type of geometry container.
400
 */
401
402
OGRErr OGRGeometryCollection::addGeometryDirectly(OGRGeometry *poNewGeom)
403
404
0
{
405
0
    if (!isCompatibleSubType(poNewGeom->getGeometryType()))
406
0
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
407
408
#if SIZEOF_VOIDP < 8
409
    if (nGeomCount == std::numeric_limits<int>::max() /
410
                          static_cast<int>(sizeof(OGRGeometry *)))
411
    {
412
        CPLError(CE_Failure, CPLE_OutOfMemory, "Too many subgeometries");
413
        return OGRERR_FAILURE;
414
    }
415
#else
416
0
    if (nGeomCount == std::numeric_limits<int>::max())
417
0
    {
418
0
        CPLError(CE_Failure, CPLE_AppDefined, "Too many subgeometries");
419
0
        return OGRERR_FAILURE;
420
0
    }
421
0
#endif
422
423
0
    HomogenizeDimensionalityWith(poNewGeom);
424
425
0
    OGRGeometry **papoNewGeoms =
426
0
        static_cast<OGRGeometry **>(VSI_REALLOC_VERBOSE(
427
0
            papoGeoms, sizeof(OGRGeometry *) * (nGeomCount + 1)));
428
0
    if (papoNewGeoms == nullptr)
429
0
        return OGRERR_FAILURE;
430
431
0
    papoGeoms = papoNewGeoms;
432
0
    papoGeoms[nGeomCount] = poNewGeom;
433
434
0
    nGeomCount++;
435
436
0
    return OGRERR_NONE;
437
0
}
438
439
/************************************************************************/
440
/*                            addGeometry()                             */
441
/************************************************************************/
442
443
/**
444
 * \brief Add a geometry directly to the container.
445
 *
446
 * Some subclasses of OGRGeometryCollection restrict the types of geometry
447
 * that can be added, and may return an error.
448
 *
449
 * There is no SFCOM analog to this method.
450
 *
451
 * @param geom geometry to add to the container.
452
 *
453
 * @return OGRERR_NONE if successful, or OGRERR_UNSUPPORTED_GEOMETRY_TYPE if
454
 * the geometry type is illegal for the type of geometry container.
455
 */
456
457
OGRErr OGRGeometryCollection::addGeometry(std::unique_ptr<OGRGeometry> geom)
458
0
{
459
0
    OGRGeometry *poGeom = geom.release();
460
0
    OGRErr eErr = addGeometryDirectly(poGeom);
461
0
    if (eErr != OGRERR_NONE)
462
0
        delete poGeom;
463
0
    return eErr;
464
0
}
465
466
/************************************************************************/
467
/*                           removeGeometry()                           */
468
/************************************************************************/
469
470
/**
471
 * \brief Remove a geometry from the container.
472
 *
473
 * Removing a geometry will cause the geometry count to drop by one, and all
474
 * "higher" geometries will shuffle down one in index.
475
 *
476
 * There is no SFCOM analog to this method.
477
 *
478
 * This method is the same as the C function OGR_G_RemoveGeometry().
479
 *
480
 * @param iGeom the index of the geometry to delete.  A value of -1 is a
481
 * special flag meaning that all geometries should be removed.
482
 *
483
 * @param bDelete if TRUE the geometry will be deallocated, otherwise it will
484
 * not.  The default is TRUE as the container is considered to own the
485
 * geometries in it. Note: using stealGeometry() might be a better alternative
486
 * to using bDelete = false.
487
 *
488
 * @return OGRERR_NONE if successful, or OGRERR_FAILURE if the index is
489
 * out of range.
490
 */
491
492
OGRErr OGRGeometryCollection::removeGeometry(int iGeom, int bDelete)
493
494
0
{
495
0
    if (iGeom < -1 || iGeom >= nGeomCount)
496
0
        return OGRERR_FAILURE;
497
498
    // Special case.
499
0
    if (iGeom == -1)
500
0
    {
501
0
        while (nGeomCount > 0)
502
0
            removeGeometry(nGeomCount - 1, bDelete);
503
0
        return OGRERR_NONE;
504
0
    }
505
506
0
    if (bDelete)
507
0
        delete papoGeoms[iGeom];
508
509
0
    memmove(papoGeoms + iGeom, papoGeoms + iGeom + 1,
510
0
            sizeof(OGRGeometry *) * (nGeomCount - iGeom - 1));
511
512
0
    nGeomCount--;
513
514
0
    return OGRERR_NONE;
515
0
}
516
517
/************************************************************************/
518
/*                           stealGeometry()                            */
519
/************************************************************************/
520
521
/**
522
 * \brief Remove a geometry from the container and return it to the caller
523
 *
524
 * Removing a geometry will cause the geometry count to drop by one, and all
525
 * "higher" geometries will shuffle down one in index.
526
 *
527
 * There is no SFCOM analog to this method.
528
 *
529
 * @param iGeom the index of the geometry to delete.
530
 *
531
 * @return the sub-geometry, or nullptr in case of error.
532
 * @since 3.10
533
 */
534
535
std::unique_ptr<OGRGeometry> OGRGeometryCollection::stealGeometry(int iGeom)
536
0
{
537
0
    if (iGeom < 0 || iGeom >= nGeomCount)
538
0
        return nullptr;
539
540
0
    auto poSubGeom = std::unique_ptr<OGRGeometry>(papoGeoms[iGeom]);
541
0
    papoGeoms[iGeom] = nullptr;
542
0
    removeGeometry(iGeom);
543
0
    return poSubGeom;
544
0
}
545
546
/************************************************************************/
547
/*                           hasEmptyParts()                            */
548
/************************************************************************/
549
550
bool OGRGeometryCollection::hasEmptyParts() const
551
0
{
552
0
    for (const auto &poSubGeom : *this)
553
0
    {
554
0
        if (poSubGeom->IsEmpty() || poSubGeom->hasEmptyParts())
555
0
            return true;
556
0
    }
557
0
    return false;
558
0
}
559
560
/************************************************************************/
561
/*                          removeEmptyParts()                          */
562
/************************************************************************/
563
564
void OGRGeometryCollection::removeEmptyParts()
565
0
{
566
0
    for (int i = nGeomCount - 1; i >= 0; --i)
567
0
    {
568
0
        papoGeoms[i]->removeEmptyParts();
569
0
        if (papoGeoms[i]->IsEmpty())
570
0
            removeGeometry(i, true);
571
0
    }
572
0
}
573
574
/************************************************************************/
575
/*                              WkbSize()                               */
576
/*                                                                      */
577
/*      Return the size of this object in well known binary             */
578
/*      representation including the byte order, and type information.  */
579
/************************************************************************/
580
581
size_t OGRGeometryCollection::WkbSize() const
582
583
0
{
584
0
    size_t nSize = 9;
585
586
0
    for (const auto &poGeom : *this)
587
0
    {
588
0
        nSize += poGeom->WkbSize();
589
0
    }
590
591
0
    return nSize;
592
0
}
593
594
/************************************************************************/
595
/*                       importFromWkbInternal()                        */
596
/************************************************************************/
597
598
//! @cond Doxygen_Suppress
599
OGRErr OGRGeometryCollection::importFromWkbInternal(
600
    const unsigned char *pabyData, size_t nSize, int nRecLevel,
601
    OGRwkbVariant eWkbVariant, size_t &nBytesConsumedOut)
602
603
0
{
604
0
    nBytesConsumedOut = 0;
605
    // Arbitrary value, but certainly large enough for reasonable use cases.
606
0
    if (nRecLevel == 32)
607
0
    {
608
0
        CPLError(CE_Failure, CPLE_AppDefined,
609
0
                 "Too many recursion levels (%d) while parsing WKB geometry.",
610
0
                 nRecLevel);
611
0
        return OGRERR_CORRUPT_DATA;
612
0
    }
613
614
0
    OGRwkbByteOrder eByteOrder = wkbXDR;
615
0
    size_t nDataOffset = 0;
616
0
    int nGeomCountNew = 0;
617
0
    OGRErr eErr = importPreambleOfCollectionFromWkb(pabyData, nSize,
618
0
                                                    nDataOffset, eByteOrder, 9,
619
0
                                                    nGeomCountNew, eWkbVariant);
620
621
0
    if (eErr != OGRERR_NONE)
622
0
        return eErr;
623
624
0
    CPLAssert(nGeomCount == 0);
625
0
    nGeomCount = nGeomCountNew;
626
627
    // coverity[tainted_data]
628
0
    papoGeoms = static_cast<OGRGeometry **>(
629
0
        VSI_CALLOC_VERBOSE(sizeof(OGRGeometry *), nGeomCount));
630
0
    if (nGeomCount != 0 && papoGeoms == nullptr)
631
0
    {
632
0
        nGeomCount = 0;
633
0
        return OGRERR_NOT_ENOUGH_MEMORY;
634
0
    }
635
636
    /* -------------------------------------------------------------------- */
637
    /*      Get the Geoms.                                                  */
638
    /* -------------------------------------------------------------------- */
639
0
    for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
640
0
    {
641
        // Parses sub-geometry.
642
0
        const unsigned char *pabySubData = pabyData + nDataOffset;
643
0
        if (nSize < 9 && nSize != static_cast<size_t>(-1))
644
0
            return OGRERR_NOT_ENOUGH_DATA;
645
646
0
        OGRwkbGeometryType eSubGeomType = wkbUnknown;
647
0
        eErr = OGRReadWKBGeometryType(pabySubData, eWkbVariant, &eSubGeomType);
648
0
        if (eErr != OGRERR_NONE)
649
0
            return eErr;
650
651
0
        if (!isCompatibleSubType(eSubGeomType))
652
0
        {
653
0
            nGeomCount = iGeom;
654
0
            CPLDebug(
655
0
                "OGR",
656
0
                "Cannot add geometry of type (%d) to geometry of type (%d)",
657
0
                eSubGeomType, getGeometryType());
658
0
            return OGRERR_CORRUPT_DATA;
659
0
        }
660
661
0
        OGRGeometry *poSubGeom = nullptr;
662
0
        size_t nSubGeomBytesConsumed = 0;
663
0
        if (OGR_GT_IsSubClassOf(eSubGeomType, wkbGeometryCollection))
664
0
        {
665
0
            poSubGeom = OGRGeometryFactory::createGeometry(eSubGeomType);
666
0
            if (poSubGeom == nullptr)
667
0
                eErr = OGRERR_FAILURE;
668
0
            else
669
0
                eErr = poSubGeom->toGeometryCollection()->importFromWkbInternal(
670
0
                    pabySubData, nSize, nRecLevel + 1, eWkbVariant,
671
0
                    nSubGeomBytesConsumed);
672
0
        }
673
0
        else
674
0
        {
675
0
            eErr = OGRGeometryFactory::createFromWkb(
676
0
                pabySubData, nullptr, &poSubGeom, nSize, eWkbVariant,
677
0
                nSubGeomBytesConsumed);
678
679
0
            if (eErr == OGRERR_NONE)
680
0
            {
681
                // if this is a Z or M geom make sure the sub geoms are as well
682
0
                if (Is3D() && !poSubGeom->Is3D())
683
0
                {
684
0
                    CPLDebug("OGR", "Promoting sub-geometry to 3D");
685
0
                    poSubGeom->set3D(TRUE);
686
0
                }
687
688
0
                if (IsMeasured() && !poSubGeom->IsMeasured())
689
0
                {
690
0
                    CPLDebug("OGR", "Promoting sub-geometry to Measured");
691
0
                    poSubGeom->setMeasured(TRUE);
692
0
                }
693
0
            }
694
0
        }
695
696
0
        if (eErr != OGRERR_NONE)
697
0
        {
698
0
            nGeomCount = iGeom;
699
0
            delete poSubGeom;
700
0
            return eErr;
701
0
        }
702
703
0
        papoGeoms[iGeom] = poSubGeom;
704
705
0
        if (papoGeoms[iGeom]->Is3D())
706
0
            flags |= OGR_G_3D;
707
0
        if (papoGeoms[iGeom]->IsMeasured())
708
0
            flags |= OGR_G_MEASURED;
709
710
0
        CPLAssert(nSubGeomBytesConsumed > 0);
711
0
        if (nSize != static_cast<size_t>(-1))
712
0
        {
713
0
            CPLAssert(nSize >= nSubGeomBytesConsumed);
714
0
            nSize -= nSubGeomBytesConsumed;
715
0
        }
716
717
0
        nDataOffset += nSubGeomBytesConsumed;
718
0
    }
719
0
    nBytesConsumedOut = nDataOffset;
720
721
0
    return OGRERR_NONE;
722
0
}
723
724
//! @endcond
725
726
/************************************************************************/
727
/*                           importFromWkb()                            */
728
/*                                                                      */
729
/*      Initialize from serialized stream in well known binary          */
730
/*      format.                                                         */
731
/************************************************************************/
732
733
OGRErr OGRGeometryCollection::importFromWkb(const unsigned char *pabyData,
734
                                            size_t nSize,
735
                                            OGRwkbVariant eWkbVariant,
736
                                            size_t &nBytesConsumedOut)
737
738
0
{
739
0
    return importFromWkbInternal(pabyData, nSize, 0, eWkbVariant,
740
0
                                 nBytesConsumedOut);
741
0
}
742
743
/************************************************************************/
744
/*                            exportToWkb()                             */
745
/*                                                                      */
746
/*      Build a well known binary representation of this object.        */
747
/************************************************************************/
748
749
OGRErr
750
OGRGeometryCollection::exportToWkb(unsigned char *pabyData,
751
                                   const OGRwkbExportOptions *psOptions) const
752
753
0
{
754
0
    if (psOptions == nullptr)
755
0
    {
756
0
        static const OGRwkbExportOptions defaultOptions;
757
0
        psOptions = &defaultOptions;
758
0
    }
759
760
0
    OGRwkbExportOptions sOptions(*psOptions);
761
762
0
    if (sOptions.eWkbVariant == wkbVariantOldOgc &&
763
0
        (wkbFlatten(getGeometryType()) == wkbMultiCurve ||
764
0
         wkbFlatten(getGeometryType()) == wkbMultiSurface))
765
0
    {
766
        // Does not make sense for new geometries, so patch it.
767
0
        sOptions.eWkbVariant = wkbVariantIso;
768
0
    }
769
770
    /* -------------------------------------------------------------------- */
771
    /*      Set the byte order.                                             */
772
    /* -------------------------------------------------------------------- */
773
0
    pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER(
774
0
        static_cast<unsigned char>(sOptions.eByteOrder));
775
776
    /* -------------------------------------------------------------------- */
777
    /*      Set the geometry feature type, ensuring that 3D flag is         */
778
    /*      preserved.                                                      */
779
    /* -------------------------------------------------------------------- */
780
0
    GUInt32 nGType = getGeometryType();
781
782
0
    if (sOptions.eWkbVariant == wkbVariantIso)
783
0
        nGType = getIsoGeometryType();
784
0
    else if (sOptions.eWkbVariant == wkbVariantPostGIS1)
785
0
    {
786
0
        const bool bIs3D = wkbHasZ(static_cast<OGRwkbGeometryType>(nGType));
787
0
        nGType = wkbFlatten(nGType);
788
0
        if (nGType == wkbMultiCurve)
789
0
            nGType = POSTGIS15_MULTICURVE;
790
0
        else if (nGType == wkbMultiSurface)
791
0
            nGType = POSTGIS15_MULTISURFACE;
792
0
        if (bIs3D)
793
            // Yes, explicitly set wkb25DBit.
794
0
            nGType =
795
0
                static_cast<OGRwkbGeometryType>(nGType | wkb25DBitInternalUse);
796
0
    }
797
798
0
    if (OGR_SWAP(sOptions.eByteOrder))
799
0
    {
800
0
        nGType = CPL_SWAP32(nGType);
801
0
    }
802
803
0
    memcpy(pabyData + 1, &nGType, 4);
804
805
    /* -------------------------------------------------------------------- */
806
    /*      Copy in the raw data.                                           */
807
    /* -------------------------------------------------------------------- */
808
0
    if (OGR_SWAP(sOptions.eByteOrder))
809
0
    {
810
0
        int nCount = CPL_SWAP32(nGeomCount);
811
0
        memcpy(pabyData + 5, &nCount, 4);
812
0
    }
813
0
    else
814
0
    {
815
0
        memcpy(pabyData + 5, &nGeomCount, 4);
816
0
    }
817
818
0
    size_t nOffset = 9;
819
820
    /* ==================================================================== */
821
    /*      Serialize each of the Geoms.                                    */
822
    /* ==================================================================== */
823
0
    int iGeom = 0;
824
0
    for (auto &&poSubGeom : *this)
825
0
    {
826
0
        poSubGeom->exportToWkb(pabyData + nOffset, &sOptions);
827
        // Should normally not happen if everyone else does its job,
828
        // but has happened sometimes. (#6332)
829
0
        if (poSubGeom->getCoordinateDimension() != getCoordinateDimension())
830
0
        {
831
0
            CPLError(CE_Warning, CPLE_AppDefined,
832
0
                     "Sub-geometry %d has coordinate dimension %d, "
833
0
                     "but container has %d",
834
0
                     iGeom, poSubGeom->getCoordinateDimension(),
835
0
                     getCoordinateDimension());
836
0
        }
837
838
0
        nOffset += poSubGeom->WkbSize();
839
0
        iGeom++;
840
0
    }
841
842
0
    return OGRERR_NONE;
843
0
}
844
845
/************************************************************************/
846
/*                       importFromWktInternal()                        */
847
/************************************************************************/
848
849
OGRErr OGRGeometryCollection::importFromWktInternal(const char **ppszInput,
850
                                                    int nRecLevel)
851
852
0
{
853
    // Arbitrary value, but certainly large enough for reasonable usages.
854
0
    if (nRecLevel == 32)
855
0
    {
856
0
        CPLError(CE_Failure, CPLE_AppDefined,
857
0
                 "Too many recursion levels (%d) while parsing WKT geometry.",
858
0
                 nRecLevel);
859
0
        return OGRERR_CORRUPT_DATA;
860
0
    }
861
862
0
    int bHasZ = FALSE;
863
0
    int bHasM = FALSE;
864
0
    bool bIsEmpty = false;
865
0
    OGRErr eErr = importPreambleFromWkt(ppszInput, &bHasZ, &bHasM, &bIsEmpty);
866
0
    if (eErr != OGRERR_NONE)
867
0
        return eErr;
868
0
    if (bHasZ)
869
0
        flags |= OGR_G_3D;
870
0
    if (bHasM)
871
0
        flags |= OGR_G_MEASURED;
872
0
    if (bIsEmpty)
873
0
        return OGRERR_NONE;
874
875
0
    char szToken[OGR_WKT_TOKEN_MAX] = {};
876
0
    const char *pszInput = *ppszInput;
877
878
    // Skip first '('.
879
0
    pszInput = OGRWktReadToken(pszInput, szToken);
880
881
    /* ==================================================================== */
882
    /*      Read each subgeometry in turn.                                  */
883
    /* ==================================================================== */
884
0
    do
885
0
    {
886
0
        OGRGeometry *poGeom = nullptr;
887
888
        /* --------------------------------------------------------------------
889
         */
890
        /*      Get the first token, which should be the geometry type. */
891
        /* --------------------------------------------------------------------
892
         */
893
0
        OGRWktReadToken(pszInput, szToken);
894
895
        /* --------------------------------------------------------------------
896
         */
897
        /*      Do the import. */
898
        /* --------------------------------------------------------------------
899
         */
900
0
        if (STARTS_WITH_CI(szToken, "GEOMETRYCOLLECTION"))
901
0
        {
902
0
            OGRGeometryCollection *poGC = new OGRGeometryCollection();
903
0
            poGeom = poGC;
904
0
            eErr = poGC->importFromWktInternal(&pszInput, nRecLevel + 1);
905
0
        }
906
0
        else
907
0
            eErr =
908
0
                OGRGeometryFactory::createFromWkt(&pszInput, nullptr, &poGeom);
909
910
0
        if (eErr == OGRERR_NONE)
911
0
        {
912
            // If this has M, but not Z, it is an error if poGeom does
913
            // not have M.
914
0
            if (!Is3D() && IsMeasured() && !poGeom->IsMeasured())
915
0
                eErr = OGRERR_CORRUPT_DATA;
916
0
            else
917
0
                eErr = addGeometryDirectly(poGeom);
918
0
        }
919
0
        if (eErr != OGRERR_NONE)
920
0
        {
921
0
            delete poGeom;
922
0
            return eErr;
923
0
        }
924
925
        /* --------------------------------------------------------------------
926
         */
927
        /*      Read the delimiter following the ring. */
928
        /* --------------------------------------------------------------------
929
         */
930
931
0
        pszInput = OGRWktReadToken(pszInput, szToken);
932
0
    } while (szToken[0] == ',');
933
934
    /* -------------------------------------------------------------------- */
935
    /*      freak if we don't get a closing bracket.                        */
936
    /* -------------------------------------------------------------------- */
937
0
    if (szToken[0] != ')')
938
0
        return OGRERR_CORRUPT_DATA;
939
940
0
    *ppszInput = pszInput;
941
942
0
    return OGRERR_NONE;
943
0
}
944
945
/************************************************************************/
946
/*                           importFromWkt()                            */
947
/************************************************************************/
948
949
OGRErr OGRGeometryCollection::importFromWkt(const char **ppszInput)
950
951
0
{
952
0
    return importFromWktInternal(ppszInput, 0);
953
0
}
954
955
/************************************************************************/
956
/*                            exportToWkt()                             */
957
/*                                                                      */
958
/*      Translate this structure into its well known text format        */
959
/*      equivalent.                                                     */
960
/************************************************************************/
961
962
std::string OGRGeometryCollection::exportToWkt(const OGRWktOptions &opts,
963
                                               OGRErr *err) const
964
0
{
965
0
    return exportToWktInternal(opts, err);
966
0
}
967
968
//! @cond Doxygen_Suppress
969
std::string OGRGeometryCollection::exportToWktInternal(
970
    const OGRWktOptions &opts, OGRErr *err, const std::string &exclude) const
971
0
{
972
0
    bool first = true;
973
0
    const size_t excludeSize = exclude.size();
974
0
    std::string wkt(getGeometryName());
975
0
    wkt += wktTypeString(opts.variant);
976
977
0
    try
978
0
    {
979
0
        for (const auto &poSubGeom : *this)
980
0
        {
981
0
            OGRErr subgeomErr = OGRERR_NONE;
982
0
            std::string tempWkt = poSubGeom->exportToWkt(opts, &subgeomErr);
983
0
            if (subgeomErr != OGRERR_NONE)
984
0
            {
985
0
                if (err)
986
0
                    *err = subgeomErr;
987
0
                return std::string();
988
0
            }
989
990
            // For some strange reason we exclude the typename leader when using
991
            // some geometries as part of a collection.
992
0
            if (excludeSize && (tempWkt.compare(0, excludeSize, exclude) == 0))
993
0
            {
994
0
                auto pos = tempWkt.find('(');
995
                // We won't have an opening paren if the geom is empty.
996
0
                if (pos == std::string::npos)
997
0
                    continue;
998
0
                tempWkt = tempWkt.substr(pos);
999
0
            }
1000
1001
            // Also strange, we allow the inclusion of ISO-only geometries (see
1002
            // OGRPolyhedralSurface) in a non-iso geometry collection.  In order
1003
            // to facilitate this, we need to rip the ISO bit from the string.
1004
0
            if (opts.variant != wkbVariantIso)
1005
0
            {
1006
0
                std::string::size_type pos;
1007
0
                if ((pos = tempWkt.find(" Z ")) != std::string::npos)
1008
0
                    tempWkt.erase(pos + 1, 2);
1009
0
                else if ((pos = tempWkt.find(" M ")) != std::string::npos)
1010
0
                    tempWkt.erase(pos + 1, 2);
1011
0
                else if ((pos = tempWkt.find(" ZM ")) != std::string::npos)
1012
0
                    tempWkt.erase(pos + 1, 3);
1013
0
            }
1014
1015
0
            if (first)
1016
0
                wkt += '(';
1017
0
            else
1018
0
                wkt += ',';
1019
0
            first = false;
1020
0
            wkt += tempWkt;
1021
0
        }
1022
1023
0
        if (err)
1024
0
            *err = OGRERR_NONE;
1025
0
        if (first)
1026
0
            wkt += "EMPTY";
1027
0
        else
1028
0
            wkt += ')';
1029
0
        return wkt;
1030
0
    }
1031
0
    catch (const std::bad_alloc &e)
1032
0
    {
1033
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1034
0
        if (err)
1035
0
            *err = OGRERR_FAILURE;
1036
0
        return std::string();
1037
0
    }
1038
0
}
1039
1040
//! @endcond
1041
1042
/************************************************************************/
1043
/*                            getEnvelope()                             */
1044
/************************************************************************/
1045
1046
void OGRGeometryCollection::getEnvelope(OGREnvelope *psEnvelope) const
1047
1048
0
{
1049
0
    OGREnvelope3D oEnv3D;
1050
0
    getEnvelope(&oEnv3D);
1051
0
    psEnvelope->MinX = oEnv3D.MinX;
1052
0
    psEnvelope->MinY = oEnv3D.MinY;
1053
0
    psEnvelope->MaxX = oEnv3D.MaxX;
1054
0
    psEnvelope->MaxY = oEnv3D.MaxY;
1055
0
}
1056
1057
/************************************************************************/
1058
/*                            getEnvelope()                             */
1059
/************************************************************************/
1060
1061
void OGRGeometryCollection::getEnvelope(OGREnvelope3D *psEnvelope) const
1062
1063
0
{
1064
0
    OGREnvelope3D oGeomEnv;
1065
0
    bool bExtentSet = false;
1066
1067
0
    *psEnvelope = OGREnvelope3D();
1068
0
    for (const auto &poSubGeom : *this)
1069
0
    {
1070
0
        if (!poSubGeom->IsEmpty())
1071
0
        {
1072
0
            bExtentSet = true;
1073
0
            poSubGeom->getEnvelope(&oGeomEnv);
1074
0
            psEnvelope->Merge(oGeomEnv);
1075
0
        }
1076
0
    }
1077
1078
0
    if (!bExtentSet)
1079
0
    {
1080
        // To be backward compatible when called on empty geom
1081
0
        psEnvelope->MinX = 0.0;
1082
0
        psEnvelope->MinY = 0.0;
1083
0
        psEnvelope->MinZ = 0.0;
1084
0
        psEnvelope->MaxX = 0.0;
1085
0
        psEnvelope->MaxY = 0.0;
1086
0
        psEnvelope->MaxZ = 0.0;
1087
0
    }
1088
0
}
1089
1090
/************************************************************************/
1091
/*                               Equals()                               */
1092
/************************************************************************/
1093
1094
OGRBoolean OGRGeometryCollection::Equals(const OGRGeometry *poOther) const
1095
1096
0
{
1097
0
    if (poOther == this)
1098
0
        return TRUE;
1099
1100
0
    if (poOther->getGeometryType() != getGeometryType())
1101
0
        return FALSE;
1102
1103
0
    if (IsEmpty() && poOther->IsEmpty())
1104
0
        return TRUE;
1105
1106
0
    auto poOGC = poOther->toGeometryCollection();
1107
0
    if (getNumGeometries() != poOGC->getNumGeometries())
1108
0
        return FALSE;
1109
1110
    // TODO(schwehr): Should test the SRS.
1111
1112
0
    for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
1113
0
    {
1114
0
        if (!getGeometryRef(iGeom)->Equals(poOGC->getGeometryRef(iGeom)))
1115
0
            return FALSE;
1116
0
    }
1117
1118
0
    return TRUE;
1119
0
}
1120
1121
/************************************************************************/
1122
/*                             transform()                              */
1123
/************************************************************************/
1124
1125
OGRErr OGRGeometryCollection::transform(OGRCoordinateTransformation *poCT)
1126
1127
0
{
1128
0
    int iGeom = 0;
1129
0
    for (auto &poSubGeom : *this)
1130
0
    {
1131
0
        const OGRErr eErr = poSubGeom->transform(poCT);
1132
0
        if (eErr != OGRERR_NONE)
1133
0
        {
1134
0
            if (iGeom != 0)
1135
0
            {
1136
0
                CPLDebug("OGR",
1137
0
                         "OGRGeometryCollection::transform() failed for a "
1138
0
                         "geometry other than the first, meaning some "
1139
0
                         "geometries are transformed and some are not.");
1140
1141
0
                return OGRERR_FAILURE;
1142
0
            }
1143
1144
0
            return eErr;
1145
0
        }
1146
0
        iGeom++;
1147
0
    }
1148
1149
0
    assignSpatialReference(poCT->GetTargetCS());
1150
1151
0
    return OGRERR_NONE;
1152
0
}
1153
1154
/************************************************************************/
1155
/*                             closeRings()                             */
1156
/************************************************************************/
1157
1158
void OGRGeometryCollection::closeRings()
1159
1160
0
{
1161
0
    for (auto &poSubGeom : *this)
1162
0
    {
1163
0
        if (OGR_GT_IsSubClassOf(wkbFlatten(poSubGeom->getGeometryType()),
1164
0
                                wkbCurvePolygon))
1165
0
        {
1166
0
            OGRCurvePolygon *poPoly = poSubGeom->toCurvePolygon();
1167
0
            poPoly->closeRings();
1168
0
        }
1169
0
    }
1170
0
}
1171
1172
/************************************************************************/
1173
/*                       setCoordinateDimension()                       */
1174
/************************************************************************/
1175
1176
bool OGRGeometryCollection::setCoordinateDimension(int nNewDimension)
1177
1178
0
{
1179
0
    for (auto &poSubGeom : *this)
1180
0
    {
1181
0
        if (!poSubGeom->setCoordinateDimension(nNewDimension))
1182
0
            return false;
1183
0
    }
1184
1185
0
    return OGRGeometry::setCoordinateDimension(nNewDimension);
1186
0
}
1187
1188
bool OGRGeometryCollection::set3D(OGRBoolean bIs3D)
1189
0
{
1190
0
    for (auto &poSubGeom : *this)
1191
0
    {
1192
0
        if (!poSubGeom->set3D(bIs3D))
1193
0
            return false;
1194
0
    }
1195
1196
0
    return OGRGeometry::set3D(bIs3D);
1197
0
}
1198
1199
bool OGRGeometryCollection::setMeasured(OGRBoolean bIsMeasured)
1200
0
{
1201
0
    for (auto &poSubGeom : *this)
1202
0
    {
1203
0
        if (!poSubGeom->setMeasured(bIsMeasured))
1204
0
            return false;
1205
0
    }
1206
1207
0
    return OGRGeometry::setMeasured(bIsMeasured);
1208
0
}
1209
1210
/************************************************************************/
1211
/*                              get_Length()                            */
1212
/************************************************************************/
1213
1214
/**
1215
 * \brief Compute the length of a multicurve.
1216
 *
1217
 * The length is computed as the sum of the length of all members
1218
 * in this collection.
1219
 *
1220
 * @note No warning will be issued if a member of the collection does not
1221
 *       support the get_Length method.
1222
 *
1223
 * @return computed length.
1224
 */
1225
1226
double OGRGeometryCollection::get_Length() const
1227
0
{
1228
0
    double dfLength = 0.0;
1229
0
    for (const auto &poSubGeom : *this)
1230
0
    {
1231
0
        const OGRwkbGeometryType eType =
1232
0
            wkbFlatten(poSubGeom->getGeometryType());
1233
0
        if (OGR_GT_IsCurve(eType))
1234
0
        {
1235
0
            const OGRCurve *poCurve = poSubGeom->toCurve();
1236
0
            dfLength += poCurve->get_Length();
1237
0
        }
1238
0
        else if (OGR_GT_IsSurface(eType))
1239
0
        {
1240
0
            const OGRSurface *poSurface = poSubGeom->toSurface();
1241
0
            dfLength += poSurface->get_Length();
1242
0
        }
1243
0
        else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1244
0
        {
1245
0
            const OGRGeometryCollection *poColl =
1246
0
                poSubGeom->toGeometryCollection();
1247
0
            dfLength += poColl->get_Length();
1248
0
        }
1249
0
    }
1250
1251
0
    return dfLength;
1252
0
}
1253
1254
/************************************************************************/
1255
/*                              get_Area()                              */
1256
/************************************************************************/
1257
1258
/**
1259
 * \brief Compute area of geometry collection.
1260
 *
1261
 * The area is computed as the sum of the areas of all members
1262
 * in this collection.
1263
 *
1264
 * @note No warning will be issued if a member of the collection does not
1265
 *       support the get_Area method.
1266
 *
1267
 * @return computed area.
1268
 */
1269
1270
double OGRGeometryCollection::get_Area() const
1271
0
{
1272
0
    double dfArea = 0.0;
1273
0
    for (const auto &poSubGeom : *this)
1274
0
    {
1275
0
        OGRwkbGeometryType eType = wkbFlatten(poSubGeom->getGeometryType());
1276
0
        if (OGR_GT_IsSurface(eType))
1277
0
        {
1278
0
            const OGRSurface *poSurface = poSubGeom->toSurface();
1279
0
            dfArea += poSurface->get_Area();
1280
0
        }
1281
0
        else if (OGR_GT_IsCurve(eType))
1282
0
        {
1283
0
            const OGRCurve *poCurve = poSubGeom->toCurve();
1284
0
            dfArea += poCurve->get_Area();
1285
0
        }
1286
0
        else if (OGR_GT_IsSubClassOf(eType, wkbMultiSurface) ||
1287
0
                 eType == wkbGeometryCollection)
1288
0
        {
1289
0
            dfArea += poSubGeom->toGeometryCollection()->get_Area();
1290
0
        }
1291
0
    }
1292
1293
0
    return dfArea;
1294
0
}
1295
1296
/************************************************************************/
1297
/*                        get_GeodesicArea()                            */
1298
/************************************************************************/
1299
1300
/**
1301
 * \brief Compute area of geometry collection, considered as a surface on
1302
 * the underlying ellipsoid of the SRS attached to the geometry.
1303
 *
1304
 * The returned area will always be in square meters, and assumes that
1305
 * polygon edges describe geodesic lines on the ellipsoid.
1306
 *
1307
 * <a href="https://geographiclib.sourceforge.io/html/python/geodesics.html">Geodesics</a>
1308
 * follow the shortest route on the surface of the ellipsoid.
1309
 *
1310
 * If the geometry' SRS is not a geographic one, geometries are reprojected to
1311
 * the underlying geographic SRS of the geometry' SRS.
1312
 * OGRSpatialReference::GetDataAxisToSRSAxisMapping() is honored.
1313
 *
1314
 * The area is computed as the sum of the areas of all members
1315
 * in this collection.
1316
 *
1317
 * @note No warning will be issued if a member of the collection does not
1318
 *       support the get_GeodesicArea method.
1319
 *
1320
 * @param poSRSOverride If not null, overrides OGRGeometry::getSpatialReference()
1321
 * @return the area of the geometry in square meters, or a negative value in case
1322
 * of error.
1323
 *
1324
 * @see get_Area() for an alternative method returning areas computed in
1325
 * 2D Cartesian space.
1326
 *
1327
 * @since GDAL 3.9
1328
 */
1329
double OGRGeometryCollection::get_GeodesicArea(
1330
    const OGRSpatialReference *poSRSOverride) const
1331
0
{
1332
0
    double dfArea = 0.0;
1333
0
    for (const auto &poSubGeom : *this)
1334
0
    {
1335
0
        OGRwkbGeometryType eType = wkbFlatten(poSubGeom->getGeometryType());
1336
0
        if (OGR_GT_IsSurface(eType))
1337
0
        {
1338
0
            const OGRSurface *poSurface = poSubGeom->toSurface();
1339
0
            const double dfLocalArea =
1340
0
                poSurface->get_GeodesicArea(poSRSOverride);
1341
0
            if (dfLocalArea < 0)
1342
0
                return dfLocalArea;
1343
0
            dfArea += dfLocalArea;
1344
0
        }
1345
0
        else if (OGR_GT_IsCurve(eType))
1346
0
        {
1347
0
            const OGRCurve *poCurve = poSubGeom->toCurve();
1348
0
            const double dfLocalArea = poCurve->get_GeodesicArea(poSRSOverride);
1349
0
            if (dfLocalArea < 0)
1350
0
                return dfLocalArea;
1351
0
            dfArea += dfLocalArea;
1352
0
        }
1353
0
        else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1354
0
        {
1355
0
            const double dfLocalArea =
1356
0
                poSubGeom->toGeometryCollection()->get_GeodesicArea(
1357
0
                    poSRSOverride);
1358
0
            if (dfLocalArea < 0)
1359
0
                return dfLocalArea;
1360
0
            dfArea += dfLocalArea;
1361
0
        }
1362
0
    }
1363
1364
0
    return dfArea;
1365
0
}
1366
1367
/************************************************************************/
1368
/*                        get_GeodesicLength()                          */
1369
/************************************************************************/
1370
1371
/**
1372
 * \brief Get the length of the collection,where curve edges are geodesic lines
1373
 * on the underlying ellipsoid of the SRS attached to the geometry.
1374
 *
1375
 * The returned length will always be in meters.
1376
 *
1377
 * <a href="https://geographiclib.sourceforge.io/html/python/geodesics.html">Geodesics</a>
1378
 * follow the shortest route on the surface of the ellipsoid.
1379
 *
1380
 * If the geometry' SRS is not a geographic one, geometries are reprojected to
1381
 * the underlying geographic SRS of the geometry' SRS.
1382
 * OGRSpatialReference::GetDataAxisToSRSAxisMapping() is honored.
1383
 *
1384
 * Note that geometries with circular arcs will be linearized in their original
1385
 * coordinate space first, so the resulting geodesic length will be an
1386
 * approximation.
1387
 *
1388
 * The length is computed as the sum of the lengths of all members
1389
 * in this collection.
1390
 *
1391
 * @note No warning will be issued if a member of the collection does not
1392
 *       support the get_GeodesicLength method.
1393
 *
1394
 * @param poSRSOverride If not null, overrides OGRGeometry::getSpatialReference()
1395
 * @return the length of the geometry in meters, or a negative value in case
1396
 * of error.
1397
 *
1398
 * @see get_Length() for an alternative method returning areas computed in
1399
 * 2D Cartesian space.
1400
 *
1401
 * @since GDAL 3.10
1402
 */
1403
double OGRGeometryCollection::get_GeodesicLength(
1404
    const OGRSpatialReference *poSRSOverride) const
1405
0
{
1406
0
    double dfLength = 0.0;
1407
0
    for (const auto &poSubGeom : *this)
1408
0
    {
1409
0
        const OGRwkbGeometryType eType =
1410
0
            wkbFlatten(poSubGeom->getGeometryType());
1411
0
        if (OGR_GT_IsSurface(eType))
1412
0
        {
1413
0
            const OGRSurface *poSurface = poSubGeom->toSurface();
1414
0
            const double dfLocalLength =
1415
0
                poSurface->get_GeodesicLength(poSRSOverride);
1416
0
            if (dfLocalLength < 0)
1417
0
                return dfLocalLength;
1418
0
            dfLength += dfLocalLength;
1419
0
        }
1420
0
        else if (OGR_GT_IsCurve(eType))
1421
0
        {
1422
0
            const OGRCurve *poCurve = poSubGeom->toCurve();
1423
0
            const double dfLocalLength =
1424
0
                poCurve->get_GeodesicLength(poSRSOverride);
1425
0
            if (dfLocalLength < 0)
1426
0
                return dfLocalLength;
1427
0
            dfLength += dfLocalLength;
1428
0
        }
1429
0
        else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1430
0
        {
1431
0
            const double dfLocalLength =
1432
0
                poSubGeom->toGeometryCollection()->get_GeodesicLength(
1433
0
                    poSRSOverride);
1434
0
            if (dfLocalLength < 0)
1435
0
                return dfLocalLength;
1436
0
            dfLength += dfLocalLength;
1437
0
        }
1438
0
    }
1439
1440
0
    return dfLength;
1441
0
}
1442
1443
/************************************************************************/
1444
/*                               IsEmpty()                              */
1445
/************************************************************************/
1446
1447
OGRBoolean OGRGeometryCollection::IsEmpty() const
1448
0
{
1449
0
    for (const auto &poSubGeom : *this)
1450
0
    {
1451
0
        if (poSubGeom->IsEmpty() == FALSE)
1452
0
            return FALSE;
1453
0
    }
1454
0
    return TRUE;
1455
0
}
1456
1457
/************************************************************************/
1458
/*                       assignSpatialReference()                       */
1459
/************************************************************************/
1460
1461
void OGRGeometryCollection::assignSpatialReference(
1462
    const OGRSpatialReference *poSR)
1463
0
{
1464
0
    OGRGeometry::assignSpatialReference(poSR);
1465
0
    for (auto &poSubGeom : *this)
1466
0
    {
1467
0
        poSubGeom->assignSpatialReference(poSR);
1468
0
    }
1469
0
}
1470
1471
/************************************************************************/
1472
/*              OGRGeometryCollection::segmentize()                     */
1473
/************************************************************************/
1474
1475
bool OGRGeometryCollection::segmentize(double dfMaxLength)
1476
0
{
1477
0
    for (auto &poSubGeom : *this)
1478
0
    {
1479
0
        if (!poSubGeom->segmentize(dfMaxLength))
1480
0
            return false;
1481
0
    }
1482
0
    return true;
1483
0
}
1484
1485
/************************************************************************/
1486
/*                               swapXY()                               */
1487
/************************************************************************/
1488
1489
void OGRGeometryCollection::swapXY()
1490
0
{
1491
0
    for (auto &poSubGeom : *this)
1492
0
    {
1493
0
        poSubGeom->swapXY();
1494
0
    }
1495
0
}
1496
1497
/************************************************************************/
1498
/*                          isCompatibleSubType()                       */
1499
/************************************************************************/
1500
1501
/** Returns whether a geometry of the specified geometry type can be a
1502
 * member of this collection.
1503
 *
1504
 * @param eSubType type of the potential member
1505
 * @return TRUE or FALSE
1506
 */
1507
1508
OGRBoolean OGRGeometryCollection::isCompatibleSubType(
1509
    CPL_UNUSED OGRwkbGeometryType eSubType) const
1510
0
{
1511
    // Accept all geometries as sub-geometries.
1512
0
    return TRUE;
1513
0
}
1514
1515
/************************************************************************/
1516
/*                         hasCurveGeometry()                           */
1517
/************************************************************************/
1518
1519
OGRBoolean OGRGeometryCollection::hasCurveGeometry(int bLookForNonLinear) const
1520
0
{
1521
0
    for (const auto &poSubGeom : *this)
1522
0
    {
1523
0
        if (poSubGeom->hasCurveGeometry(bLookForNonLinear))
1524
0
            return TRUE;
1525
0
    }
1526
0
    return FALSE;
1527
0
}
1528
1529
/************************************************************************/
1530
/*                         getLinearGeometry()                        */
1531
/************************************************************************/
1532
1533
OGRGeometry *
1534
OGRGeometryCollection::getLinearGeometry(double dfMaxAngleStepSizeDegrees,
1535
                                         const char *const *papszOptions) const
1536
0
{
1537
0
    auto poGC = std::unique_ptr<OGRGeometryCollection>(
1538
0
        OGRGeometryFactory::createGeometry(OGR_GT_GetLinear(getGeometryType()))
1539
0
            ->toGeometryCollection());
1540
0
    if (!poGC)
1541
0
        return nullptr;
1542
0
    poGC->assignSpatialReference(getSpatialReference());
1543
0
    for (const auto &poSubGeom : *this)
1544
0
    {
1545
0
        OGRGeometry *poSubGeomNew = poSubGeom->getLinearGeometry(
1546
0
            dfMaxAngleStepSizeDegrees, papszOptions);
1547
0
        if (poGC->addGeometryDirectly(poSubGeomNew) != OGRERR_NONE)
1548
0
            return nullptr;
1549
0
    }
1550
0
    return poGC.release();
1551
0
}
1552
1553
/************************************************************************/
1554
/*                             getCurveGeometry()                       */
1555
/************************************************************************/
1556
1557
OGRGeometry *
1558
OGRGeometryCollection::getCurveGeometry(const char *const *papszOptions) const
1559
0
{
1560
0
    auto poGC = std::unique_ptr<OGRGeometryCollection>(
1561
0
        OGRGeometryFactory::createGeometry(OGR_GT_GetCurve(getGeometryType()))
1562
0
            ->toGeometryCollection());
1563
0
    if (!poGC)
1564
0
        return nullptr;
1565
0
    poGC->assignSpatialReference(getSpatialReference());
1566
0
    bool bHasCurveGeometry = false;
1567
0
    for (const auto &poSubGeom : *this)
1568
0
    {
1569
0
        OGRGeometry *poSubGeomNew = poSubGeom->getCurveGeometry(papszOptions);
1570
0
        if (poSubGeomNew->hasCurveGeometry())
1571
0
            bHasCurveGeometry = true;
1572
0
        if (poGC->addGeometryDirectly(poSubGeomNew) != OGRERR_NONE)
1573
0
            return nullptr;
1574
0
    }
1575
0
    if (!bHasCurveGeometry)
1576
0
    {
1577
0
        return clone();
1578
0
    }
1579
0
    return poGC.release();
1580
0
}
1581
1582
/************************************************************************/
1583
/*                      TransferMembersAndDestroy()                     */
1584
/************************************************************************/
1585
1586
//! @cond Doxygen_Suppress
1587
OGRGeometryCollection *
1588
OGRGeometryCollection::TransferMembersAndDestroy(OGRGeometryCollection *poSrc,
1589
                                                 OGRGeometryCollection *poDst)
1590
0
{
1591
0
    poDst->assignSpatialReference(poSrc->getSpatialReference());
1592
0
    poDst->set3D(poSrc->Is3D());
1593
0
    poDst->setMeasured(poSrc->IsMeasured());
1594
0
    poDst->nGeomCount = poSrc->nGeomCount;
1595
0
    poDst->papoGeoms = poSrc->papoGeoms;
1596
0
    poSrc->nGeomCount = 0;
1597
0
    poSrc->papoGeoms = nullptr;
1598
0
    delete poSrc;
1599
0
    return poDst;
1600
0
}
1601
1602
//! @endcond
1603
1604
/************************************************************************/
1605
/*                        CastToGeometryCollection()                    */
1606
/************************************************************************/
1607
1608
/**
1609
 * \brief Cast to geometry collection.
1610
 *
1611
 * This methods cast a derived class of geometry collection to a plain
1612
 * geometry collection.
1613
 *
1614
 * The passed in geometry is consumed and a new one returned (or NULL in case
1615
 * of failure).
1616
 *
1617
 * @param poSrc the input geometry - ownership is passed to the method.
1618
 * @return new geometry.
1619
 * @since GDAL 2.2
1620
 */
1621
1622
OGRGeometryCollection *
1623
OGRGeometryCollection::CastToGeometryCollection(OGRGeometryCollection *poSrc)
1624
0
{
1625
0
    if (wkbFlatten(poSrc->getGeometryType()) == wkbGeometryCollection)
1626
0
        return poSrc;
1627
0
    return TransferMembersAndDestroy(poSrc, new OGRGeometryCollection());
1628
0
}