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