Line data Source code
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 34648 : OGRGeometryCollection::OGRGeometryCollection(const OGRGeometryCollection &other)
40 34648 : : OGRGeometry(other)
41 : {
42 : // Do not use addGeometry() as it is virtual.
43 34648 : papoGeoms = static_cast<OGRGeometry **>(
44 34648 : VSI_CALLOC_VERBOSE(sizeof(OGRGeometry *), other.nGeomCount));
45 34648 : if (papoGeoms)
46 : {
47 34648 : nGeomCount = other.nGeomCount;
48 137218 : for (int i = 0; i < other.nGeomCount; i++)
49 : {
50 102570 : papoGeoms[i] = other.papoGeoms[i]->clone();
51 : }
52 : }
53 34648 : }
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 8 : OGRGeometryCollection::OGRGeometryCollection(OGRGeometryCollection &&other)
67 16 : : OGRGeometry(std::move(other)), nGeomCount(other.nGeomCount),
68 8 : papoGeoms(other.papoGeoms)
69 : {
70 8 : other.nGeomCount = 0;
71 8 : other.papoGeoms = nullptr;
72 8 : }
73 :
74 : // cppcheck-suppress-end accessMoved
75 :
76 : /************************************************************************/
77 : /* ~OGRGeometryCollection() */
78 : /************************************************************************/
79 :
80 159571 : OGRGeometryCollection::~OGRGeometryCollection()
81 :
82 : {
83 153822 : OGRGeometryCollection::empty();
84 159574 : }
85 :
86 : /************************************************************************/
87 : /* operator=( const OGRGeometryCollection&) */
88 : /************************************************************************/
89 :
90 : /**
91 : * \brief Assignment operator.
92 : */
93 :
94 : OGRGeometryCollection &
95 30 : OGRGeometryCollection::operator=(const OGRGeometryCollection &other)
96 : {
97 30 : if (this != &other)
98 : {
99 29 : OGRGeometry::operator=(other);
100 :
101 51 : for (const auto *poOtherSubGeom : other)
102 : {
103 23 : if (!isCompatibleSubType(poOtherSubGeom->getGeometryType()))
104 : {
105 1 : CPLError(CE_Failure, CPLE_AppDefined,
106 : "Illegal use of OGRGeometryCollection::operator=(): "
107 : "trying to assign an incompatible sub-geometry");
108 1 : return *this;
109 : }
110 : }
111 :
112 28 : papoGeoms = static_cast<OGRGeometry **>(
113 28 : VSI_CALLOC_VERBOSE(sizeof(OGRGeometry *), other.nGeomCount));
114 28 : if (papoGeoms)
115 : {
116 28 : nGeomCount = other.nGeomCount;
117 50 : for (int i = 0; i < other.nGeomCount; i++)
118 : {
119 22 : papoGeoms[i] = other.papoGeoms[i]->clone();
120 : }
121 : }
122 : }
123 29 : return *this;
124 : }
125 :
126 : /************************************************************************/
127 : /* operator=( OGRGeometryCollection&&) */
128 : /************************************************************************/
129 :
130 : /**
131 : * \brief Move assignment operator.
132 : *
133 : * @since GDAL 3.11
134 : */
135 :
136 : OGRGeometryCollection &
137 16 : OGRGeometryCollection::operator=(OGRGeometryCollection &&other)
138 : {
139 16 : if (this != &other)
140 : {
141 16 : empty();
142 :
143 16 : OGRGeometry::operator=(std::move(other));
144 16 : std::swap(nGeomCount, other.nGeomCount);
145 16 : std::swap(papoGeoms, other.papoGeoms);
146 : }
147 16 : return *this;
148 : }
149 :
150 : /************************************************************************/
151 : /* empty() */
152 : /************************************************************************/
153 :
154 165756 : void OGRGeometryCollection::empty()
155 :
156 : {
157 165756 : if (papoGeoms != nullptr)
158 : {
159 1799470 : for (auto &poSubGeom : *this)
160 : {
161 1652830 : delete poSubGeom;
162 : }
163 146631 : CPLFree(papoGeoms);
164 : }
165 :
166 165756 : nGeomCount = 0;
167 165756 : papoGeoms = nullptr;
168 165756 : }
169 :
170 : /************************************************************************/
171 : /* clone() */
172 : /************************************************************************/
173 :
174 545 : OGRGeometryCollection *OGRGeometryCollection::clone() const
175 :
176 : {
177 545 : auto ret = new (std::nothrow) OGRGeometryCollection(*this);
178 545 : if (ret)
179 : {
180 545 : if (ret->WkbSize() != WkbSize())
181 : {
182 0 : delete ret;
183 0 : ret = nullptr;
184 : }
185 : }
186 545 : return ret;
187 : }
188 :
189 : /************************************************************************/
190 : /* getGeometryType() */
191 : /************************************************************************/
192 :
193 10071 : OGRwkbGeometryType OGRGeometryCollection::getGeometryType() const
194 :
195 : {
196 10071 : if ((flags & OGR_G_3D) && (flags & OGR_G_MEASURED))
197 2967 : return wkbGeometryCollectionZM;
198 7104 : else if (flags & OGR_G_MEASURED)
199 247 : return wkbGeometryCollectionM;
200 6857 : else if (flags & OGR_G_3D)
201 3844 : return wkbGeometryCollection25D;
202 : else
203 3013 : return wkbGeometryCollection;
204 : }
205 :
206 : /************************************************************************/
207 : /* getDimension() */
208 : /************************************************************************/
209 :
210 6 : int OGRGeometryCollection::getDimension() const
211 :
212 : {
213 6 : 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 12 : for (const auto &poSubGeom : *this)
217 : {
218 9 : int nSubGeomDimension = poSubGeom->getDimension();
219 9 : if (nSubGeomDimension > nDimension)
220 : {
221 5 : nDimension = nSubGeomDimension;
222 5 : if (nDimension == 2)
223 3 : break;
224 : }
225 : }
226 6 : return nDimension;
227 : }
228 :
229 : /************************************************************************/
230 : /* flattenTo2D() */
231 : /************************************************************************/
232 :
233 101 : void OGRGeometryCollection::flattenTo2D()
234 :
235 : {
236 301 : for (auto &poSubGeom : *this)
237 : {
238 200 : poSubGeom->flattenTo2D();
239 : }
240 :
241 101 : flags &= ~OGR_G_3D;
242 101 : flags &= ~OGR_G_MEASURED;
243 101 : }
244 :
245 : /************************************************************************/
246 : /* getGeometryName() */
247 : /************************************************************************/
248 :
249 1048 : const char *OGRGeometryCollection::getGeometryName() const
250 :
251 : {
252 1048 : return "GEOMETRYCOLLECTION";
253 : }
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 122735 : int OGRGeometryCollection::getNumGeometries() const
269 :
270 : {
271 122735 : return nGeomCount;
272 : }
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 34424 : OGRGeometry *OGRGeometryCollection::getGeometryRef(int i)
294 :
295 : {
296 34424 : if (i < 0 || i >= nGeomCount)
297 0 : return nullptr;
298 :
299 34424 : return papoGeoms[i];
300 : }
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 175718 : const OGRGeometry *OGRGeometryCollection::getGeometryRef(int i) const
318 :
319 : {
320 175718 : if (i < 0 || i >= nGeomCount)
321 0 : return nullptr;
322 :
323 175718 : return papoGeoms[i];
324 : }
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 1892 : OGRErr OGRGeometryCollection::addGeometry(const OGRGeometry *poNewGeom)
352 :
353 : {
354 1892 : OGRGeometry *poClone = poNewGeom->clone();
355 1892 : if (poClone == nullptr)
356 0 : return OGRERR_FAILURE;
357 :
358 1892 : const OGRErr eErr = addGeometryDirectly(poClone);
359 1892 : if (eErr != OGRERR_NONE)
360 2 : delete poClone;
361 :
362 1892 : return eErr;
363 : }
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 186689 : OGRErr OGRGeometryCollection::addGeometryDirectly(OGRGeometry *poNewGeom)
393 :
394 : {
395 186689 : if (!isCompatibleSubType(poNewGeom->getGeometryType()))
396 6 : 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 186678 : if (nGeomCount == std::numeric_limits<int>::max())
407 : {
408 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too many subgeometries");
409 0 : return OGRERR_FAILURE;
410 : }
411 : #endif
412 :
413 186680 : HomogenizeDimensionalityWith(poNewGeom);
414 :
415 : OGRGeometry **papoNewGeoms =
416 186678 : static_cast<OGRGeometry **>(VSI_REALLOC_VERBOSE(
417 : papoGeoms, sizeof(OGRGeometry *) * (nGeomCount + 1)));
418 186680 : if (papoNewGeoms == nullptr)
419 0 : return OGRERR_FAILURE;
420 :
421 186680 : papoGeoms = papoNewGeoms;
422 186680 : papoGeoms[nGeomCount] = poNewGeom;
423 :
424 186680 : nGeomCount++;
425 :
426 186680 : return OGRERR_NONE;
427 : }
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 2354 : OGRErr OGRGeometryCollection::addGeometry(std::unique_ptr<OGRGeometry> geom)
448 : {
449 2354 : OGRGeometry *poGeom = geom.release();
450 2354 : OGRErr eErr = addGeometryDirectly(poGeom);
451 2354 : if (eErr != OGRERR_NONE)
452 2 : delete poGeom;
453 2354 : return eErr;
454 : }
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 5405 : OGRErr OGRGeometryCollection::removeGeometry(int iGeom, int bDelete)
483 :
484 : {
485 5405 : if (iGeom < -1 || iGeom >= nGeomCount)
486 4 : return OGRERR_FAILURE;
487 :
488 : // Special case.
489 5401 : if (iGeom == -1)
490 : {
491 7 : while (nGeomCount > 0)
492 5 : removeGeometry(nGeomCount - 1, bDelete);
493 2 : return OGRERR_NONE;
494 : }
495 :
496 5399 : if (bDelete)
497 103 : delete papoGeoms[iGeom];
498 :
499 5399 : memmove(papoGeoms + iGeom, papoGeoms + iGeom + 1,
500 5399 : sizeof(OGRGeometry *) * (nGeomCount - iGeom - 1));
501 :
502 5399 : nGeomCount--;
503 :
504 5399 : return OGRERR_NONE;
505 : }
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 13 : std::unique_ptr<OGRGeometry> OGRGeometryCollection::stealGeometry(int iGeom)
526 : {
527 13 : if (iGeom < 0 || iGeom >= nGeomCount)
528 0 : return nullptr;
529 :
530 26 : auto poSubGeom = std::unique_ptr<OGRGeometry>(papoGeoms[iGeom]);
531 13 : papoGeoms[iGeom] = nullptr;
532 13 : removeGeometry(iGeom);
533 13 : return poSubGeom;
534 : }
535 :
536 : /************************************************************************/
537 : /* hasEmptyParts() */
538 : /************************************************************************/
539 :
540 9 : bool OGRGeometryCollection::hasEmptyParts() const
541 : {
542 17 : for (const auto &poSubGeom : *this)
543 : {
544 11 : if (poSubGeom->IsEmpty() || poSubGeom->hasEmptyParts())
545 3 : return true;
546 : }
547 6 : return false;
548 : }
549 :
550 : /************************************************************************/
551 : /* removeEmptyParts() */
552 : /************************************************************************/
553 :
554 5 : void OGRGeometryCollection::removeEmptyParts()
555 : {
556 13 : for (int i = nGeomCount - 1; i >= 0; --i)
557 : {
558 8 : papoGeoms[i]->removeEmptyParts();
559 8 : if (papoGeoms[i]->IsEmpty())
560 3 : removeGeometry(i, true);
561 : }
562 5 : }
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 79688 : size_t OGRGeometryCollection::WkbSize() const
572 :
573 : {
574 79688 : size_t nSize = 9;
575 :
576 349251 : for (const auto &poGeom : *this)
577 : {
578 269569 : nSize += poGeom->WkbSize();
579 : }
580 :
581 79680 : return nSize;
582 : }
583 :
584 : /************************************************************************/
585 : /* importFromWkbInternal() */
586 : /************************************************************************/
587 :
588 : //! @cond Doxygen_Suppress
589 8784 : OGRErr OGRGeometryCollection::importFromWkbInternal(
590 : const unsigned char *pabyData, size_t nSize, int nRecLevel,
591 : OGRwkbVariant eWkbVariant, size_t &nBytesConsumedOut)
592 :
593 : {
594 8784 : nBytesConsumedOut = 0;
595 : // Arbitrary value, but certainly large enough for reasonable use cases.
596 8784 : if (nRecLevel == 32)
597 : {
598 1 : CPLError(CE_Failure, CPLE_AppDefined,
599 : "Too many recursion levels (%d) while parsing WKB geometry.",
600 : nRecLevel);
601 1 : return OGRERR_CORRUPT_DATA;
602 : }
603 :
604 8783 : OGRwkbByteOrder eByteOrder = wkbXDR;
605 8783 : size_t nDataOffset = 0;
606 8783 : int nGeomCountNew = 0;
607 8783 : OGRErr eErr = importPreambleOfCollectionFromWkb(pabyData, nSize,
608 : nDataOffset, eByteOrder, 9,
609 : nGeomCountNew, eWkbVariant);
610 :
611 8783 : if (eErr != OGRERR_NONE)
612 324 : return eErr;
613 :
614 8459 : CPLAssert(nGeomCount == 0);
615 8459 : nGeomCount = nGeomCountNew;
616 :
617 : // coverity[tainted_data]
618 8458 : papoGeoms = static_cast<OGRGeometry **>(
619 8459 : VSI_CALLOC_VERBOSE(sizeof(OGRGeometry *), nGeomCount));
620 8458 : if (nGeomCount != 0 && papoGeoms == nullptr)
621 : {
622 0 : nGeomCount = 0;
623 0 : return OGRERR_NOT_ENOUGH_MEMORY;
624 : }
625 :
626 : /* -------------------------------------------------------------------- */
627 : /* Get the Geoms. */
628 : /* -------------------------------------------------------------------- */
629 19559 : for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
630 : {
631 : // Parses sub-geometry.
632 11991 : const unsigned char *pabySubData = pabyData + nDataOffset;
633 11991 : if (nSize < 9 && nSize != static_cast<size_t>(-1))
634 891 : return OGRERR_NOT_ENOUGH_DATA;
635 :
636 11979 : OGRwkbGeometryType eSubGeomType = wkbUnknown;
637 11979 : eErr = OGRReadWKBGeometryType(pabySubData, eWkbVariant, &eSubGeomType);
638 11980 : if (eErr != OGRERR_NONE)
639 372 : return eErr;
640 :
641 11608 : if (!isCompatibleSubType(eSubGeomType))
642 : {
643 1 : nGeomCount = iGeom;
644 1 : CPLDebug(
645 : "OGR",
646 : "Cannot add geometry of type (%d) to geometry of type (%d)",
647 1 : eSubGeomType, getGeometryType());
648 1 : return OGRERR_CORRUPT_DATA;
649 : }
650 :
651 11607 : OGRGeometry *poSubGeom = nullptr;
652 11607 : size_t nSubGeomBytesConsumed = 0;
653 11607 : if (OGR_GT_IsSubClassOf(eSubGeomType, wkbGeometryCollection))
654 : {
655 161 : poSubGeom = OGRGeometryFactory::createGeometry(eSubGeomType);
656 161 : if (poSubGeom == nullptr)
657 0 : eErr = OGRERR_FAILURE;
658 : else
659 161 : eErr = poSubGeom->toGeometryCollection()->importFromWkbInternal(
660 : pabySubData, nSize, nRecLevel + 1, eWkbVariant,
661 : nSubGeomBytesConsumed);
662 : }
663 : else
664 : {
665 11446 : eErr = OGRGeometryFactory::createFromWkb(
666 : pabySubData, nullptr, &poSubGeom, nSize, eWkbVariant,
667 : nSubGeomBytesConsumed);
668 :
669 11446 : if (eErr == OGRERR_NONE)
670 : {
671 : // if this is a Z or M geom make sure the sub geoms are as well
672 10972 : if (Is3D() && !poSubGeom->Is3D())
673 : {
674 0 : CPLDebug("OGR", "Promoting sub-geometry to 3D");
675 0 : poSubGeom->set3D(TRUE);
676 : }
677 :
678 10972 : if (IsMeasured() && !poSubGeom->IsMeasured())
679 : {
680 0 : CPLDebug("OGR", "Promoting sub-geometry to Measured");
681 0 : poSubGeom->setMeasured(TRUE);
682 : }
683 : }
684 : }
685 :
686 11607 : if (eErr != OGRERR_NONE)
687 : {
688 506 : nGeomCount = iGeom;
689 506 : delete poSubGeom;
690 506 : return eErr;
691 : }
692 :
693 11101 : papoGeoms[iGeom] = poSubGeom;
694 :
695 11101 : if (papoGeoms[iGeom]->Is3D())
696 5778 : flags |= OGR_G_3D;
697 11101 : if (papoGeoms[iGeom]->IsMeasured())
698 5678 : flags |= OGR_G_MEASURED;
699 :
700 11101 : CPLAssert(nSubGeomBytesConsumed > 0);
701 11101 : if (nSize != static_cast<size_t>(-1))
702 : {
703 11096 : CPLAssert(nSize >= nSubGeomBytesConsumed);
704 11096 : nSize -= nSubGeomBytesConsumed;
705 : }
706 :
707 11101 : nDataOffset += nSubGeomBytesConsumed;
708 : }
709 7568 : nBytesConsumedOut = nDataOffset;
710 :
711 7568 : return OGRERR_NONE;
712 : }
713 :
714 : //! @endcond
715 :
716 : /************************************************************************/
717 : /* importFromWkb() */
718 : /* */
719 : /* Initialize from serialized stream in well known binary */
720 : /* format. */
721 : /************************************************************************/
722 :
723 4993 : OGRErr OGRGeometryCollection::importFromWkb(const unsigned char *pabyData,
724 : size_t nSize,
725 : OGRwkbVariant eWkbVariant,
726 : size_t &nBytesConsumedOut)
727 :
728 : {
729 4993 : return importFromWkbInternal(pabyData, nSize, 0, eWkbVariant,
730 4993 : nBytesConsumedOut);
731 : }
732 :
733 : /************************************************************************/
734 : /* exportToWkb() */
735 : /* */
736 : /* Build a well known binary representation of this object. */
737 : /************************************************************************/
738 :
739 : OGRErr
740 11308 : OGRGeometryCollection::exportToWkb(unsigned char *pabyData,
741 : const OGRwkbExportOptions *psOptions) const
742 :
743 : {
744 11308 : if (psOptions == nullptr)
745 : {
746 : static const OGRwkbExportOptions defaultOptions;
747 0 : psOptions = &defaultOptions;
748 : }
749 :
750 11308 : OGRwkbExportOptions sOptions(*psOptions);
751 :
752 11432 : if (sOptions.eWkbVariant == wkbVariantOldOgc &&
753 124 : (wkbFlatten(getGeometryType()) == wkbMultiCurve ||
754 114 : wkbFlatten(getGeometryType()) == wkbMultiSurface))
755 : {
756 : // Does not make sense for new geometries, so patch it.
757 19 : sOptions.eWkbVariant = wkbVariantIso;
758 : }
759 :
760 : /* -------------------------------------------------------------------- */
761 : /* Set the byte order. */
762 : /* -------------------------------------------------------------------- */
763 11308 : pabyData[0] = DB2_V72_UNFIX_BYTE_ORDER(
764 : static_cast<unsigned char>(sOptions.eByteOrder));
765 :
766 : /* -------------------------------------------------------------------- */
767 : /* Set the geometry feature type, ensuring that 3D flag is */
768 : /* preserved. */
769 : /* -------------------------------------------------------------------- */
770 11308 : GUInt32 nGType = getGeometryType();
771 :
772 11304 : if (sOptions.eWkbVariant == wkbVariantIso)
773 11196 : nGType = getIsoGeometryType();
774 108 : else if (sOptions.eWkbVariant == wkbVariantPostGIS1)
775 : {
776 4 : const bool bIs3D = wkbHasZ(static_cast<OGRwkbGeometryType>(nGType));
777 4 : nGType = wkbFlatten(nGType);
778 4 : if (nGType == wkbMultiCurve)
779 0 : nGType = POSTGIS15_MULTICURVE;
780 4 : else if (nGType == wkbMultiSurface)
781 0 : nGType = POSTGIS15_MULTISURFACE;
782 4 : if (bIs3D)
783 : // Yes, explicitly set wkb25DBit.
784 1 : nGType =
785 1 : static_cast<OGRwkbGeometryType>(nGType | wkb25DBitInternalUse);
786 : }
787 :
788 11304 : if (OGR_SWAP(sOptions.eByteOrder))
789 : {
790 7 : nGType = CPL_SWAP32(nGType);
791 : }
792 :
793 11304 : memcpy(pabyData + 1, &nGType, 4);
794 :
795 : /* -------------------------------------------------------------------- */
796 : /* Copy in the raw data. */
797 : /* -------------------------------------------------------------------- */
798 11304 : if (OGR_SWAP(sOptions.eByteOrder))
799 : {
800 7 : int nCount = CPL_SWAP32(nGeomCount);
801 7 : memcpy(pabyData + 5, &nCount, 4);
802 : }
803 : else
804 : {
805 11297 : memcpy(pabyData + 5, &nGeomCount, 4);
806 : }
807 :
808 11304 : size_t nOffset = 9;
809 :
810 : /* ==================================================================== */
811 : /* Serialize each of the Geoms. */
812 : /* ==================================================================== */
813 11304 : int iGeom = 0;
814 78464 : for (auto &&poSubGeom : *this)
815 : {
816 67161 : poSubGeom->exportToWkb(pabyData + nOffset, &sOptions);
817 : // Should normally not happen if everyone else does its job,
818 : // but has happened sometimes. (#6332)
819 67161 : if (poSubGeom->getCoordinateDimension() != getCoordinateDimension())
820 : {
821 0 : CPLError(CE_Warning, CPLE_AppDefined,
822 : "Sub-geometry %d has coordinate dimension %d, "
823 : "but container has %d",
824 0 : iGeom, poSubGeom->getCoordinateDimension(),
825 0 : getCoordinateDimension());
826 : }
827 :
828 67163 : nOffset += poSubGeom->WkbSize();
829 67160 : iGeom++;
830 : }
831 :
832 11303 : return OGRERR_NONE;
833 : }
834 :
835 : /************************************************************************/
836 : /* importFromWktInternal() */
837 : /************************************************************************/
838 :
839 632 : OGRErr OGRGeometryCollection::importFromWktInternal(const char **ppszInput,
840 : int nRecLevel)
841 :
842 : {
843 : // Arbitrary value, but certainly large enough for reasonable usages.
844 632 : if (nRecLevel == 32)
845 : {
846 1 : CPLError(CE_Failure, CPLE_AppDefined,
847 : "Too many recursion levels (%d) while parsing WKT geometry.",
848 : nRecLevel);
849 1 : return OGRERR_CORRUPT_DATA;
850 : }
851 :
852 631 : int bHasZ = FALSE;
853 631 : int bHasM = FALSE;
854 631 : bool bIsEmpty = false;
855 631 : OGRErr eErr = importPreambleFromWkt(ppszInput, &bHasZ, &bHasM, &bIsEmpty);
856 631 : if (eErr != OGRERR_NONE)
857 4 : return eErr;
858 627 : if (bHasZ)
859 96 : flags |= OGR_G_3D;
860 627 : if (bHasM)
861 61 : flags |= OGR_G_MEASURED;
862 627 : if (bIsEmpty)
863 43 : return OGRERR_NONE;
864 :
865 584 : char szToken[OGR_WKT_TOKEN_MAX] = {};
866 584 : const char *pszInput = *ppszInput;
867 :
868 : // Skip first '('.
869 584 : pszInput = OGRWktReadToken(pszInput, szToken);
870 :
871 : /* ==================================================================== */
872 : /* Read each subgeometry in turn. */
873 : /* ==================================================================== */
874 644 : do
875 : {
876 1228 : OGRGeometry *poGeom = nullptr;
877 :
878 : /* --------------------------------------------------------------------
879 : */
880 : /* Get the first token, which should be the geometry type. */
881 : /* --------------------------------------------------------------------
882 : */
883 1228 : OGRWktReadToken(pszInput, szToken);
884 :
885 : /* --------------------------------------------------------------------
886 : */
887 : /* Do the import. */
888 : /* --------------------------------------------------------------------
889 : */
890 1228 : if (STARTS_WITH_CI(szToken, "GEOMETRYCOLLECTION"))
891 : {
892 110 : OGRGeometryCollection *poGC = new OGRGeometryCollection();
893 110 : poGeom = poGC;
894 110 : eErr = poGC->importFromWktInternal(&pszInput, nRecLevel + 1);
895 : }
896 : else
897 : eErr =
898 1118 : OGRGeometryFactory::createFromWkt(&pszInput, nullptr, &poGeom);
899 :
900 1228 : if (eErr == OGRERR_NONE)
901 : {
902 : // If this has M, but not Z, it is an error if poGeom does
903 : // not have M.
904 1184 : if (!Is3D() && IsMeasured() && !poGeom->IsMeasured())
905 0 : eErr = OGRERR_CORRUPT_DATA;
906 : else
907 1184 : eErr = addGeometryDirectly(poGeom);
908 : }
909 1228 : if (eErr != OGRERR_NONE)
910 : {
911 44 : delete poGeom;
912 44 : return eErr;
913 : }
914 :
915 : /* --------------------------------------------------------------------
916 : */
917 : /* Read the delimiter following the ring. */
918 : /* --------------------------------------------------------------------
919 : */
920 :
921 1184 : pszInput = OGRWktReadToken(pszInput, szToken);
922 1184 : } while (szToken[0] == ',');
923 :
924 : /* -------------------------------------------------------------------- */
925 : /* freak if we don't get a closing bracket. */
926 : /* -------------------------------------------------------------------- */
927 540 : if (szToken[0] != ')')
928 2 : return OGRERR_CORRUPT_DATA;
929 :
930 538 : *ppszInput = pszInput;
931 :
932 538 : return OGRERR_NONE;
933 : }
934 :
935 : /************************************************************************/
936 : /* importFromWkt() */
937 : /************************************************************************/
938 :
939 522 : OGRErr OGRGeometryCollection::importFromWkt(const char **ppszInput)
940 :
941 : {
942 522 : return importFromWktInternal(ppszInput, 0);
943 : }
944 :
945 : /************************************************************************/
946 : /* exportToWkt() */
947 : /* */
948 : /* Translate this structure into its well known text format */
949 : /* equivalent. */
950 : /************************************************************************/
951 :
952 299 : std::string OGRGeometryCollection::exportToWkt(const OGRWktOptions &opts,
953 : OGRErr *err) const
954 : {
955 299 : return exportToWktInternal(opts, err);
956 : }
957 :
958 : //! @cond Doxygen_Suppress
959 1246 : std::string OGRGeometryCollection::exportToWktInternal(
960 : const OGRWktOptions &opts, OGRErr *err, const std::string &exclude) const
961 : {
962 1246 : bool first = true;
963 1246 : const size_t excludeSize = exclude.size();
964 2492 : std::string wkt(getGeometryName());
965 1246 : wkt += wktTypeString(opts.variant);
966 :
967 : try
968 : {
969 2951 : for (const auto &poSubGeom : *this)
970 : {
971 1705 : OGRErr subgeomErr = OGRERR_NONE;
972 1705 : std::string tempWkt = poSubGeom->exportToWkt(opts, &subgeomErr);
973 1705 : if (subgeomErr != OGRERR_NONE)
974 : {
975 0 : if (err)
976 0 : *err = subgeomErr;
977 0 : return std::string();
978 : }
979 :
980 : // For some strange reason we exclude the typename leader when using
981 : // some geometries as part of a collection.
982 1705 : if (excludeSize && (tempWkt.compare(0, excludeSize, exclude) == 0))
983 : {
984 1128 : auto pos = tempWkt.find('(');
985 : // We won't have an opening paren if the geom is empty.
986 1128 : if (pos == std::string::npos)
987 31 : continue;
988 1097 : tempWkt = tempWkt.substr(pos);
989 : }
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 1674 : if (opts.variant != wkbVariantIso)
995 : {
996 : std::string::size_type pos;
997 653 : if ((pos = tempWkt.find(" Z ")) != std::string::npos)
998 11 : tempWkt.erase(pos + 1, 2);
999 642 : else if ((pos = tempWkt.find(" M ")) != std::string::npos)
1000 0 : tempWkt.erase(pos + 1, 2);
1001 642 : else if ((pos = tempWkt.find(" ZM ")) != std::string::npos)
1002 0 : tempWkt.erase(pos + 1, 3);
1003 : }
1004 :
1005 1674 : if (first)
1006 1086 : wkt += '(';
1007 : else
1008 588 : wkt += ',';
1009 1674 : first = false;
1010 1674 : wkt += tempWkt;
1011 : }
1012 :
1013 1246 : if (err)
1014 1129 : *err = OGRERR_NONE;
1015 1246 : if (first)
1016 160 : wkt += "EMPTY";
1017 : else
1018 1086 : wkt += ')';
1019 1246 : return wkt;
1020 : }
1021 0 : catch (const std::bad_alloc &e)
1022 : {
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 : }
1028 : }
1029 :
1030 : //! @endcond
1031 :
1032 : /************************************************************************/
1033 : /* getEnvelope() */
1034 : /************************************************************************/
1035 :
1036 15637 : void OGRGeometryCollection::getEnvelope(OGREnvelope *psEnvelope) const
1037 :
1038 : {
1039 15637 : OGREnvelope3D oEnv3D;
1040 15637 : getEnvelope(&oEnv3D);
1041 15637 : psEnvelope->MinX = oEnv3D.MinX;
1042 15637 : psEnvelope->MinY = oEnv3D.MinY;
1043 15637 : psEnvelope->MaxX = oEnv3D.MaxX;
1044 15637 : psEnvelope->MaxY = oEnv3D.MaxY;
1045 15637 : }
1046 :
1047 : /************************************************************************/
1048 : /* getEnvelope() */
1049 : /************************************************************************/
1050 :
1051 16441 : void OGRGeometryCollection::getEnvelope(OGREnvelope3D *psEnvelope) const
1052 :
1053 : {
1054 16441 : OGREnvelope3D oGeomEnv;
1055 16441 : bool bExtentSet = false;
1056 :
1057 16441 : *psEnvelope = OGREnvelope3D();
1058 401776 : for (const auto &poSubGeom : *this)
1059 : {
1060 385335 : if (!poSubGeom->IsEmpty())
1061 : {
1062 385329 : bExtentSet = true;
1063 385329 : poSubGeom->getEnvelope(&oGeomEnv);
1064 385329 : psEnvelope->Merge(oGeomEnv);
1065 : }
1066 : }
1067 :
1068 16441 : if (!bExtentSet)
1069 : {
1070 : // To be backward compatible when called on empty geom
1071 13 : psEnvelope->MinX = 0.0;
1072 13 : psEnvelope->MinY = 0.0;
1073 13 : psEnvelope->MinZ = 0.0;
1074 13 : psEnvelope->MaxX = 0.0;
1075 13 : psEnvelope->MaxY = 0.0;
1076 13 : psEnvelope->MaxZ = 0.0;
1077 : }
1078 16441 : }
1079 :
1080 : /************************************************************************/
1081 : /* Equals() */
1082 : /************************************************************************/
1083 :
1084 3854 : OGRBoolean OGRGeometryCollection::Equals(const OGRGeometry *poOther) const
1085 :
1086 : {
1087 3854 : if (poOther == this)
1088 2 : return TRUE;
1089 :
1090 3852 : if (poOther->getGeometryType() != getGeometryType())
1091 0 : return FALSE;
1092 :
1093 3852 : if (IsEmpty() && poOther->IsEmpty())
1094 12 : return TRUE;
1095 :
1096 3840 : auto poOGC = poOther->toGeometryCollection();
1097 3840 : if (getNumGeometries() != poOGC->getNumGeometries())
1098 2 : return FALSE;
1099 :
1100 : // TODO(schwehr): Should test the SRS.
1101 :
1102 8425 : for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
1103 : {
1104 4588 : if (!getGeometryRef(iGeom)->Equals(poOGC->getGeometryRef(iGeom)))
1105 1 : return FALSE;
1106 : }
1107 :
1108 3837 : return TRUE;
1109 : }
1110 :
1111 : /************************************************************************/
1112 : /* transform() */
1113 : /************************************************************************/
1114 :
1115 180 : OGRErr OGRGeometryCollection::transform(OGRCoordinateTransformation *poCT)
1116 :
1117 : {
1118 180 : int iGeom = 0;
1119 405 : for (auto &poSubGeom : *this)
1120 : {
1121 225 : const OGRErr eErr = poSubGeom->transform(poCT);
1122 225 : if (eErr != OGRERR_NONE)
1123 : {
1124 0 : if (iGeom != 0)
1125 : {
1126 0 : CPLDebug("OGR",
1127 : "OGRGeometryCollection::transform() failed for a "
1128 : "geometry other than the first, meaning some "
1129 : "geometries are transformed and some are not.");
1130 :
1131 0 : return OGRERR_FAILURE;
1132 : }
1133 :
1134 0 : return eErr;
1135 : }
1136 225 : iGeom++;
1137 : }
1138 :
1139 180 : assignSpatialReference(poCT->GetTargetCS());
1140 :
1141 180 : return OGRERR_NONE;
1142 : }
1143 :
1144 : /************************************************************************/
1145 : /* closeRings() */
1146 : /************************************************************************/
1147 :
1148 129 : void OGRGeometryCollection::closeRings()
1149 :
1150 : {
1151 412 : for (auto &poSubGeom : *this)
1152 : {
1153 283 : if (OGR_GT_IsSubClassOf(wkbFlatten(poSubGeom->getGeometryType()),
1154 283 : wkbCurvePolygon))
1155 : {
1156 83 : OGRCurvePolygon *poPoly = poSubGeom->toCurvePolygon();
1157 83 : poPoly->closeRings();
1158 : }
1159 : }
1160 129 : }
1161 :
1162 : /************************************************************************/
1163 : /* setCoordinateDimension() */
1164 : /************************************************************************/
1165 :
1166 479 : bool OGRGeometryCollection::setCoordinateDimension(int nNewDimension)
1167 :
1168 : {
1169 1409 : for (auto &poSubGeom : *this)
1170 : {
1171 930 : if (!poSubGeom->setCoordinateDimension(nNewDimension))
1172 0 : return false;
1173 : }
1174 :
1175 479 : return OGRGeometry::setCoordinateDimension(nNewDimension);
1176 : }
1177 :
1178 88577 : bool OGRGeometryCollection::set3D(OGRBoolean bIs3D)
1179 : {
1180 195356 : for (auto &poSubGeom : *this)
1181 : {
1182 106779 : if (!poSubGeom->set3D(bIs3D))
1183 0 : return false;
1184 : }
1185 :
1186 88577 : return OGRGeometry::set3D(bIs3D);
1187 : }
1188 :
1189 46187 : bool OGRGeometryCollection::setMeasured(OGRBoolean bIsMeasured)
1190 : {
1191 153517 : for (auto &poSubGeom : *this)
1192 : {
1193 107330 : if (!poSubGeom->setMeasured(bIsMeasured))
1194 0 : return false;
1195 : }
1196 :
1197 46187 : return OGRGeometry::setMeasured(bIsMeasured);
1198 : }
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 9 : double OGRGeometryCollection::get_Length() const
1217 : {
1218 9 : double dfLength = 0.0;
1219 26 : for (const auto &poSubGeom : *this)
1220 : {
1221 : const OGRwkbGeometryType eType =
1222 17 : wkbFlatten(poSubGeom->getGeometryType());
1223 17 : if (OGR_GT_IsCurve(eType))
1224 : {
1225 13 : const OGRCurve *poCurve = poSubGeom->toCurve();
1226 13 : dfLength += poCurve->get_Length();
1227 : }
1228 4 : else if (OGR_GT_IsSurface(eType))
1229 : {
1230 1 : const OGRSurface *poSurface = poSubGeom->toSurface();
1231 1 : dfLength += poSurface->get_Length();
1232 : }
1233 3 : else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1234 : {
1235 : const OGRGeometryCollection *poColl =
1236 2 : poSubGeom->toGeometryCollection();
1237 2 : dfLength += poColl->get_Length();
1238 : }
1239 : }
1240 :
1241 9 : return dfLength;
1242 : }
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 12 : double OGRGeometryCollection::get_Area() const
1261 : {
1262 12 : double dfArea = 0.0;
1263 33 : for (const auto &poSubGeom : *this)
1264 : {
1265 21 : OGRwkbGeometryType eType = wkbFlatten(poSubGeom->getGeometryType());
1266 21 : if (OGR_GT_IsSurface(eType))
1267 : {
1268 16 : const OGRSurface *poSurface = poSubGeom->toSurface();
1269 16 : dfArea += poSurface->get_Area();
1270 : }
1271 5 : else if (OGR_GT_IsCurve(eType))
1272 : {
1273 1 : const OGRCurve *poCurve = poSubGeom->toCurve();
1274 1 : dfArea += poCurve->get_Area();
1275 : }
1276 4 : else if (OGR_GT_IsSubClassOf(eType, wkbMultiSurface) ||
1277 : eType == wkbGeometryCollection)
1278 : {
1279 2 : dfArea += poSubGeom->toGeometryCollection()->get_Area();
1280 : }
1281 : }
1282 :
1283 12 : return dfArea;
1284 : }
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 6 : double OGRGeometryCollection::get_GeodesicArea(
1320 : const OGRSpatialReference *poSRSOverride) const
1321 : {
1322 6 : double dfArea = 0.0;
1323 11 : for (const auto &poSubGeom : *this)
1324 : {
1325 8 : OGRwkbGeometryType eType = wkbFlatten(poSubGeom->getGeometryType());
1326 8 : if (OGR_GT_IsSurface(eType))
1327 : {
1328 4 : const OGRSurface *poSurface = poSubGeom->toSurface();
1329 : const double dfLocalArea =
1330 4 : poSurface->get_GeodesicArea(poSRSOverride);
1331 4 : if (dfLocalArea < 0)
1332 1 : return dfLocalArea;
1333 3 : dfArea += dfLocalArea;
1334 : }
1335 4 : else if (OGR_GT_IsCurve(eType))
1336 : {
1337 2 : const OGRCurve *poCurve = poSubGeom->toCurve();
1338 2 : const double dfLocalArea = poCurve->get_GeodesicArea(poSRSOverride);
1339 2 : if (dfLocalArea < 0)
1340 1 : return dfLocalArea;
1341 1 : dfArea += dfLocalArea;
1342 : }
1343 2 : else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1344 : {
1345 : const double dfLocalArea =
1346 2 : poSubGeom->toGeometryCollection()->get_GeodesicArea(
1347 2 : poSRSOverride);
1348 2 : if (dfLocalArea < 0)
1349 1 : return dfLocalArea;
1350 1 : dfArea += dfLocalArea;
1351 : }
1352 : }
1353 :
1354 3 : return dfArea;
1355 : }
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 5 : double OGRGeometryCollection::get_GeodesicLength(
1394 : const OGRSpatialReference *poSRSOverride) const
1395 : {
1396 5 : double dfLength = 0.0;
1397 10 : for (const auto &poSubGeom : *this)
1398 : {
1399 : const OGRwkbGeometryType eType =
1400 7 : wkbFlatten(poSubGeom->getGeometryType());
1401 7 : if (OGR_GT_IsSurface(eType))
1402 : {
1403 4 : const OGRSurface *poSurface = poSubGeom->toSurface();
1404 : const double dfLocalLength =
1405 4 : poSurface->get_GeodesicLength(poSRSOverride);
1406 4 : if (dfLocalLength < 0)
1407 1 : return dfLocalLength;
1408 3 : dfLength += dfLocalLength;
1409 : }
1410 3 : else if (OGR_GT_IsCurve(eType))
1411 : {
1412 1 : const OGRCurve *poCurve = poSubGeom->toCurve();
1413 : const double dfLocalLength =
1414 1 : poCurve->get_GeodesicLength(poSRSOverride);
1415 1 : if (dfLocalLength < 0)
1416 0 : return dfLocalLength;
1417 1 : dfLength += dfLocalLength;
1418 : }
1419 2 : else if (OGR_GT_IsSubClassOf(eType, wkbGeometryCollection))
1420 : {
1421 : const double dfLocalLength =
1422 2 : poSubGeom->toGeometryCollection()->get_GeodesicLength(
1423 2 : poSRSOverride);
1424 2 : if (dfLocalLength < 0)
1425 1 : return dfLocalLength;
1426 1 : dfLength += dfLocalLength;
1427 : }
1428 : }
1429 :
1430 3 : return dfLength;
1431 : }
1432 :
1433 : /************************************************************************/
1434 : /* IsEmpty() */
1435 : /************************************************************************/
1436 :
1437 32466 : OGRBoolean OGRGeometryCollection::IsEmpty() const
1438 : {
1439 32605 : for (const auto &poSubGeom : *this)
1440 : {
1441 31303 : if (poSubGeom->IsEmpty() == FALSE)
1442 31164 : return FALSE;
1443 : }
1444 1302 : return TRUE;
1445 : }
1446 :
1447 : /************************************************************************/
1448 : /* assignSpatialReference() */
1449 : /************************************************************************/
1450 :
1451 112641 : void OGRGeometryCollection::assignSpatialReference(
1452 : const OGRSpatialReference *poSR)
1453 : {
1454 112641 : OGRGeometry::assignSpatialReference(poSR);
1455 1524110 : for (auto &poSubGeom : *this)
1456 : {
1457 1411460 : poSubGeom->assignSpatialReference(poSR);
1458 : }
1459 112641 : }
1460 :
1461 : /************************************************************************/
1462 : /* OGRGeometryCollection::segmentize() */
1463 : /************************************************************************/
1464 :
1465 13 : bool OGRGeometryCollection::segmentize(double dfMaxLength)
1466 : {
1467 26 : for (auto &poSubGeom : *this)
1468 : {
1469 13 : if (!poSubGeom->segmentize(dfMaxLength))
1470 0 : return false;
1471 : }
1472 13 : return true;
1473 : }
1474 :
1475 : /************************************************************************/
1476 : /* swapXY() */
1477 : /************************************************************************/
1478 :
1479 30 : void OGRGeometryCollection::swapXY()
1480 : {
1481 76 : for (auto &poSubGeom : *this)
1482 : {
1483 46 : poSubGeom->swapXY();
1484 : }
1485 30 : }
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 9253 : OGRBoolean OGRGeometryCollection::isCompatibleSubType(
1499 : CPL_UNUSED OGRwkbGeometryType eSubType) const
1500 : {
1501 : // Accept all geometries as sub-geometries.
1502 9253 : return TRUE;
1503 : }
1504 :
1505 : /************************************************************************/
1506 : /* hasCurveGeometry() */
1507 : /************************************************************************/
1508 :
1509 2492 : OGRBoolean OGRGeometryCollection::hasCurveGeometry(int bLookForNonLinear) const
1510 : {
1511 6316 : for (const auto &poSubGeom : *this)
1512 : {
1513 3907 : if (poSubGeom->hasCurveGeometry(bLookForNonLinear))
1514 83 : return TRUE;
1515 : }
1516 2409 : return FALSE;
1517 : }
1518 :
1519 : /************************************************************************/
1520 : /* getLinearGeometry() */
1521 : /************************************************************************/
1522 :
1523 : OGRGeometry *
1524 331 : OGRGeometryCollection::getLinearGeometry(double dfMaxAngleStepSizeDegrees,
1525 : const char *const *papszOptions) const
1526 : {
1527 : auto poGC = std::unique_ptr<OGRGeometryCollection>(
1528 331 : OGRGeometryFactory::createGeometry(OGR_GT_GetLinear(getGeometryType()))
1529 662 : ->toGeometryCollection());
1530 331 : if (!poGC)
1531 0 : return nullptr;
1532 331 : poGC->assignSpatialReference(getSpatialReference());
1533 704 : for (const auto &poSubGeom : *this)
1534 : {
1535 746 : OGRGeometry *poSubGeomNew = poSubGeom->getLinearGeometry(
1536 373 : dfMaxAngleStepSizeDegrees, papszOptions);
1537 373 : if (poGC->addGeometryDirectly(poSubGeomNew) != OGRERR_NONE)
1538 0 : return nullptr;
1539 : }
1540 331 : return poGC.release();
1541 : }
1542 :
1543 : /************************************************************************/
1544 : /* getCurveGeometry() */
1545 : /************************************************************************/
1546 :
1547 : OGRGeometry *
1548 15 : OGRGeometryCollection::getCurveGeometry(const char *const *papszOptions) const
1549 : {
1550 : auto poGC = std::unique_ptr<OGRGeometryCollection>(
1551 15 : OGRGeometryFactory::createGeometry(OGR_GT_GetCurve(getGeometryType()))
1552 30 : ->toGeometryCollection());
1553 15 : if (!poGC)
1554 0 : return nullptr;
1555 15 : poGC->assignSpatialReference(getSpatialReference());
1556 15 : bool bHasCurveGeometry = false;
1557 162 : for (const auto &poSubGeom : *this)
1558 : {
1559 147 : OGRGeometry *poSubGeomNew = poSubGeom->getCurveGeometry(papszOptions);
1560 147 : if (poSubGeomNew->hasCurveGeometry())
1561 7 : bHasCurveGeometry = true;
1562 147 : if (poGC->addGeometryDirectly(poSubGeomNew) != OGRERR_NONE)
1563 0 : return nullptr;
1564 : }
1565 15 : if (!bHasCurveGeometry)
1566 : {
1567 10 : return clone();
1568 : }
1569 5 : return poGC.release();
1570 : }
1571 :
1572 : /************************************************************************/
1573 : /* TransferMembersAndDestroy() */
1574 : /************************************************************************/
1575 :
1576 : //! @cond Doxygen_Suppress
1577 : OGRGeometryCollection *
1578 1049 : OGRGeometryCollection::TransferMembersAndDestroy(OGRGeometryCollection *poSrc,
1579 : OGRGeometryCollection *poDst)
1580 : {
1581 1049 : poDst->assignSpatialReference(poSrc->getSpatialReference());
1582 1049 : poDst->set3D(poSrc->Is3D());
1583 1049 : poDst->setMeasured(poSrc->IsMeasured());
1584 1049 : poDst->nGeomCount = poSrc->nGeomCount;
1585 1049 : poDst->papoGeoms = poSrc->papoGeoms;
1586 1049 : poSrc->nGeomCount = 0;
1587 1049 : poSrc->papoGeoms = nullptr;
1588 1049 : delete poSrc;
1589 1049 : return poDst;
1590 : }
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 920 : OGRGeometryCollection::CastToGeometryCollection(OGRGeometryCollection *poSrc)
1613 : {
1614 920 : if (wkbFlatten(poSrc->getGeometryType()) == wkbGeometryCollection)
1615 0 : return poSrc;
1616 920 : return TransferMembersAndDestroy(poSrc, new OGRGeometryCollection());
1617 : }
|