Coverage Report

Created: 2025-11-16 06:25

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