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