QGIS API Documentation 4.1.0-Master (31622b25bb0)
Loading...
Searching...
No Matches
qgspolyhedralsurface.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspolyhedralsurface.cpp
3 ---------------------
4 begin : August 2024
5 copyright : (C) 2024 by Jean Felder
6 email : jean dot felder at oslandia dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include <memory>
21#include <nlohmann/json.hpp>
22
23#include "qgsapplication.h"
24#include "qgscurve.h"
25#include "qgsfeedback.h"
26#include "qgsgeometryutils.h"
27#include "qgsgeos.h"
28#include "qgslinestring.h"
29#include "qgslogger.h"
30#include "qgsmultilinestring.h"
31#include "qgsmultipolygon.h"
32#include "qgsmultisurface.h"
33#include "qgspolygon.h"
34#include "qgsvertexid.h"
35#include "qgswkbptr.h"
36
37#include <QPainter>
38#include <QPainterPath>
39#include <QString>
40
41using namespace Qt::StringLiterals;
42
47
49{
50 if ( multiPolygon->isEmpty() )
51 {
52 return;
53 }
54
55 mPatches.reserve( multiPolygon->numGeometries() );
56 for ( int i = 0; i < multiPolygon->numGeometries(); ++i )
57 {
58 mPatches.append( multiPolygon->polygonN( i )->clone() );
59 }
60
62}
63
68
70{
71 auto result = std::make_unique< QgsPolyhedralSurface >();
72 result->mWkbType = mWkbType;
73 return result.release();
74}
75
77{
78 return u"PolyhedralSurface"_s;
79}
80
82{
83 return 2;
84}
85
87 : QgsSurface( p )
88
89{
91 mPatches.reserve( p.mPatches.size() );
92
93 for ( const QgsPolygon *patch : p.mPatches )
94 {
95 mPatches.push_back( patch->clone() );
96 }
97
101}
102
103// cppcheck-suppress operatorEqVarError
105{
106 if ( &p != this )
107 {
109 mPatches.reserve( p.mPatches.size() );
110
111 for ( const QgsPolygon *patch : p.mPatches )
112 {
113 mPatches.push_back( patch->clone() );
114 }
115 }
116 return *this;
117}
118
123
125{
127 qDeleteAll( mPatches );
128 mPatches.clear();
129 clearCache();
130}
131
132
134{
135 clear();
136
137 if ( !wkbPtr )
138 {
139 return false;
140 }
141
142 Qgis::WkbType type = wkbPtr.readHeader();
144 {
145 return false;
146 }
147 mWkbType = type;
148
149 int nPatches;
150 wkbPtr >> nPatches;
151 std::unique_ptr< QgsPolygon > currentPatch;
152 for ( int i = 0; i < nPatches; ++i )
153 {
154 Qgis::WkbType polygonType = wkbPtr.readHeader();
155 wkbPtr -= 1 + sizeof( int );
156 Qgis::WkbType flatPolygonType = QgsWkbTypes::flatType( polygonType );
157 if ( flatPolygonType == Qgis::WkbType::Polygon )
158 {
159 currentPatch = std::make_unique<QgsPolygon>();
160 }
161 else
162 {
163 return false;
164 }
165 currentPatch->fromWkb( wkbPtr ); // also updates wkbPtr
166 mPatches.append( currentPatch.release() );
167 }
168
169 return true;
170}
171
172bool QgsPolyhedralSurface::fromWkt( const QString &wkt )
173{
174 clear();
175
176 QPair<Qgis::WkbType, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
177
179 return false;
180
181 mWkbType = parts.first;
182
183 QString secondWithoutParentheses = parts.second;
184 secondWithoutParentheses = secondWithoutParentheses.remove( '(' ).remove( ')' ).simplified().remove( ' ' );
185 if ( ( parts.second.compare( "EMPTY"_L1, Qt::CaseInsensitive ) == 0 ) || secondWithoutParentheses.isEmpty() )
186 return true;
187
188 QString defaultChildWkbType = u"Polygon%1%2"_s.arg( is3D() ? u"Z"_s : QString(), isMeasure() ? u"M"_s : QString() );
189
190 const QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, defaultChildWkbType );
191 for ( const QString &childWkt : blocks )
192 {
193 QPair<Qgis::WkbType, QString> childParts = QgsGeometryUtils::wktReadBlock( childWkt );
194
195 if ( QgsWkbTypes::flatType( childParts.first ) == Qgis::WkbType::Polygon )
196 {
197 mPatches.append( new QgsPolygon() );
198 }
199 else
200 {
201 clear();
202 return false;
203 }
204
205 if ( !mPatches.back()->fromWkt( childWkt ) )
206 {
207 clear();
208 return false;
209 }
210 }
211
212 return true;
213}
214
216{
217 if ( mPatches.empty() )
218 {
219 return QgsBox3D();
220 }
221
222 QgsBox3D bbox = mPatches.at( 0 )->boundingBox3D();
223 for ( int i = 1; i < mPatches.size(); ++i )
224 {
225 QgsBox3D polygonBox = mPatches.at( i )->boundingBox3D();
226 bbox.combineWith( polygonBox );
227 }
228 return bbox;
229}
230
232{
233 int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
234 for ( const QgsPolygon *patch : mPatches )
235 {
236 binarySize += patch->wkbSize( flags );
237 }
238 return binarySize;
239}
240
241QByteArray QgsPolyhedralSurface::asWkb( WkbFlags flags ) const
242{
243 QByteArray wkbArray;
244 wkbArray.resize( QgsPolyhedralSurface::wkbSize( flags ) );
245 QgsWkbPtr wkb( wkbArray );
246 wkb << static_cast<char>( QgsApplication::endian() );
247 wkb << static_cast<quint32>( wkbType() );
248 wkb << static_cast<quint32>( mPatches.size() );
249 for ( const QgsPolygon *patch : mPatches )
250 {
251 wkb << patch->asWkb( flags );
252 }
253 return wkbArray;
254}
255
256QString QgsPolyhedralSurface::asWkt( int precision ) const
257{
258 QString wkt = wktTypeStr();
259
260 if ( isEmpty() )
261 wkt += " EMPTY"_L1;
262 else
263 {
264 wkt += " ("_L1;
265 for ( const QgsPolygon *patch : mPatches )
266 {
267 QString childWkt = patch->asWkt( precision );
269 {
270 // Type names of linear geometries are omitted
271 childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
272 }
273 wkt += childWkt + ',';
274 }
275 if ( wkt.endsWith( ',' ) )
276 {
277 wkt.chop( 1 ); // Remove last ','
278 }
279 wkt += ')';
280 }
281 return wkt;
282}
283
284QDomElement QgsPolyhedralSurface::asGml2( QDomDocument &, int, const QString &, const AxisOrder ) const
285{
286 QgsDebugError( u"gml version 2 does not support PolyhedralSurface geometry"_s );
287 return QDomElement();
288}
289
290QDomElement QgsPolyhedralSurface::asGml3( QDomDocument &doc, int precision, const QString &ns, const QgsAbstractGeometry::AxisOrder axisOrder ) const
291{
292 QDomElement elemPolyhedralSurface = doc.createElementNS( ns, u"PolyhedralSurface"_s );
293
294 if ( isEmpty() )
295 return elemPolyhedralSurface;
296
297 QDomElement elemPolygonPatches = doc.createElementNS( ns, u"polygonPatches"_s );
298
299 for ( QgsPolygon *patch : mPatches )
300 {
301 QDomElement elemPolygonPatch = patch->asGml3( doc, precision, ns, axisOrder );
302 elemPolygonPatch.setTagName( "PolygonPatch" );
303
304 elemPolygonPatches.appendChild( elemPolygonPatch );
305 }
306 elemPolyhedralSurface.appendChild( elemPolygonPatches );
307
308 return elemPolyhedralSurface;
309}
310
311json QgsPolyhedralSurface::asJsonObject( int precision ) const
312{
313 // GeoJSON format does not support PolyhedralSurface geometry
314 // Return a multipolygon instead;
315 std::unique_ptr<QgsMultiPolygon> multiPolygon( toMultiPolygon() );
316 return multiPolygon->asJsonObject( precision );
317}
318
319QString QgsPolyhedralSurface::asKml( int ) const
320{
321 QgsDebugError( u"kml format does not support PolyhedralSurface geometry"_s );
322 return QString( "" );
323}
324
326{
327 for ( QgsPolygon *patch : mPatches )
328 {
329 QgsCurve *exteriorRing = patch->exteriorRing();
330 if ( !exteriorRing )
331 continue;
332
333 exteriorRing->normalize();
334
335 if ( patch->numInteriorRings() > 0 )
336 {
337 QVector<QgsCurve *> interiorRings;
338 for ( int i = 0, n = patch->numInteriorRings(); i < n; ++i )
339 {
340 interiorRings.push_back( patch->interiorRing( i )->clone() );
341 }
342
343 // sort rings
344 std::sort( interiorRings.begin(), interiorRings.end(), []( const QgsCurve *a, const QgsCurve *b ) { return a->compareTo( b ) > 0; } );
345
346 patch->removeInteriorRings();
347 for ( QgsCurve *curve : interiorRings )
348 patch->addInteriorRing( curve );
349 }
350 }
351}
352
354{
355 // sum area of patches
356 double area = 0.0;
357 for ( const QgsPolygon *patch : mPatches )
358 {
359 area += patch->area();
360 }
361
362 return area;
363}
364
366{
367 // sum area 3D of patches
368 double area = 0.0;
369 for ( const QgsPolygon *patch : mPatches )
370 {
371 area += patch->area3D();
372 }
373
374 return area;
375}
376
378{
379 // sum perimeter of patches
380 double perimeter = 0.0;
381 for ( const QgsPolygon *patch : mPatches )
382 {
383 perimeter += patch->perimeter();
384 }
385
386 return perimeter;
387}
388
390{
391 auto multiLine = std::make_unique<QgsMultiLineString>();
392 multiLine->reserve( mPatches.size() );
393 for ( QgsPolygon *polygon : mPatches )
394 {
395 std::unique_ptr<QgsAbstractGeometry> polygonBoundary( polygon->boundary() );
396 if ( QgsLineString *lineStringBoundary = qgsgeometry_cast< QgsLineString * >( polygonBoundary.get() ) )
397 {
398 multiLine->addGeometry( lineStringBoundary->clone() );
399 }
400 else if ( QgsMultiLineString *multiLineStringBoundary = qgsgeometry_cast< QgsMultiLineString * >( polygonBoundary.get() ) )
401 {
402 for ( int j = 0; j < multiLineStringBoundary->numGeometries(); ++j )
403 {
404 multiLine->addGeometry( multiLineStringBoundary->geometryN( j )->clone() );
405 }
406 }
407 }
408
409 if ( multiLine->numGeometries() == 0 )
410 {
411 return nullptr;
412 }
413 return multiLine.release();
414}
415
416QgsPolyhedralSurface *QgsPolyhedralSurface::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const
417{
418 std::unique_ptr< QgsPolyhedralSurface > surface( createEmptyWithSameType() );
419
420 for ( QgsPolygon *patch : mPatches )
421 {
422 // exterior ring
423 std::unique_ptr<QgsCurve> exteriorRing( static_cast< QgsCurve *>( patch->exteriorRing()->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) );
424 if ( !exteriorRing )
425 {
426 return nullptr;
427 }
428
429 auto gridifiedPatch = std::make_unique<QgsPolygon>();
430 gridifiedPatch->setExteriorRing( exteriorRing.release() );
431
432 //interior rings
433 for ( int i = 0, n = patch->numInteriorRings(); i < n; ++i )
434 {
435 QgsCurve *interiorRing = patch->interiorRing( i );
436 if ( !interiorRing )
437 continue;
438
439 std::unique_ptr<QgsCurve> gridifiedInterior( static_cast< QgsCurve * >( interiorRing->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) );
440 if ( gridifiedInterior )
441 gridifiedPatch->addInteriorRing( gridifiedInterior.release() );
442 }
443
444 surface->addPatch( gridifiedPatch.release() );
445 }
446
447 return surface.release();
448}
449
451{
452 if ( isEmpty() )
453 return nullptr;
454
455 auto simplifiedGeom = std::make_unique< QgsPolyhedralSurface >();
456 for ( QgsPolygon *polygon : mPatches )
457 {
458 std::unique_ptr<QgsCurvePolygon> polygonSimplified( polygon->simplifyByDistance( tolerance ) );
459 simplifiedGeom->addPatch( polygonSimplified->surfaceToPolygon() );
460 }
461 return simplifiedGeom.release();
462}
463
464bool QgsPolyhedralSurface::removeDuplicateNodes( double epsilon, bool useZValues )
465{
466 bool result = false;
467
468 for ( QgsPolygon *patch : std::as_const( mPatches ) )
469 {
470 if ( patch->removeDuplicateNodes( epsilon, useZValues ) )
471 {
472 result = true;
473 }
474 }
475 return result;
476}
477
479{
480 // if we already have the bounding box calculated, then this check is trivial!
481 if ( !mBoundingBox.isNull() )
482 {
483 return mBoundingBox.intersects( box3d );
484 }
485
486 // loop through each patch and test the bounding box intersection.
487 // This gives us a chance to use optimisations which may be present on the individual
488 // ring geometry subclasses, and at worst it will cause a calculation of the bounding box
489 // of each individual patch geometry which we would have to do anyway... (and these
490 // bounding boxes are cached, so would be reused without additional expense)
491 for ( const QgsPolygon *patch : mPatches )
492 {
493 if ( patch->boundingBoxIntersects( box3d ) )
494 return true;
495 }
496
497 // even if we don't intersect the bounding box of any rings, we may still intersect the
498 // bounding box of the overall polygon (we are considering worst case scenario here and
499 // the polygon is invalid, with rings outside the exterior ring!)
500 // so here we fall back to the non-optimised base class check which has to first calculate
501 // the overall bounding box of the polygon..
502 return QgsSurface::boundingBoxIntersects( box3d );
503}
504
505void QgsPolyhedralSurface::setPatches( const QVector<QgsPolygon *> &patches )
506{
507 qDeleteAll( mPatches );
508 mPatches.clear();
509
510 for ( QgsPolygon *patch : patches )
511 {
512 addPatch( patch );
513 }
514
515 clearCache();
516}
517
519{
520 if ( !patch )
521 return;
522
523 if ( mPatches.empty() )
524 {
526 }
527
528 // Ensure dimensionality of patch matches polyhedral surface
529 if ( !is3D() )
530 patch->dropZValue();
531 else if ( !patch->is3D() )
532 patch->addZValue();
533
534 if ( !isMeasure() )
535 patch->dropMValue();
536 else if ( !patch->isMeasure() )
537 patch->addMValue();
538
539 mPatches.append( patch );
540 clearCache();
541}
542
544{
545 if ( patchIndex < 0 || patchIndex >= mPatches.size() )
546 {
547 return false;
548 }
549
550 delete mPatches.takeAt( patchIndex );
551 clearCache();
552 return true;
553}
554
556{
557 QPainterPath painterPath;
558 for ( const QgsPolygon *patch : mPatches )
559 {
560 QPainterPath patchPath = patch->asQPainterPath();
561 patchPath.closeSubpath();
562 painterPath.addPath( patchPath );
563 }
564
565 return painterPath;
566}
567
568void QgsPolyhedralSurface::draw( QPainter &p ) const
569{
570 if ( mPatches.empty() )
571 return;
572
573 for ( const QgsPolygon *patch : mPatches )
574 {
575 patch->draw( p );
576 }
577}
578
580{
581 for ( QgsPolygon *patch : std::as_const( mPatches ) )
582 {
583 patch->transform( ct, d, transformZ );
584 }
585 clearCache();
586}
587
588void QgsPolyhedralSurface::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
589{
590 for ( QgsPolygon *patch : std::as_const( mPatches ) )
591 {
592 patch->transform( t, zTranslate, zScale, mTranslate, mScale );
593 }
594 clearCache();
595}
596
598{
599 QgsCoordinateSequence sequence;
600 for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
601 {
602 QgsCoordinateSequence polyCoords = polygon->coordinateSequence();
603 QgsCoordinateSequence::const_iterator cIt = polyCoords.constBegin();
604 for ( ; cIt != polyCoords.constEnd(); ++cIt )
605 {
606 sequence.push_back( *cIt );
607 }
608 }
609
610 return sequence;
611}
612
614{
615 int count = 0;
616 for ( const QgsPolygon *patch : mPatches )
617 {
618 count += patch->nCoordinates();
619 }
620 return count;
621}
622
624{
625 if ( id.part < 0 || id.part >= partCount() )
626 return -1;
627
628 int number = 0;
629 for ( int i = 0; i < mPatches.count(); ++i )
630 {
631 if ( id.part == i )
632 {
633 int partNumber = mPatches.at( i )->vertexNumberFromVertexId( QgsVertexId( 0, id.ring, id.vertex ) );
634 if ( partNumber == -1 )
635 {
636 return -1;
637 }
638
639 return number + partNumber;
640 }
641 else
642 {
643 number += mPatches.at( i )->nCoordinates();
644 }
645 }
646
647 return -1; // should not happen
648}
649
651{
652 return mPatches.isEmpty();
653}
654
655double QgsPolyhedralSurface::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
656{
657 QVector<QgsPolygon *> segmentList = mPatches;
658 return QgsGeometryUtils::closestSegmentFromComponents( segmentList, QgsGeometryUtils::Part, pt, segmentPt, vertexAfter, leftOf, epsilon );
659}
660
662{
663 if ( vId.part < 0 )
664 {
665 vId.part = 0;
666 vId.ring = -1;
667 vId.vertex = -1;
668 }
669
670 if ( isEmpty() || vId.part >= partCount() )
671 {
672 return false;
673 }
674
675 QgsPolygon *patch = mPatches[vId.part];
676 if ( patch->nextVertex( vId, vertex ) )
677 {
678 return true;
679 }
680
681 ++vId.part;
682 vId.ring = 0;
683 vId.vertex = -1;
684 if ( vId.part >= partCount() )
685 {
686 return false;
687 }
688 patch = mPatches[vId.part];
689 return patch->nextVertex( vId, vertex );
690}
691
693{
694 if ( vertex.part < 0 || vertex.part >= partCount() )
695 {
696 previousVertex = QgsVertexId();
698 return;
699 }
700
701 QgsPolygon *patch = mPatches[vertex.ring];
702 patch->adjacentVertices( QgsVertexId( 0, 0, vertex.vertex ), previousVertex, nextVertex );
703 return;
704}
705
707{
708 if ( vId.part < 0 || vId.part >= partCount() )
709 {
710 return false;
711 }
712
713 QgsPolygon *patch = mPatches.at( vId.part );
714 bool success = patch->insertVertex( QgsVertexId( 0, vId.ring, vId.vertex ), vertex );
715 if ( success )
716 {
717 clearCache();
718 }
719
720 return success;
721}
722
724{
725 if ( vId.part < 0 || vId.part >= partCount() )
726 {
727 return false;
728 }
729
730 QgsPolygon *patch = mPatches.at( vId.part );
731 bool success = patch->moveVertex( QgsVertexId( 0, vId.ring, vId.vertex ), newPos );
732 if ( success )
733 {
734 clearCache();
735 }
736
737 return success;
738}
739
741{
742 if ( vId.part < 0 || vId.part >= partCount() )
743 {
744 return false;
745 }
746
747 QgsPolygon *patch = mPatches.at( vId.part );
748 bool success = patch->deleteVertex( QgsVertexId( 0, vId.ring, vId.vertex ) );
749 if ( success )
750 {
751 // if the patch has lost its exterior ring, remove it
752 if ( !patch->exteriorRing() )
753 {
754 delete mPatches.takeAt( vId.part );
755 }
756 clearCache();
757 }
758
759 return success;
760}
761
762bool QgsPolyhedralSurface::deleteVertices( const QSet<QgsVertexId> &positions )
763{
764 QMap<int, QSet<QgsVertexId>> partVertices;
765 for ( QgsVertexId pos : positions )
766 {
767 if ( !hasVertex( pos ) )
768 {
769 return false;
770 }
771
772 partVertices[pos.part].insert( QgsVertexId( 0, pos.ring, pos.vertex ) );
773 }
774
775 QMapIterator<int, QSet<QgsVertexId>> partVerticesIt( partVertices );
776 partVerticesIt.toBack();
777 while ( partVerticesIt.hasPrevious() )
778 {
779 partVerticesIt.previous();
780
781 int part = partVerticesIt.key();
782 QSet<QgsVertexId> vertexMap = partVerticesIt.value();
783 QgsPolygon *patch = mPatches.at( part );
784
785 if ( !patch->deleteVertices( vertexMap ) )
786 {
787 Q_ASSERT( false );
788 return false;
789 }
790
791 if ( !patch->exteriorRing() )
792 {
793 delete mPatches.takeAt( part );
794 }
795 }
796
797 clearCache();
798 return true;
799}
800
802{
803 size_t parts = partCount();
804 if ( id.part < 0 || static_cast<size_t>( id.part ) >= parts )
805 return false;
806
807 QgsAbstractGeometry *geom = mPatches.at( id.part );
808 if ( !geom )
809 return false;
810
811 return geom->hasVertex( QgsVertexId( 0, id.ring, id.vertex ) );
812}
813
815{
816 return false;
817}
818
820{
821 // This is only used by curves
822 Q_UNUSED( tolerance )
823 Q_UNUSED( toleranceType )
824 return clone();
825}
826
828{
829 if ( vertex.part < 0 || vertex.part >= partCount() )
830 {
831 return 0.0;
832 }
833
834 QgsPolygon *patch = mPatches[vertex.part];
835 return patch->vertexAngle( QgsVertexId( 0, vertex.ring, vertex.vertex ) );
836}
837
838int QgsPolyhedralSurface::vertexCount( int part, int ring ) const
839{
840 if ( part < 0 || part >= partCount() )
841 {
842 return 0;
843 }
844
845 QgsPolygon *patchPolygon = mPatches[part];
846 QgsCurve *ringCurve = ring == 0 ? patchPolygon->exteriorRing() : patchPolygon->interiorRing( ring - 1 );
847 if ( ringCurve )
848 {
849 return ringCurve->vertexCount();
850 }
851
852 return 0;
853}
854
856{
857 if ( part < 0 || part >= partCount() )
858 return 0;
859
860 return mPatches[part]->ringCount();
861}
862
864{
865 return mPatches.size();
866}
867
869{
870 if ( id.part < 0 || id.part >= partCount() )
871 return QgsPoint();
872
873 return mPatches[id.part]->vertexAt( id );
874}
875
877{
878 if ( startVertex.part < 0 || startVertex.part >= partCount() )
879 {
880 return 0.0;
881 }
882
883 const QgsPolygon *patch = mPatches[startVertex.part];
884 return patch->segmentLength( QgsVertexId( 0, startVertex.ring, startVertex.vertex ) );
885}
886
888{
890 {
891 return false;
892 }
893
895
896 for ( QgsPolygon *patch : std::as_const( mPatches ) )
897 {
898 patch->addZValue( zValue );
899 }
900 clearCache();
901 return true;
902}
903
905{
907 {
908 return false;
909 }
910
912
913 for ( QgsPolygon *patch : std::as_const( mPatches ) )
914 {
915 patch->addMValue( mValue );
916 }
917 clearCache();
918 return true;
919}
920
922{
923 if ( !is3D() )
924 {
925 return false;
926 }
927
929 for ( QgsPolygon *patch : std::as_const( mPatches ) )
930 {
931 patch->dropZValue();
932 }
933 clearCache();
934 return true;
935}
936
938{
939 if ( !isMeasure() )
940 {
941 return false;
942 }
943
945 for ( QgsPolygon *patch : std::as_const( mPatches ) )
946 {
947 patch->dropMValue();
948 }
949 clearCache();
950 return true;
951}
952
954{
955 for ( QgsPolygon *patch : std::as_const( mPatches ) )
956 {
957 patch->swapXy();
958 }
959 clearCache();
960}
961
963{
964 auto multiSurface = std::make_unique< QgsMultiSurface >();
965 multiSurface->reserve( mPatches.size() );
966 for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
967 {
968 multiSurface->addGeometry( polygon->clone() );
969 }
970 return multiSurface.release();
971}
972
974{
975 if ( !transformer )
976 return false;
977
978 bool res = true;
979
980 for ( QgsPolygon *patch : std::as_const( mPatches ) )
981 {
982 res = patch->transform( transformer );
983 if ( feedback && feedback->isCanceled() )
984 res = false;
985
986 if ( !res )
987 break;
988 }
989
990 clearCache();
991 return res;
992}
993
995{
996 auto multiPolygon = std::make_unique< QgsMultiPolygon >();
997 multiPolygon->reserve( mPatches.size() );
998 for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
999 {
1000 multiPolygon->addGeometry( polygon->clone() );
1001 }
1002 return multiPolygon.release();
1003}
1004
1005void QgsPolyhedralSurface::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
1006{
1007 for ( QgsPolygon *patch : std::as_const( mPatches ) )
1008 {
1009 patch->filterVertices( filter );
1010 }
1011
1012 clearCache();
1013}
1014
1016{
1017 for ( QgsPolygon *patch : std::as_const( mPatches ) )
1018 {
1019 patch->transformVertices( transform );
1020 }
1021
1022 clearCache();
1023}
1024
1026{
1027 return mPatches.count();
1028}
1029
1031{
1032 return mPatches.at( index );
1033}
1034
1036{
1038 if ( !otherPolySurface )
1039 return -1;
1040
1041 const int nPatches1 = mPatches.size();
1042 const int nPatches2 = otherPolySurface->mPatches.size();
1043 if ( nPatches1 < nPatches2 )
1044 {
1045 return -1;
1046 }
1047 if ( nPatches1 > nPatches2 )
1048 {
1049 return 1;
1050 }
1051
1052 for ( int i = 0; i < nPatches1; i++ )
1053 {
1054 const int polygonComp = mPatches.at( i )->compareTo( otherPolySurface->mPatches.at( i ) );
1055 if ( polygonComp != 0 )
1056 {
1057 return polygonComp;
1058 }
1059 }
1060
1061 return 0;
1062}
1063
1065{
1066 if ( flags == 0 && mHasCachedValidity )
1067 {
1068 // use cached validity results
1069 error = mValidityFailureReason;
1070 return error.isEmpty();
1071 }
1072
1073 if ( isEmpty() )
1074 return true;
1075
1076 error.clear();
1077
1078 // GEOS does not handle PolyhedralSurface, check the polygons one by one
1079 for ( int i = 0; i < mPatches.size(); ++i )
1080 {
1081 const QgsGeos geos( mPatches.at( i ), 0, Qgis::GeosCreationFlags() );
1082 const bool valid = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr );
1083 if ( !valid )
1084 {
1085 error = u"Polygon %1 is invalid: %2"_s.arg( QString::number( i ), error );
1086 break;
1087 }
1088 }
1089
1090 //TODO: Also check that the polyhedral surface is connected
1091 // For example, see SFCGAL implementation:
1092 // https://gitlab.com/sfcgal/SFCGAL/-/blob/19e3ff0c9057542a0e271edfee873d5f8b220871/src/algorithm/isValid.cpp#L469
1093
1094 const bool valid = error.isEmpty();
1095 if ( flags == 0 )
1096 {
1097 mValidityFailureReason = !valid ? error : QString();
1098 mHasCachedValidity = true;
1099 }
1100
1101 return valid;
1102}
@ AllowSelfTouchingHoles
Indicates that self-touching holes are permitted. OGC validity states that self-touching holes are NO...
Definition qgis.h:2192
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2196
QFlags< GeosCreationFlag > GeosCreationFlags
Geos geometry creation behavior flags.
Definition qgis.h:2291
@ Polygon
Polygons.
Definition qgis.h:382
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ Polygon
Polygon.
Definition qgis.h:298
@ PolyhedralSurface
PolyhedralSurface.
Definition qgis.h:309
TransformDirection
Indicates the direction (forward or inverse) of a transform.
Definition qgis.h:2831
An abstract base class for classes which transform geometries by transforming input points to output ...
virtual QgsAbstractGeometry * snappedToGrid(double hSpacing, double vSpacing, double dSpacing=0, double mSpacing=0, bool removeRedundantPoints=false) const =0
Makes a new geometry with all the points or vertices snapped to the closest point of the grid.
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
bool isMeasure() const
Returns true if the geometry contains m values.
QFlags< WkbFlag > WkbFlags
virtual bool hasVertex(QgsVertexId position) const =0
Returns true if the geometry contains a vertex matching the given position.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
AxisOrder
Axis order for GML generation.
QString wktTypeStr() const
Returns the WKT type string of the geometry.
QgsAbstractGeometry & operator=(const QgsAbstractGeometry &geom)
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
void setZMTypeFromSubGeometry(const QgsAbstractGeometry *subggeom, Qgis::WkbType baseGeomType)
Updates the geometry type based on whether sub geometries contain z or m values.
virtual bool boundingBoxIntersects(const QgsRectangle &rectangle) const
Returns true if the bounding box of this geometry intersects with a rectangle.
QgsAbstractGeometry()=default
QgsGeometryConstPartIterator parts() const
Returns Java-style iterator for traversal of parts of the geometry.
static endian_t endian()
Returns whether this machine uses big or little endian.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
void combineWith(const QgsBox3D &box)
Expands the bbox so that it covers both the original rectangle and the given rectangle.
Definition qgsbox3d.cpp:211
A const WKB pointer.
Definition qgswkbptr.h:211
Qgis::WkbType readHeader() const
readHeader
Definition qgswkbptr.cpp:60
Handles coordinate transforms between two coordinate systems.
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
double vertexAngle(QgsVertexId vertex) const override
Returns approximate rotation angle for a vertex.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
void adjacentVertices(QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex) const override
Returns the vertices adjacent to a specified vertex within a geometry.
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const override
Returns next vertex id and coordinates.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
bool dropMValue() override
Drops any measure values which exist in the geometry.
bool deleteVertices(const QSet< QgsVertexId > &positions) override
Deletes vertices within the geometry.
double segmentLength(QgsVertexId startVertex) const override
Returns the length of the segment of the geometry which begins at startVertex.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
void normalize() final
Reorganizes the geometry into a normalized form (or "canonical" form).
Definition qgscurve.cpp:211
int vertexCount(int part=0, int ring=0) const override
Returns the number of vertices of which this geometry is built.
Definition qgscurve.cpp:180
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
bool isEmpty() const override
Returns true if the geometry is empty.
int numGeometries() const
Returns the number of geometries within the collection.
static QStringList wktGetChildBlocks(const QString &wkt, const QString &defaultType=QString())
Parses a WKT string and returns of list of blocks contained in the WKT.
static QPair< Qgis::WkbType, QString > wktReadBlock(const QString &wkt)
Parses a WKT block of the format "TYPE( contents )" and returns a pair of geometry type to contents (...
static double closestSegmentFromComponents(T &container, ComponentType ctype, const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon)
Does vector analysis using the GEOS library and handles import, export, and exception handling.
Definition qgsgeos.h:175
Line string geometry type, with support for z-dimension and m-values.
Multi line string geometry collection.
Multi polygon geometry collection.
QgsPolygon * polygonN(int index)
Returns the polygon with the specified index.
Multi surface geometry collection.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:37
QgsPolygon * clone() const override
Clones the geometry by performing a deep copy.
QgsPolyhedralSurface * clone() const override
Clones the geometry by performing a deep copy.
int nCoordinates() const override
Returns the number of nodes contained in the geometry.
QVector< QgsPolygon * > mPatches
double area() const override
Returns the planar, 2-dimensional area of the geometry.
QgsAbstractGeometry * boundary() const override
Returns the closure of the combinatorial boundary of the geometry (ie the topological boundary of the...
bool hasVertex(QgsVertexId position) const override
Returns true if the geometry contains a vertex matching the given position.
QByteArray asWkb(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns a WKB representation of the geometry.
QgsAbstractGeometry * segmentize(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a geometry without curves.
QString geometryType() const override
Returns a unique string representing the geometry type.
int wkbSize(QgsAbstractGeometry::WkbFlags flags=QgsAbstractGeometry::WkbFlags()) const override
Returns the length of the QByteArray returned by asWkb().
QString asWkt(int precision=17) const override
Returns a WKT representation of the geometry.
QgsAbstractGeometry * childGeometry(int index) const override
Returns pointer to child geometry (for geometries with child geometries - i.e.
QDomElement asGml2(QDomDocument &doc, int precision=17, const QString &ns="gml", QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const override
Returns a GML2 representation of the geometry.
double area3D() const override
Returns the 3-dimensional surface area of the geometry.
bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const override
Returns next vertex id and coordinates.
bool moveVertex(QgsVertexId position, const QgsPoint &newPos) override
Moves a vertex within the geometry.
int dimension() const final
Returns the inherent dimension of the geometry.
json asJsonObject(int precision=17) const override
Returns a json object representation of the geometry.
bool removeDuplicateNodes(double epsilon=4 *std::numeric_limits< double >::epsilon(), bool useZValues=false) override
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a degenerat...
void normalize() override
Reorganizes the geometry into a normalized form (or "canonical" form).
bool removePatch(int patchIndex)
Removes a patch from the polyhedral surface.
bool insertVertex(QgsVertexId position, const QgsPoint &vertex) override
Inserts a vertex into the geometry.
bool fromWkb(QgsConstWkbPtr &wkb) override
Sets the geometry from a WKB string.
void draw(QPainter &p) const override
Draws the geometry using the specified QPainter.
QDomElement asGml3(QDomDocument &doc, int precision=17, const QString &ns="gml", QgsAbstractGeometry::AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const override
Returns a GML3 representation of the geometry.
bool dropMValue() override
Drops any measure values which exist in the geometry.
bool dropZValue() override
Drops any z-dimensions which exist in the geometry.
int compareToSameClass(const QgsAbstractGeometry *other) const override
Compares to an other geometry of the same class, and returns a integer for sorting of the two geometr...
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
QgsPolyhedralSurface * createEmptyWithSameType() const override
Creates a new geometry with the same class and same WKB type as the original and transfers ownership.
void adjacentVertices(QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex) const override
Returns the vertices adjacent to a specified vertex within a geometry.
bool isValid(QString &error, Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const override
Checks validity of the geometry, and returns true if the geometry is valid.
QgsPolyhedralSurface * simplifyByDistance(double tolerance) const override
Simplifies the geometry by applying the Douglas Peucker simplification by distance algorithm.
bool deleteVertex(QgsVertexId position) override
Deletes a vertex within the geometry.
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
QPainterPath asQPainterPath() const override
Returns the geometry represented as a QPainterPath.
virtual void addPatch(QgsPolygon *patch)
Adds a patch to the geometry, transferring ownership to the polyhedral surface.
QgsBox3D calculateBoundingBox3D() const override
Calculates the minimal 3D bounding box for the geometry.
bool fromWkt(const QString &wkt) override
Sets the geometry from a WKT string.
bool addMValue(double mValue=0) override
Adds a measure to the geometry, initialized to a preset value.
void filterVertices(const std::function< bool(const QgsPoint &) > &filter) override
Filters the vertices from the geometry in place, removing any which do not return true for the filter...
bool hasCurvedSegments() const final
Returns true if the geometry contains curved segments.
QgsPoint vertexAt(QgsVertexId id) const override
Returns the point corresponding to a specified vertex id.
double vertexAngle(QgsVertexId vertex) const override
Returns approximate rotation angle for a vertex.
virtual void setPatches(const QVector< QgsPolygon * > &patches)
Sets all patches, transferring ownership to the polyhedral surface.
void clear() override
Clears the geometry, ie reset it to a null geometry.
bool isEmpty() const override
Returns true if the geometry is empty.
void transformVertices(const std::function< QgsPoint(const QgsPoint &) > &transform) override
Transforms the vertices from the geometry in place, applying the transform function to every vertex.
double segmentLength(QgsVertexId startVertex) const override
Returns the length of the segment of the geometry which begins at startVertex.
QgsPolyhedralSurface * snappedToGrid(double hSpacing, double vSpacing, double dSpacing=0, double mSpacing=0, bool removeRedundantPoints=false) const override
Makes a new geometry with all the points or vertices snapped to the closest point of the grid.
int vertexCount(int part=0, int ring=0) const override
Returns the number of vertices of which this geometry is built.
double perimeter() const override
Returns the planar, 2-dimensional perimeter of the geometry.
QgsMultiSurface * toCurveType() const override
Returns the geometry converted to the more generic curve type.
QString asKml(int precision=17) const override
Returns a KML representation of the geometry.
int childCount() const override
Returns number of child geometries (for geometries with child geometries) or child points (for geomet...
int partCount() const override
Returns count of parts contained in the geometry.
int vertexNumberFromVertexId(QgsVertexId id) const override
Returns the vertex number corresponding to a vertex id.
void swapXy() override
Swaps the x and y coordinates from the geometry.
QgsPolyhedralSurface & operator=(const QgsPolyhedralSurface &p)
double closestSegment(const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf=nullptr, double epsilon=4 *std::numeric_limits< double >::epsilon()) const override
Searches for the closest segment of the geometry to a given point.
bool deleteVertices(const QSet< QgsVertexId > &positions) override
Deletes vertices within the geometry.
int ringCount(int part=0) const override
Returns the number of rings of which this geometry is built.
bool boundingBoxIntersects(const QgsBox3D &box3d) const override
Returns true if the bounding box of this geometry intersects with a box3d.
QgsMultiPolygon * toMultiPolygon() const
Converts a polyhedral surface to a multipolygon.
QgsCoordinateSequence coordinateSequence() const override
Retrieves the sequence of geometries, rings and nodes.
Surface geometry type.
Definition qgssurface.h:34
QgsBox3D mBoundingBox
Definition qgssurface.h:99
void clearCache() const override
Clears any cached parameters associated with the geometry, e.g., bounding boxes.
QString mValidityFailureReason
Definition qgssurface.h:101
bool mHasCachedValidity
Definition qgssurface.h:100
WKB pointer handler.
Definition qgswkbptr.h:47
static Qgis::WkbType dropM(Qgis::WkbType type)
Drops the m dimension (if present) for a WKB type and returns the new type.
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Qgis::WkbType dropZ(Qgis::WkbType type)
Drops the z dimension (if present) for a WKB type and returns the new type.
static Qgis::WkbType addM(Qgis::WkbType type)
Adds the m dimension to a WKB type and returns the new type.
static Qgis::WkbType addZ(Qgis::WkbType type)
Adds the z dimension to a WKB type and returns the new type.
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
Contains geos related utilities and functions.
Definition qgsgeos.h:112
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsRingSequence > QgsCoordinateSequence
#define QgsDebugError(str)
Definition qgslogger.h:59
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:35
int vertex
Vertex number.
int part
Part number.
Definition qgsvertexid.h:94
int ring
Ring number.
Definition qgsvertexid.h:97