Coverage Report

Created: 2026-04-01 06:20

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
2.11k
{
83
2.11k
    OGRGeometryCollection::empty();
84
2.11k
}
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
4.08k
{
157
4.08k
    if (papoGeoms != nullptr)
158
1.68k
    {
159
1.68k
        for (auto &poSubGeom : *this)
160
7.16k
        {
161
7.16k
            delete poSubGeom;
162
7.16k
        }
163
1.68k
        CPLFree(papoGeoms);
164
1.68k
    }
165
166
4.08k
    nGeomCount = 0;
167
4.08k
    papoGeoms = nullptr;
168
4.08k
}
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
29
{
196
29
    if ((flags & OGR_G_3D) && (flags & OGR_G_MEASURED))
197
26
        return wkbGeometryCollectionZM;
198
3
    else if (flags & OGR_G_MEASURED)
199
0
        return wkbGeometryCollectionM;
200
3
    else if (flags & OGR_G_3D)
201
2
        return wkbGeometryCollection25D;
202
1
    else
203
1
        return wkbGeometryCollection;
204
29
}
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
207
{
252
207
    return "GEOMETRYCOLLECTION";
253
207
}
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.66k
{
271
3.66k
    return nGeomCount;
272
3.66k
}
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
5.25k
{
296
5.25k
    if (i < 0 || i >= nGeomCount)
297
0
        return nullptr;
298
299
5.25k
    return papoGeoms[i];
300
5.25k
}
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
6.75k
{
395
6.75k
    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
6.75k
    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
6.75k
#endif
412
413
6.75k
    HomogenizeDimensionalityWith(poNewGeom);
414
415
6.75k
    OGRGeometry **papoNewGeoms =
416
6.75k
        static_cast<OGRGeometry **>(VSI_REALLOC_VERBOSE(
417
6.75k
            papoGeoms, sizeof(OGRGeometry *) * (nGeomCount + 1)));
418
6.75k
    if (papoNewGeoms == nullptr)
419
0
        return OGRERR_FAILURE;
420
421
6.75k
    papoGeoms = papoNewGeoms;
422
6.75k
    papoGeoms[nGeomCount] = poNewGeom;
423
424
6.75k
    nGeomCount++;
425
426
6.75k
    return OGRERR_NONE;
427
6.75k
}
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
/*                       addGeometryComponents()                        */
458
/************************************************************************/
459
460
/**
461
 * \brief Add the components of another OGRGeometryCollection to this one.
462
 *
463
 * Some subclasses of OGRGeometryCollection restrict the types of geometry
464
 * that can be added, and may return an error.
465
 *
466
 * @param geom geometry whose components should be added to add to the container.
467
 *
468
 * @return OGRERR_NONE if successful, or OGRERR_UNSUPPORTED_GEOMETRY_TYPE if
469
 * the geometry type is illegal for the type of geometry container.
470
 *
471
 * @since 3.13
472
 */
473
474
OGRErr OGRGeometryCollection::addGeometryComponents(
475
    std::unique_ptr<OGRGeometryCollection> geom)
476
0
{
477
0
    if (geom->nGeomCount == 0)
478
0
    {
479
0
        return OGRERR_NONE;
480
0
    }
481
482
0
    for (int i = 0; i < geom->nGeomCount; i++)
483
0
    {
484
0
        if (!isCompatibleSubType(geom->papoGeoms[i]->getGeometryType()))
485
0
        {
486
0
            return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
487
0
        }
488
0
    }
489
490
0
    for (int i = 0; i < geom->nGeomCount; i++)
491
0
    {
492
0
        HomogenizeDimensionalityWith(geom->papoGeoms[i]);
493
0
    }
494
495
0
    OGRGeometry **papoNewGeoms = static_cast<OGRGeometry **>(
496
0
        VSI_REALLOC_VERBOSE(papoGeoms, sizeof(OGRGeometry *) *
497
0
                                           (nGeomCount + geom->nGeomCount)));
498
0
    if (papoNewGeoms == nullptr)
499
0
        return OGRERR_FAILURE;
500
501
0
    for (int i = 0; i < geom->nGeomCount; i++)
502
0
    {
503
0
        papoNewGeoms[nGeomCount + i] = geom->papoGeoms[i];
504
0
        geom->papoGeoms[i] = nullptr;
505
0
    }
506
507
0
    papoGeoms = papoNewGeoms;
508
0
    nGeomCount += geom->nGeomCount;
509
510
0
    return OGRERR_NONE;
511
0
}
512
513
/************************************************************************/
514
/*                           removeGeometry()                           */
515
/************************************************************************/
516
517
/**
518
 * \brief Remove a geometry from the container.
519
 *
520
 * Removing a geometry will cause the geometry count to drop by one, and all
521
 * "higher" geometries will shuffle down one in index.
522
 *
523
 * There is no SFCOM analog to this method.
524
 *
525
 * This method is the same as the C function OGR_G_RemoveGeometry().
526
 *
527
 * @param iGeom the index of the geometry to delete.  A value of -1 is a
528
 * special flag meaning that all geometries should be removed.
529
 *
530
 * @param bDelete if TRUE the geometry will be deallocated, otherwise it will
531
 * not.  The default is TRUE as the container is considered to own the
532
 * geometries in it. Note: using stealGeometry() might be a better alternative
533
 * to using bDelete = false.
534
 *
535
 * @return OGRERR_NONE if successful, or OGRERR_FAILURE if the index is
536
 * out of range.
537
 */
538
539
OGRErr OGRGeometryCollection::removeGeometry(int iGeom, int bDelete)
540
541
0
{
542
0
    if (iGeom < -1 || iGeom >= nGeomCount)
543
0
        return OGRERR_FAILURE;
544
545
    // Special case.
546
0
    if (iGeom == -1)
547
0
    {
548
0
        while (nGeomCount > 0)
549
0
            removeGeometry(nGeomCount - 1, bDelete);
550
0
        return OGRERR_NONE;
551
0
    }
552
553
0
    if (bDelete)
554
0
        delete papoGeoms[iGeom];
555
556
0
    memmove(papoGeoms + iGeom, papoGeoms + iGeom + 1,
557
0
            sizeof(OGRGeometry *) * (nGeomCount - iGeom - 1));
558
559
0
    nGeomCount--;
560
561
0
    return OGRERR_NONE;
562
0
}
563
564
/************************************************************************/
565
/*                           stealGeometry()                            */
566
/************************************************************************/
567
568
/**
569
 * \brief Remove a geometry from the container and return it to the caller
570
 *
571
 * Removing a geometry will cause the geometry count to drop by one, and all
572
 * "higher" geometries will shuffle down one in index.
573
 *
574
 * There is no SFCOM analog to this method.
575
 *
576
 * @param iGeom the index of the geometry to delete.
577
 *
578
 * @return the sub-geometry, or nullptr in case of error.
579
 * @since 3.10
580
 */
581
582
std::unique_ptr<OGRGeometry> OGRGeometryCollection::stealGeometry(int iGeom)
583
0
{
584
0
    if (iGeom < 0 || iGeom >= nGeomCount)
585
0
        return nullptr;
586
587
0
    auto poSubGeom = std::unique_ptr<OGRGeometry>(papoGeoms[iGeom]);
588
0
    papoGeoms[iGeom] = nullptr;
589
0
    removeGeometry(iGeom);
590
0
    return poSubGeom;
591
0
}
592
593
/************************************************************************/
594
/*                           hasEmptyParts()                            */
595
/************************************************************************/
596
597
bool OGRGeometryCollection::hasEmptyParts() const
598
0
{
599
0
    for (const auto &poSubGeom : *this)
600
0
    {
601
0
        if (poSubGeom->IsEmpty() || poSubGeom->hasEmptyParts())
602
0
            return true;
603
0
    }
604
0
    return false;
605
0
}
606
607
/************************************************************************/
608
/*                          removeEmptyParts()                          */
609
/************************************************************************/
610
611
void OGRGeometryCollection::removeEmptyParts()
612
0
{
613
0
    for (int i = nGeomCount - 1; i >= 0; --i)
614
0
    {
615
0
        papoGeoms[i]->removeEmptyParts();
616
0
        if (papoGeoms[i]->IsEmpty())
617
0
            removeGeometry(i, true);
618
0
    }
619
0
}
620
621
/************************************************************************/
622
/*                              WkbSize()                               */
623
/*                                                                      */
624
/*      Return the size of this object in well known binary             */
625
/*      representation including the byte order, and type information.  */
626
/************************************************************************/
627
628
size_t OGRGeometryCollection::WkbSize() const
629
630
0
{
631
0
    size_t nSize = 9;
632
633
0
    for (const auto &poGeom : *this)
634
0
    {
635
0
        nSize += poGeom->WkbSize();
636
0
    }
637
638
0
    return nSize;
639
0
}
640
641
/************************************************************************/
642
/*                       importFromWkbInternal()                        */
643
/************************************************************************/
644
645
//! @cond Doxygen_Suppress
646
OGRErr OGRGeometryCollection::importFromWkbInternal(
647
    const unsigned char *pabyData, size_t nSize, int nRecLevel,
648
    OGRwkbVariant eWkbVariant, size_t &nBytesConsumedOut)
649
650
0
{
651
0
    nBytesConsumedOut = 0;
652
    // Arbitrary value, but certainly large enough for reasonable use cases.
653
0
    if (nRecLevel == 32)
654
0
    {
655
0
        CPLError(CE_Failure, CPLE_AppDefined,
656
0
                 "Too many recursion levels (%d) while parsing WKB geometry.",
657
0
                 nRecLevel);
658
0
        return OGRERR_CORRUPT_DATA;
659
0
    }
660
661
0
    OGRwkbByteOrder eByteOrder = wkbXDR;
662
0
    size_t nDataOffset = 0;
663
0
    int nGeomCountNew = 0;
664
0
    OGRErr eErr = importPreambleOfCollectionFromWkb(pabyData, nSize,
665
0
                                                    nDataOffset, eByteOrder, 9,
666
0
                                                    nGeomCountNew, eWkbVariant);
667
668
0
    if (eErr != OGRERR_NONE)
669
0
        return eErr;
670
671
0
    CPLAssert(nGeomCount == 0);
672
0
    nGeomCount = nGeomCountNew;
673
674
    // coverity[tainted_data]
675
0
    papoGeoms = static_cast<OGRGeometry **>(
676
0
        VSI_CALLOC_VERBOSE(sizeof(OGRGeometry *), nGeomCount));
677
0
    if (nGeomCount != 0 && papoGeoms == nullptr)
678
0
    {
679
0
        nGeomCount = 0;
680
0
        return OGRERR_NOT_ENOUGH_MEMORY;
681
0
    }
682
683
    /* -------------------------------------------------------------------- */
684
    /*      Get the Geoms.                                                  */
685
    /* -------------------------------------------------------------------- */
686
0
    for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
687
0
    {
688
        // Parses sub-geometry.
689
0
        const unsigned char *pabySubData = pabyData + nDataOffset;
690
0
        if (nSize < 9 && nSize != static_cast<size_t>(-1))
691
0
            return OGRERR_NOT_ENOUGH_DATA;
692
693
0
        OGRwkbGeometryType eSubGeomType = wkbUnknown;
694
0
        eErr = OGRReadWKBGeometryType(pabySubData, eWkbVariant, &eSubGeomType);
695
0
        if (eErr != OGRERR_NONE)
696
0
            return eErr;
697
698
0
        if (!isCompatibleSubType(eSubGeomType))
699
0
        {
700
0
            nGeomCount = iGeom;
701
0
            CPLDebug(
702
0
                "OGR",
703
0
                "Cannot add geometry of type (%d) to geometry of type (%d)",
704
0
                eSubGeomType, getGeometryType());
705
0
            return OGRERR_CORRUPT_DATA;
706
0
        }
707
708
0
        OGRGeometry *poSubGeom = nullptr;
709
0
        size_t nSubGeomBytesConsumed = 0;
710
0
        if (OGR_GT_IsSubClassOf(eSubGeomType, wkbGeometryCollection))
711
0
        {
712
0
            poSubGeom = OGRGeometryFactory::createGeometry(eSubGeomType);
713
0
            if (poSubGeom == nullptr)
714
0
                eErr = OGRERR_FAILURE;
715
0
            else
716
0
                eErr = poSubGeom->toGeometryCollection()->importFromWkbInternal(
717
0
                    pabySubData, nSize, nRecLevel + 1, eWkbVariant,
718
0
                    nSubGeomBytesConsumed);
719
0
        }
720
0
        else
721
0
        {
722
0
            eErr = OGRGeometryFactory::createFromWkb(
723
0
                pabySubData, nullptr, &poSubGeom, nSize, eWkbVariant,
724
0
                nSubGeomBytesConsumed);
725
726
0
            if (eErr == OGRERR_NONE)
727
0
            {
728
                // if this is a Z or M geom make sure the sub geoms are as well
729
0
                if (Is3D() && !poSubGeom->Is3D())
730
0
                {
731
0
                    CPLDebug("OGR", "Promoting sub-geometry to 3D");
732
0
                    poSubGeom->set3D(TRUE);
733
0
                }
734
735
0
                if (IsMeasured() && !poSubGeom->IsMeasured())
736
0
                {
737
0
                    CPLDebug("OGR", "Promoting sub-geometry to Measured");
738
0
                    poSubGeom->setMeasured(TRUE);
739
0
                }
740
0
            }
741
0
        }
742
743
0
        if (eErr != OGRERR_NONE)
744
0
        {
745
0
            nGeomCount = iGeom;
746
0
            delete poSubGeom;
747
0
            return eErr;
748
0
        }
749
750
0
        papoGeoms[iGeom] = poSubGeom;
751
752
0
        if (papoGeoms[iGeom]->Is3D())
753
0
            flags |= OGR_G_3D;
754
0
        if (papoGeoms[iGeom]->IsMeasured())
755
0
            flags |= OGR_G_MEASURED;
756
757
0
        CPLAssert(nSubGeomBytesConsumed > 0);
758
0
        if (nSize != static_cast<size_t>(-1))
759
0
        {
760
0
            CPLAssert(nSize >= nSubGeomBytesConsumed);
761
0
            nSize -= nSubGeomBytesConsumed;
762
0
        }
763
764
0
        nDataOffset += nSubGeomBytesConsumed;
765
0
    }
766
0
    nBytesConsumedOut = nDataOffset;
767
768
0
    return OGRERR_NONE;
769
0
}
770
771
//! @endcond
772
773
/************************************************************************/
774
/*                           importFromWkb()                            */
775
/*                                                                      */
776
/*      Initialize from serialized stream in well known binary          */
777
/*      format.                                                         */
778
/************************************************************************/
779
780
OGRErr OGRGeometryCollection::importFromWkb(const unsigned char *pabyData,
781
                                            size_t nSize,
782
                                            OGRwkbVariant eWkbVariant,
783
                                            size_t &nBytesConsumedOut)
784
785
0
{
786
0
    return importFromWkbInternal(pabyData, nSize, 0, eWkbVariant,
787
0
                                 nBytesConsumedOut);
788
0
}
789
790
/************************************************************************/
791
/*                            exportToWkb()                             */
792
/*                                                                      */
793
/*      Build a well known binary representation of this object.        */
794
/************************************************************************/
795
796
OGRErr
797
OGRGeometryCollection::exportToWkb(unsigned char *pabyData,
798
                                   const OGRwkbExportOptions *psOptions) const
799
800
0
{
801
0
    if (psOptions == nullptr)
802
0
    {
803
0
        static const OGRwkbExportOptions defaultOptions;
804
0
        psOptions = &defaultOptions;
805
0
    }
806
807
0
    OGRwkbExportOptions sOptions(*psOptions);
808
809
0
    if (sOptions.eWkbVariant == wkbVariantOldOgc &&
810
0
        (wkbFlatten(getGeometryType()) == wkbMultiCurve ||
811
0
         wkbFlatten(getGeometryType()) == wkbMultiSurface))
812
0
    {
813
        // Does not make sense for new geometries, so patch it.
814
0
        sOptions.eWkbVariant = wkbVariantIso;
815
0
    }
816
817
    /* -------------------------------------------------------------------- */
818
    /*      Set the byte order.                                             */
819
    /* -------------------------------------------------------------------- */
820
0
    pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER(
821
0
        static_cast<unsigned char>(sOptions.eByteOrder));
822
823
    /* -------------------------------------------------------------------- */
824
    /*      Set the geometry feature type, ensuring that 3D flag is         */
825
    /*      preserved.                                                      */
826
    /* -------------------------------------------------------------------- */
827
0
    GUInt32 nGType = getGeometryType();
828
829
0
    if (sOptions.eWkbVariant == wkbVariantIso)
830
0
        nGType = getIsoGeometryType();
831
0
    else if (sOptions.eWkbVariant == wkbVariantPostGIS1)
832
0
    {
833
0
        const bool bIs3D = wkbHasZ(static_cast<OGRwkbGeometryType>(nGType));
834
0
        nGType = wkbFlatten(nGType);
835
0
        if (nGType == wkbMultiCurve)
836
0
            nGType = POSTGIS15_MULTICURVE;
837
0
        else if (nGType == wkbMultiSurface)
838
0
            nGType = POSTGIS15_MULTISURFACE;
839
0
        if (bIs3D)
840
            // Yes, explicitly set wkb25DBit.
841
0
            nGType =
842
0
                static_cast<OGRwkbGeometryType>(nGType | wkb25DBitInternalUse);
843
0
    }
844
845
0
    if (OGR_SWAP(sOptions.eByteOrder))
846
0
    {
847
0
        nGType = CPL_SWAP32(nGType);
848
0
    }
849
850
0
    memcpy(pabyData + 1, &nGType, 4);
851
852
    /* -------------------------------------------------------------------- */
853
    /*      Copy in the raw data.                                           */
854
    /* -------------------------------------------------------------------- */
855
0
    if (OGR_SWAP(sOptions.eByteOrder))
856
0
    {
857
0
        int nCount = CPL_SWAP32(nGeomCount);
858
0
        memcpy(pabyData + 5, &nCount, 4);
859
0
    }
860
0
    else
861
0
    {
862
0
        memcpy(pabyData + 5, &nGeomCount, 4);
863
0
    }
864
865
0
    size_t nOffset = 9;
866
867
    /* ==================================================================== */
868
    /*      Serialize each of the Geoms.                                    */
869
    /* ==================================================================== */
870
0
    int iGeom = 0;
871
0
    for (auto &&poSubGeom : *this)
872
0
    {
873
0
        poSubGeom->exportToWkb(pabyData + nOffset, &sOptions);
874
        // Should normally not happen if everyone else does its job,
875
        // but has happened sometimes. (#6332)
876
0
        if (poSubGeom->getCoordinateDimension() != getCoordinateDimension())
877
0
        {
878
0
            CPLError(CE_Warning, CPLE_AppDefined,
879
0
                     "Sub-geometry %d has coordinate dimension %d, "
880
0
                     "but container has %d",
881
0
                     iGeom, poSubGeom->getCoordinateDimension(),
882
0
                     getCoordinateDimension());
883
0
        }
884
885
0
        nOffset += poSubGeom->WkbSize();
886
0
        iGeom++;
887
0
    }
888
889
0
    return OGRERR_NONE;
890
0
}
891
892
/************************************************************************/
893
/*                       importFromWktInternal()                        */
894
/************************************************************************/
895
896
OGRErr OGRGeometryCollection::importFromWktInternal(const char **ppszInput,
897
                                                    int nRecLevel)
898
899
208
{
900
    // Arbitrary value, but certainly large enough for reasonable usages.
901
208
    if (nRecLevel == 32)
902
1
    {
903
1
        CPLError(CE_Failure, CPLE_AppDefined,
904
1
                 "Too many recursion levels (%d) while parsing WKT geometry.",
905
1
                 nRecLevel);
906
1
        return OGRERR_CORRUPT_DATA;
907
1
    }
908
909
207
    int bHasZ = FALSE;
910
207
    int bHasM = FALSE;
911
207
    bool bIsEmpty = false;
912
207
    OGRErr eErr = importPreambleFromWkt(ppszInput, &bHasZ, &bHasM, &bIsEmpty);
913
207
    if (eErr != OGRERR_NONE)
914
3
        return eErr;
915
204
    if (bHasZ)
916
0
        flags |= OGR_G_3D;
917
204
    if (bHasM)
918
24
        flags |= OGR_G_MEASURED;
919
204
    if (bIsEmpty)
920
0
        return OGRERR_NONE;
921
922
204
    char szToken[OGR_WKT_TOKEN_MAX] = {};
923
204
    const char *pszInput = *ppszInput;
924
925
    // Skip first '('.
926
204
    pszInput = OGRWktReadToken(pszInput, szToken);
927
928
    /* ==================================================================== */
929
    /*      Read each subgeometry in turn.                                  */
930
    /* ==================================================================== */
931
204
    do
932
434
    {
933
434
        OGRGeometry *poGeom = nullptr;
934
935
        /* --------------------------------------------------------------------
936
         */
937
        /*      Get the first token, which should be the geometry type. */
938
        /* --------------------------------------------------------------------
939
         */
940
434
        OGRWktReadToken(pszInput, szToken);
941
942
        /* --------------------------------------------------------------------
943
         */
944
        /*      Do the import. */
945
        /* --------------------------------------------------------------------
946
         */
947
434
        if (STARTS_WITH_CI(szToken, "GEOMETRYCOLLECTION"))
948
158
        {
949
158
            OGRGeometryCollection *poGC = new OGRGeometryCollection();
950
158
            poGeom = poGC;
951
158
            eErr = poGC->importFromWktInternal(&pszInput, nRecLevel + 1);
952
158
        }
953
276
        else
954
276
            eErr =
955
276
                OGRGeometryFactory::createFromWkt(&pszInput, nullptr, &poGeom);
956
957
434
        if (eErr == OGRERR_NONE)
958
263
        {
959
            // If this has M, but not Z, it is an error if poGeom does
960
            // not have M.
961
263
            if (!Is3D() && IsMeasured() && !poGeom->IsMeasured())
962
2
                eErr = OGRERR_CORRUPT_DATA;
963
261
            else
964
261
                eErr = addGeometryDirectly(poGeom);
965
263
        }
966
434
        if (eErr != OGRERR_NONE)
967
173
        {
968
173
            delete poGeom;
969
173
            return eErr;
970
173
        }
971
972
        /* --------------------------------------------------------------------
973
         */
974
        /*      Read the delimiter following the ring. */
975
        /* --------------------------------------------------------------------
976
         */
977
978
261
        pszInput = OGRWktReadToken(pszInput, szToken);
979
261
    } while (szToken[0] == ',');
980
981
    /* -------------------------------------------------------------------- */
982
    /*      freak if we don't get a closing bracket.                        */
983
    /* -------------------------------------------------------------------- */
984
31
    if (szToken[0] != ')')
985
2
        return OGRERR_CORRUPT_DATA;
986
987
29
    *ppszInput = pszInput;
988
989
29
    return OGRERR_NONE;
990
31
}
991
992
/************************************************************************/
993
/*                           importFromWkt()                            */
994
/************************************************************************/
995
996
OGRErr OGRGeometryCollection::importFromWkt(const char **ppszInput)
997
998
50
{
999
50
    return importFromWktInternal(ppszInput, 0);
1000
50
}
1001
1002
/************************************************************************/
1003
/*                            exportToWkt()                             */
1004
/*                                                                      */
1005
/*      Translate this structure into its well known text format        */
1006
/*      equivalent.                                                     */
1007
/************************************************************************/
1008
1009
std::string OGRGeometryCollection::exportToWkt(const OGRWktOptions &opts,
1010
                                               OGRErr *err) const
1011
0
{
1012
0
    return exportToWktInternal(opts, err);
1013
0
}
1014
1015
//! @cond Doxygen_Suppress
1016
std::string OGRGeometryCollection::exportToWktInternal(
1017
    const OGRWktOptions &opts, OGRErr *err, const std::string &exclude) const
1018
0
{
1019
0
    bool first = true;
1020
0
    const size_t excludeSize = exclude.size();
1021
0
    std::string wkt(getGeometryName());
1022
0
    wkt += wktTypeString(opts.variant);
1023
1024
0
    try
1025
0
    {
1026
0
        for (const auto &poSubGeom : *this)
1027
0
        {
1028
0
            OGRErr subgeomErr = OGRERR_NONE;
1029
0
            std::string tempWkt = poSubGeom->exportToWkt(opts, &subgeomErr);
1030
0
            if (subgeomErr != OGRERR_NONE)
1031
0
            {
1032
0
                if (err)
1033
0
                    *err = subgeomErr;
1034
0
                return std::string();
1035
0
            }
1036
1037
            // For some strange reason we exclude the typename leader when using
1038
            // some geometries as part of a collection.
1039
0
            if (excludeSize && (tempWkt.compare(0, excludeSize, exclude) == 0))
1040
0
            {
1041
0
                auto pos = tempWkt.find('(');
1042
                // We won't have an opening paren if the geom is empty.
1043
0
                if (pos == std::string::npos)
1044
0
                    continue;
1045
0
                tempWkt = tempWkt.substr(pos);
1046
0
            }
1047
1048
            // Also strange, we allow the inclusion of ISO-only geometries (see
1049
            // OGRPolyhedralSurface) in a non-iso geometry collection.  In order
1050
            // to facilitate this, we need to rip the ISO bit from the string.
1051
0
            if (opts.variant != wkbVariantIso)
1052
0
            {
1053
0
                std::string::size_type pos;
1054
0
                if ((pos = tempWkt.find(" Z ")) != std::string::npos)
1055
0
                    tempWkt.erase(pos + 1, 2);
1056
0
                else if ((pos = tempWkt.find(" M ")) != std::string::npos)
1057
0
                    tempWkt.erase(pos + 1, 2);
1058
0
                else if ((pos = tempWkt.find(" ZM ")) != std::string::npos)
1059
0
                    tempWkt.erase(pos + 1, 3);
1060
0
            }
1061
1062
0
            if (first)
1063
0
                wkt += '(';
1064
0
            else
1065
0
                wkt += ',';
1066
0
            first = false;
1067
0
            wkt += tempWkt;
1068
0
        }
1069
1070
0
        if (err)
1071
0
            *err = OGRERR_NONE;
1072
0
        if (first)
1073
0
            wkt += "EMPTY";
1074
0
        else
1075
0
            wkt += ')';
1076
0
        return wkt;
1077
0
    }
1078
0
    catch (const std::bad_alloc &e)
1079
0
    {
1080
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1081
0
        if (err)
1082
0
            *err = OGRERR_FAILURE;
1083
0
        return std::string();
1084
0
    }
1085
0
}
1086
1087
//! @endcond
1088
1089
/************************************************************************/
1090
/*                            getEnvelope()                             */
1091
/************************************************************************/
1092
1093
void OGRGeometryCollection::getEnvelope(OGREnvelope *psEnvelope) const
1094
1095
0
{
1096
0
    OGREnvelope3D oEnv3D;
1097
0
    getEnvelope(&oEnv3D);
1098
0
    psEnvelope->MinX = oEnv3D.MinX;
1099
0
    psEnvelope->MinY = oEnv3D.MinY;
1100
0
    psEnvelope->MaxX = oEnv3D.MaxX;
1101
0
    psEnvelope->MaxY = oEnv3D.MaxY;
1102
0
}
1103
1104
/************************************************************************/
1105
/*                            getEnvelope()                             */
1106
/************************************************************************/
1107
1108
void OGRGeometryCollection::getEnvelope(OGREnvelope3D *psEnvelope) const
1109
1110
0
{
1111
0
    OGREnvelope3D oGeomEnv;
1112
0
    bool bExtentSet = false;
1113
1114
0
    *psEnvelope = OGREnvelope3D();
1115
0
    for (const auto &poSubGeom : *this)
1116
0
    {
1117
0
        if (!poSubGeom->IsEmpty())
1118
0
        {
1119
0
            bExtentSet = true;
1120
0
            poSubGeom->getEnvelope(&oGeomEnv);
1121
0
            psEnvelope->Merge(oGeomEnv);
1122
0
        }
1123
0
    }
1124
1125
0
    if (!bExtentSet)
1126
0
    {
1127
        // To be backward compatible when called on empty geom
1128
0
        psEnvelope->MinX = 0.0;
1129
0
        psEnvelope->MinY = 0.0;
1130
0
        psEnvelope->MinZ = 0.0;
1131
0
        psEnvelope->MaxX = 0.0;
1132
0
        psEnvelope->MaxY = 0.0;
1133
0
        psEnvelope->MaxZ = 0.0;
1134
0
    }
1135
0
}
1136
1137
/************************************************************************/
1138
/*                               Equals()                               */
1139
/************************************************************************/
1140
1141
OGRBoolean OGRGeometryCollection::Equals(const OGRGeometry *poOther) const
1142
1143
0
{
1144
0
    if (poOther == this)
1145
0
        return TRUE;
1146
1147
0
    if (poOther->getGeometryType() != getGeometryType())
1148
0
        return FALSE;
1149
1150
0
    if (IsEmpty() && poOther->IsEmpty())
1151
0
        return TRUE;
1152
1153
0
    auto poOGC = poOther->toGeometryCollection();
1154
0
    if (getNumGeometries() != poOGC->getNumGeometries())
1155
0
        return FALSE;
1156
1157
    // TODO(schwehr): Should test the SRS.
1158
1159
0
    for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
1160
0
    {
1161
0
        if (!getGeometryRef(iGeom)->Equals(poOGC->getGeometryRef(iGeom)))
1162
0
            return FALSE;
1163
0
    }
1164
1165
0
    return TRUE;
1166
0
}
1167
1168
/************************************************************************/
1169
/*                             transform()                              */
1170
/************************************************************************/
1171
1172
OGRErr OGRGeometryCollection::transform(OGRCoordinateTransformation *poCT)
1173
1174
0
{
1175
0
    int iGeom = 0;
1176
0
    for (auto &poSubGeom : *this)
1177
0
    {
1178
0
        const OGRErr eErr = poSubGeom->transform(poCT);
1179
0
        if (eErr != OGRERR_NONE)
1180
0
        {
1181
0
            if (iGeom != 0)
1182
0
            {
1183
0
                CPLDebug("OGR",
1184
0
                         "OGRGeometryCollection::transform() failed for a "
1185
0
                         "geometry other than the first, meaning some "
1186
0
                         "geometries are transformed and some are not.");
1187
1188
0
                return OGRERR_FAILURE;
1189
0
            }
1190
1191
0
            return eErr;
1192
0
        }
1193
0
        iGeom++;
1194
0
    }
1195
1196
0
    assignSpatialReference(poCT->GetTargetCS());
1197
1198
0
    return OGRERR_NONE;
1199
0
}
1200
1201
/************************************************************************/
1202
/*                             closeRings()                             */
1203
/************************************************************************/
1204
1205
void OGRGeometryCollection::closeRings()
1206
1207
0
{
1208
0
    for (auto &poSubGeom : *this)
1209
0
    {
1210
0
        if (OGR_GT_IsSubClassOf(wkbFlatten(poSubGeom->getGeometryType()),
1211
0
                                wkbCurvePolygon))
1212
0
        {
1213
0
            OGRCurvePolygon *poPoly = poSubGeom->toCurvePolygon();
1214
0
            poPoly->closeRings();
1215
0
        }
1216
0
    }
1217
0
}
1218
1219
/************************************************************************/
1220
/*                       setCoordinateDimension()                       */
1221
/************************************************************************/
1222
1223
bool OGRGeometryCollection::setCoordinateDimension(int nNewDimension)
1224
1225
0
{
1226
0
    for (auto &poSubGeom : *this)
1227
0
    {
1228
0
        if (!poSubGeom->setCoordinateDimension(nNewDimension))
1229
0
            return false;
1230
0
    }
1231
1232
0
    return OGRGeometry::setCoordinateDimension(nNewDimension);
1233
0
}
1234
1235
bool OGRGeometryCollection::set3D(OGRBoolean bIs3D)
1236
644
{
1237
644
    for (auto &poSubGeom : *this)
1238
123
    {
1239
123
        if (!poSubGeom->set3D(bIs3D))
1240
0
            return false;
1241
123
    }
1242
1243
644
    return OGRGeometry::set3D(bIs3D);
1244
644
}
1245
1246
bool OGRGeometryCollection::setMeasured(OGRBoolean bIsMeasured)
1247
545
{
1248
545
    for (auto &poSubGeom : *this)
1249
410
    {
1250
410
        if (!poSubGeom->setMeasured(bIsMeasured))
1251
0
            return false;
1252
410
    }
1253
1254
545
    return OGRGeometry::setMeasured(bIsMeasured);
1255
545
}
1256
1257
/************************************************************************/
1258
/*                             get_Length()                             */
1259
/************************************************************************/
1260
1261
/**
1262
 * \brief Compute the length of a multicurve.
1263
 *
1264
 * The length is computed as the sum of the length of all members
1265
 * in this collection.
1266
 *
1267
 * @note No warning will be issued if a member of the collection does not
1268
 *       support the get_Length method.
1269
 *
1270
 * @return computed length.
1271
 */
1272
1273
double OGRGeometryCollection::get_Length() const
1274
0
{
1275
0
    double dfLength = 0.0;
1276
0
    for (const auto &poSubGeom : *this)
1277
0
    {
1278
0
        const OGRwkbGeometryType eType =
1279
0
            wkbFlatten(poSubGeom->getGeometryType());
1280
0
        if (OGR_GT_IsCurve(eType))
1281
0
        {
1282
0
            const OGRCurve *poCurve = poSubGeom->toCurve();
1283
0
            dfLength += poCurve->get_Length();
1284
0
        }
1285
0
        else if (OGR_GT_IsSurface(eType))
1286
0
        {
1287
0
            const OGRSurface *poSurface = poSubGeom->toSurface();
1288
0
            dfLength += poSurface->get_Length();
1289
0
        }
1290
0
        else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1291
0
        {
1292
0
            const OGRGeometryCollection *poColl =
1293
0
                poSubGeom->toGeometryCollection();
1294
0
            dfLength += poColl->get_Length();
1295
0
        }
1296
0
    }
1297
1298
0
    return dfLength;
1299
0
}
1300
1301
/************************************************************************/
1302
/*                              get_Area()                              */
1303
/************************************************************************/
1304
1305
/**
1306
 * \brief Compute area of geometry collection.
1307
 *
1308
 * The area is computed as the sum of the areas of all members
1309
 * in this collection.
1310
 *
1311
 * @note No warning will be issued if a member of the collection does not
1312
 *       support the get_Area method.
1313
 *
1314
 * @return computed area.
1315
 */
1316
1317
double OGRGeometryCollection::get_Area() const
1318
0
{
1319
0
    double dfArea = 0.0;
1320
0
    for (const auto &poSubGeom : *this)
1321
0
    {
1322
0
        OGRwkbGeometryType eType = wkbFlatten(poSubGeom->getGeometryType());
1323
0
        if (OGR_GT_IsSurface(eType))
1324
0
        {
1325
0
            const OGRSurface *poSurface = poSubGeom->toSurface();
1326
0
            dfArea += poSurface->get_Area();
1327
0
        }
1328
0
        else if (OGR_GT_IsCurve(eType))
1329
0
        {
1330
0
            const OGRCurve *poCurve = poSubGeom->toCurve();
1331
0
            dfArea += poCurve->get_Area();
1332
0
        }
1333
0
        else if (OGR_GT_IsSubClassOf(eType, wkbMultiSurface) ||
1334
0
                 eType == wkbGeometryCollection)
1335
0
        {
1336
0
            dfArea += poSubGeom->toGeometryCollection()->get_Area();
1337
0
        }
1338
0
    }
1339
1340
0
    return dfArea;
1341
0
}
1342
1343
/************************************************************************/
1344
/*                          get_GeodesicArea()                          */
1345
/************************************************************************/
1346
1347
/**
1348
 * \brief Compute area of geometry collection, considered as a surface on
1349
 * the underlying ellipsoid of the SRS attached to the geometry.
1350
 *
1351
 * The returned area will always be in square meters, and assumes that
1352
 * polygon edges describe geodesic lines on the ellipsoid.
1353
 *
1354
 * <a href="https://geographiclib.sourceforge.io/html/python/geodesics.html">Geodesics</a>
1355
 * follow the shortest route on the surface of the ellipsoid.
1356
 *
1357
 * If the geometry' SRS is not a geographic one, geometries are reprojected to
1358
 * the underlying geographic SRS of the geometry' SRS.
1359
 * OGRSpatialReference::GetDataAxisToSRSAxisMapping() is honored.
1360
 *
1361
 * The area is computed as the sum of the areas of all members
1362
 * in this collection.
1363
 *
1364
 * @note No warning will be issued if a member of the collection does not
1365
 *       support the get_GeodesicArea method.
1366
 *
1367
 * @param poSRSOverride If not null, overrides OGRGeometry::getSpatialReference()
1368
 * @return the area of the geometry in square meters, or a negative value in case
1369
 * of error.
1370
 *
1371
 * @see get_Area() for an alternative method returning areas computed in
1372
 * 2D Cartesian space.
1373
 *
1374
 * @since GDAL 3.9
1375
 */
1376
double OGRGeometryCollection::get_GeodesicArea(
1377
    const OGRSpatialReference *poSRSOverride) const
1378
0
{
1379
0
    double dfArea = 0.0;
1380
0
    for (const auto &poSubGeom : *this)
1381
0
    {
1382
0
        OGRwkbGeometryType eType = wkbFlatten(poSubGeom->getGeometryType());
1383
0
        if (OGR_GT_IsSurface(eType))
1384
0
        {
1385
0
            const OGRSurface *poSurface = poSubGeom->toSurface();
1386
0
            const double dfLocalArea =
1387
0
                poSurface->get_GeodesicArea(poSRSOverride);
1388
0
            if (dfLocalArea < 0)
1389
0
                return dfLocalArea;
1390
0
            dfArea += dfLocalArea;
1391
0
        }
1392
0
        else if (OGR_GT_IsCurve(eType))
1393
0
        {
1394
0
            const OGRCurve *poCurve = poSubGeom->toCurve();
1395
0
            const double dfLocalArea = poCurve->get_GeodesicArea(poSRSOverride);
1396
0
            if (dfLocalArea < 0)
1397
0
                return dfLocalArea;
1398
0
            dfArea += dfLocalArea;
1399
0
        }
1400
0
        else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1401
0
        {
1402
0
            const double dfLocalArea =
1403
0
                poSubGeom->toGeometryCollection()->get_GeodesicArea(
1404
0
                    poSRSOverride);
1405
0
            if (dfLocalArea < 0)
1406
0
                return dfLocalArea;
1407
0
            dfArea += dfLocalArea;
1408
0
        }
1409
0
    }
1410
1411
0
    return dfArea;
1412
0
}
1413
1414
/************************************************************************/
1415
/*                         get_GeodesicLength()                         */
1416
/************************************************************************/
1417
1418
/**
1419
 * \brief Get the length of the collection,where curve edges are geodesic lines
1420
 * on the underlying ellipsoid of the SRS attached to the geometry.
1421
 *
1422
 * The returned length will always be in meters.
1423
 *
1424
 * <a href="https://geographiclib.sourceforge.io/html/python/geodesics.html">Geodesics</a>
1425
 * follow the shortest route on the surface of the ellipsoid.
1426
 *
1427
 * If the geometry' SRS is not a geographic one, geometries are reprojected to
1428
 * the underlying geographic SRS of the geometry' SRS.
1429
 * OGRSpatialReference::GetDataAxisToSRSAxisMapping() is honored.
1430
 *
1431
 * Note that geometries with circular arcs will be linearized in their original
1432
 * coordinate space first, so the resulting geodesic length will be an
1433
 * approximation.
1434
 *
1435
 * The length is computed as the sum of the lengths of all members
1436
 * in this collection.
1437
 *
1438
 * @note No warning will be issued if a member of the collection does not
1439
 *       support the get_GeodesicLength method.
1440
 *
1441
 * @param poSRSOverride If not null, overrides OGRGeometry::getSpatialReference()
1442
 * @return the length of the geometry in meters, or a negative value in case
1443
 * of error.
1444
 *
1445
 * @see get_Length() for an alternative method returning areas computed in
1446
 * 2D Cartesian space.
1447
 *
1448
 * @since GDAL 3.10
1449
 */
1450
double OGRGeometryCollection::get_GeodesicLength(
1451
    const OGRSpatialReference *poSRSOverride) const
1452
0
{
1453
0
    double dfLength = 0.0;
1454
0
    for (const auto &poSubGeom : *this)
1455
0
    {
1456
0
        const OGRwkbGeometryType eType =
1457
0
            wkbFlatten(poSubGeom->getGeometryType());
1458
0
        if (OGR_GT_IsSurface(eType))
1459
0
        {
1460
0
            const OGRSurface *poSurface = poSubGeom->toSurface();
1461
0
            const double dfLocalLength =
1462
0
                poSurface->get_GeodesicLength(poSRSOverride);
1463
0
            if (dfLocalLength < 0)
1464
0
                return dfLocalLength;
1465
0
            dfLength += dfLocalLength;
1466
0
        }
1467
0
        else if (OGR_GT_IsCurve(eType))
1468
0
        {
1469
0
            const OGRCurve *poCurve = poSubGeom->toCurve();
1470
0
            const double dfLocalLength =
1471
0
                poCurve->get_GeodesicLength(poSRSOverride);
1472
0
            if (dfLocalLength < 0)
1473
0
                return dfLocalLength;
1474
0
            dfLength += dfLocalLength;
1475
0
        }
1476
0
        else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1477
0
        {
1478
0
            const double dfLocalLength =
1479
0
                poSubGeom->toGeometryCollection()->get_GeodesicLength(
1480
0
                    poSRSOverride);
1481
0
            if (dfLocalLength < 0)
1482
0
                return dfLocalLength;
1483
0
            dfLength += dfLocalLength;
1484
0
        }
1485
0
    }
1486
1487
0
    return dfLength;
1488
0
}
1489
1490
/************************************************************************/
1491
/*                              IsEmpty()                               */
1492
/************************************************************************/
1493
1494
OGRBoolean OGRGeometryCollection::IsEmpty() const
1495
0
{
1496
0
    for (const auto &poSubGeom : *this)
1497
0
    {
1498
0
        if (poSubGeom->IsEmpty() == FALSE)
1499
0
            return FALSE;
1500
0
    }
1501
0
    return TRUE;
1502
0
}
1503
1504
/************************************************************************/
1505
/*                       assignSpatialReference()                       */
1506
/************************************************************************/
1507
1508
void OGRGeometryCollection::assignSpatialReference(
1509
    const OGRSpatialReference *poSR)
1510
1.55k
{
1511
1.55k
    OGRGeometry::assignSpatialReference(poSR);
1512
1.55k
    for (auto &poSubGeom : *this)
1513
5.36k
    {
1514
5.36k
        poSubGeom->assignSpatialReference(poSR);
1515
5.36k
    }
1516
1.55k
}
1517
1518
/************************************************************************/
1519
/*                 OGRGeometryCollection::segmentize()                  */
1520
/************************************************************************/
1521
1522
bool OGRGeometryCollection::segmentize(double dfMaxLength)
1523
0
{
1524
0
    for (auto &poSubGeom : *this)
1525
0
    {
1526
0
        if (!poSubGeom->segmentize(dfMaxLength))
1527
0
            return false;
1528
0
    }
1529
0
    return true;
1530
0
}
1531
1532
/************************************************************************/
1533
/*                               swapXY()                               */
1534
/************************************************************************/
1535
1536
void OGRGeometryCollection::swapXY()
1537
0
{
1538
0
    for (auto &poSubGeom : *this)
1539
0
    {
1540
0
        poSubGeom->swapXY();
1541
0
    }
1542
0
}
1543
1544
/************************************************************************/
1545
/*                        isCompatibleSubType()                         */
1546
/************************************************************************/
1547
1548
/** Returns whether a geometry of the specified geometry type can be a
1549
 * member of this collection.
1550
 *
1551
 * @param eSubType type of the potential member
1552
 * @return TRUE or FALSE
1553
 */
1554
1555
OGRBoolean OGRGeometryCollection::isCompatibleSubType(
1556
    CPL_UNUSED OGRwkbGeometryType eSubType) const
1557
261
{
1558
    // Accept all geometries as sub-geometries.
1559
261
    return TRUE;
1560
261
}
1561
1562
/************************************************************************/
1563
/*                          hasCurveGeometry()                          */
1564
/************************************************************************/
1565
1566
OGRBoolean OGRGeometryCollection::hasCurveGeometry(int bLookForNonLinear) const
1567
29
{
1568
29
    for (const auto &poSubGeom : *this)
1569
54
    {
1570
54
        if (poSubGeom->hasCurveGeometry(bLookForNonLinear))
1571
0
            return TRUE;
1572
54
    }
1573
29
    return FALSE;
1574
29
}
1575
1576
/************************************************************************/
1577
/*                         getLinearGeometry()                          */
1578
/************************************************************************/
1579
1580
OGRGeometry *
1581
OGRGeometryCollection::getLinearGeometry(double dfMaxAngleStepSizeDegrees,
1582
                                         const char *const *papszOptions) const
1583
0
{
1584
0
    auto poGC = std::unique_ptr<OGRGeometryCollection>(
1585
0
        OGRGeometryFactory::createGeometry(OGR_GT_GetLinear(getGeometryType()))
1586
0
            ->toGeometryCollection());
1587
0
    if (!poGC)
1588
0
        return nullptr;
1589
0
    poGC->assignSpatialReference(getSpatialReference());
1590
0
    for (const auto &poSubGeom : *this)
1591
0
    {
1592
0
        OGRGeometry *poSubGeomNew = poSubGeom->getLinearGeometry(
1593
0
            dfMaxAngleStepSizeDegrees, papszOptions);
1594
0
        if (poGC->addGeometryDirectly(poSubGeomNew) != OGRERR_NONE)
1595
0
            return nullptr;
1596
0
    }
1597
0
    return poGC.release();
1598
0
}
1599
1600
/************************************************************************/
1601
/*                          getCurveGeometry()                          */
1602
/************************************************************************/
1603
1604
OGRGeometry *
1605
OGRGeometryCollection::getCurveGeometry(const char *const *papszOptions) const
1606
0
{
1607
0
    auto poGC = std::unique_ptr<OGRGeometryCollection>(
1608
0
        OGRGeometryFactory::createGeometry(OGR_GT_GetCurve(getGeometryType()))
1609
0
            ->toGeometryCollection());
1610
0
    if (!poGC)
1611
0
        return nullptr;
1612
0
    poGC->assignSpatialReference(getSpatialReference());
1613
0
    bool bHasCurveGeometry = false;
1614
0
    for (const auto &poSubGeom : *this)
1615
0
    {
1616
0
        OGRGeometry *poSubGeomNew = poSubGeom->getCurveGeometry(papszOptions);
1617
0
        if (poSubGeomNew->hasCurveGeometry())
1618
0
            bHasCurveGeometry = true;
1619
0
        if (poGC->addGeometryDirectly(poSubGeomNew) != OGRERR_NONE)
1620
0
            return nullptr;
1621
0
    }
1622
0
    if (!bHasCurveGeometry)
1623
0
    {
1624
0
        return clone();
1625
0
    }
1626
0
    return poGC.release();
1627
0
}
1628
1629
/************************************************************************/
1630
/*                     TransferMembersAndDestroy()                      */
1631
/************************************************************************/
1632
1633
//! @cond Doxygen_Suppress
1634
OGRGeometryCollection *
1635
OGRGeometryCollection::TransferMembersAndDestroy(OGRGeometryCollection *poSrc,
1636
                                                 OGRGeometryCollection *poDst)
1637
0
{
1638
0
    poDst->assignSpatialReference(poSrc->getSpatialReference());
1639
0
    poDst->set3D(poSrc->Is3D());
1640
0
    poDst->setMeasured(poSrc->IsMeasured());
1641
0
    poDst->nGeomCount = poSrc->nGeomCount;
1642
0
    poDst->papoGeoms = poSrc->papoGeoms;
1643
0
    poSrc->nGeomCount = 0;
1644
0
    poSrc->papoGeoms = nullptr;
1645
0
    delete poSrc;
1646
0
    return poDst;
1647
0
}
1648
1649
//! @endcond
1650
1651
/************************************************************************/
1652
/*                      CastToGeometryCollection()                      */
1653
/************************************************************************/
1654
1655
/**
1656
 * \brief Cast to geometry collection.
1657
 *
1658
 * This methods cast a derived class of geometry collection to a plain
1659
 * geometry collection.
1660
 *
1661
 * The passed in geometry is consumed and a new one returned (or NULL in case
1662
 * of failure).
1663
 *
1664
 * @param poSrc the input geometry - ownership is passed to the method.
1665
 * @return new geometry.
1666
 */
1667
1668
OGRGeometryCollection *
1669
OGRGeometryCollection::CastToGeometryCollection(OGRGeometryCollection *poSrc)
1670
0
{
1671
0
    if (wkbFlatten(poSrc->getGeometryType()) == wkbGeometryCollection)
1672
0
        return poSrc;
1673
0
    return TransferMembersAndDestroy(poSrc, new OGRGeometryCollection());
1674
0
}