QGIS API Documentation 4.3.0-Master (16eddb2670e)
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
312{
313 // JSON-FG profile is not supported yet for PolyhedralSurface geometry
314 Q_UNUSED( profile );
315 // GeoJSON format does not support PolyhedralSurface geometry
316 // Return a multipolygon instead;
317 std::unique_ptr<QgsMultiPolygon> multiPolygon( toMultiPolygon() );
318 return multiPolygon->asJsonObject( precision );
319}
320
321QString QgsPolyhedralSurface::asKml( int ) const
322{
323 QgsDebugError( u"kml format does not support PolyhedralSurface geometry"_s );
324 return QString( "" );
325}
326
328{
329 for ( QgsPolygon *patch : mPatches )
330 {
331 QgsCurve *exteriorRing = patch->exteriorRing();
332 if ( !exteriorRing )
333 continue;
334
335 exteriorRing->normalize();
336
337 if ( patch->numInteriorRings() > 0 )
338 {
339 QVector<QgsCurve *> interiorRings;
340 for ( int i = 0, n = patch->numInteriorRings(); i < n; ++i )
341 {
342 interiorRings.push_back( patch->interiorRing( i )->clone() );
343 }
344
345 // sort rings
346 std::sort( interiorRings.begin(), interiorRings.end(), []( const QgsCurve *a, const QgsCurve *b ) { return a->compareTo( b ) > 0; } );
347
348 patch->removeInteriorRings();
349 for ( QgsCurve *curve : interiorRings )
350 patch->addInteriorRing( curve );
351 }
352 }
353}
354
356{
357 // sum area of patches
358 double area = 0.0;
359 for ( const QgsPolygon *patch : mPatches )
360 {
361 area += patch->area();
362 }
363
364 return area;
365}
366
368{
369 // sum area 3D of patches
370 double area = 0.0;
371 for ( const QgsPolygon *patch : mPatches )
372 {
373 area += patch->area3D();
374 }
375
376 return area;
377}
378
380{
381 // sum perimeter of patches
382 double perimeter = 0.0;
383 for ( const QgsPolygon *patch : mPatches )
384 {
385 perimeter += patch->perimeter();
386 }
387
388 return perimeter;
389}
390
392{
393 auto multiLine = std::make_unique<QgsMultiLineString>();
394 multiLine->reserve( mPatches.size() );
395 for ( QgsPolygon *polygon : mPatches )
396 {
397 std::unique_ptr<QgsAbstractGeometry> polygonBoundary( polygon->boundary() );
398 if ( QgsLineString *lineStringBoundary = qgsgeometry_cast< QgsLineString * >( polygonBoundary.get() ) )
399 {
400 multiLine->addGeometry( lineStringBoundary->clone() );
401 }
402 else if ( QgsMultiLineString *multiLineStringBoundary = qgsgeometry_cast< QgsMultiLineString * >( polygonBoundary.get() ) )
403 {
404 for ( int j = 0; j < multiLineStringBoundary->numGeometries(); ++j )
405 {
406 multiLine->addGeometry( multiLineStringBoundary->geometryN( j )->clone() );
407 }
408 }
409 }
410
411 if ( multiLine->numGeometries() == 0 )
412 {
413 return nullptr;
414 }
415 return multiLine.release();
416}
417
418QgsPolyhedralSurface *QgsPolyhedralSurface::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing, bool removeRedundantPoints ) const
419{
420 std::unique_ptr< QgsPolyhedralSurface > surface( createEmptyWithSameType() );
421
422 for ( QgsPolygon *patch : mPatches )
423 {
424 // exterior ring
425 std::unique_ptr<QgsCurve> exteriorRing( static_cast< QgsCurve *>( patch->exteriorRing()->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) );
426 if ( !exteriorRing )
427 {
428 return nullptr;
429 }
430
431 auto gridifiedPatch = std::make_unique<QgsPolygon>();
432 gridifiedPatch->setExteriorRing( exteriorRing.release() );
433
434 //interior rings
435 for ( int i = 0, n = patch->numInteriorRings(); i < n; ++i )
436 {
437 QgsCurve *interiorRing = patch->interiorRing( i );
438 if ( !interiorRing )
439 continue;
440
441 std::unique_ptr<QgsCurve> gridifiedInterior( static_cast< QgsCurve * >( interiorRing->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, removeRedundantPoints ) ) );
442 if ( gridifiedInterior )
443 gridifiedPatch->addInteriorRing( gridifiedInterior.release() );
444 }
445
446 surface->addPatch( gridifiedPatch.release() );
447 }
448
449 return surface.release();
450}
451
453{
454 if ( isEmpty() )
455 return nullptr;
456
457 auto simplifiedGeom = std::make_unique< QgsPolyhedralSurface >();
458 for ( QgsPolygon *polygon : mPatches )
459 {
460 std::unique_ptr<QgsCurvePolygon> polygonSimplified( polygon->simplifyByDistance( tolerance ) );
461 simplifiedGeom->addPatch( polygonSimplified->surfaceToPolygon() );
462 }
463 return simplifiedGeom.release();
464}
465
466bool QgsPolyhedralSurface::removeDuplicateNodes( double epsilon, bool useZValues )
467{
468 bool result = false;
469
470 for ( QgsPolygon *patch : std::as_const( mPatches ) )
471 {
472 if ( patch->removeDuplicateNodes( epsilon, useZValues ) )
473 {
474 result = true;
475 }
476 }
477 return result;
478}
479
481{
482 // if we already have the bounding box calculated, then this check is trivial!
483 if ( !mBoundingBox.isNull() )
484 {
485 return mBoundingBox.intersects( box3d );
486 }
487
488 // loop through each patch and test the bounding box intersection.
489 // This gives us a chance to use optimisations which may be present on the individual
490 // ring geometry subclasses, and at worst it will cause a calculation of the bounding box
491 // of each individual patch geometry which we would have to do anyway... (and these
492 // bounding boxes are cached, so would be reused without additional expense)
493 for ( const QgsPolygon *patch : mPatches )
494 {
495 if ( patch->boundingBoxIntersects( box3d ) )
496 return true;
497 }
498
499 // even if we don't intersect the bounding box of any rings, we may still intersect the
500 // bounding box of the overall polygon (we are considering worst case scenario here and
501 // the polygon is invalid, with rings outside the exterior ring!)
502 // so here we fall back to the non-optimised base class check which has to first calculate
503 // the overall bounding box of the polygon..
504 return QgsSurface::boundingBoxIntersects( box3d );
505}
506
507void QgsPolyhedralSurface::setPatches( const QVector<QgsPolygon *> &patches )
508{
509 qDeleteAll( mPatches );
510 mPatches.clear();
511
512 for ( QgsPolygon *patch : patches )
513 {
514 addPatch( patch );
515 }
516
517 clearCache();
518}
519
521{
522 if ( !patch )
523 return;
524
525 if ( mPatches.empty() )
526 {
528 }
529
530 // Ensure dimensionality of patch matches polyhedral surface
531 if ( !is3D() )
532 patch->dropZValue();
533 else if ( !patch->is3D() )
534 patch->addZValue();
535
536 if ( !isMeasure() )
537 patch->dropMValue();
538 else if ( !patch->isMeasure() )
539 patch->addMValue();
540
541 mPatches.append( patch );
542 clearCache();
543}
544
546{
547 if ( patchIndex < 0 || patchIndex >= mPatches.size() )
548 {
549 return false;
550 }
551
552 delete mPatches.takeAt( patchIndex );
553 clearCache();
554 return true;
555}
556
558{
559 QPainterPath painterPath;
560 for ( const QgsPolygon *patch : mPatches )
561 {
562 QPainterPath patchPath = patch->asQPainterPath();
563 patchPath.closeSubpath();
564 painterPath.addPath( patchPath );
565 }
566
567 return painterPath;
568}
569
570void QgsPolyhedralSurface::draw( QPainter &p ) const
571{
572 if ( mPatches.empty() )
573 return;
574
575 for ( const QgsPolygon *patch : mPatches )
576 {
577 patch->draw( p );
578 }
579}
580
582{
583 for ( QgsPolygon *patch : std::as_const( mPatches ) )
584 {
585 patch->transform( ct, d, transformZ );
586 }
587 clearCache();
588}
589
590void QgsPolyhedralSurface::transform( const QTransform &t, double zTranslate, double zScale, double mTranslate, double mScale )
591{
592 for ( QgsPolygon *patch : std::as_const( mPatches ) )
593 {
594 patch->transform( t, zTranslate, zScale, mTranslate, mScale );
595 }
596 clearCache();
597}
598
600{
601 QgsCoordinateSequence sequence;
602 for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
603 {
604 QgsCoordinateSequence polyCoords = polygon->coordinateSequence();
605 QgsCoordinateSequence::const_iterator cIt = polyCoords.constBegin();
606 for ( ; cIt != polyCoords.constEnd(); ++cIt )
607 {
608 sequence.push_back( *cIt );
609 }
610 }
611
612 return sequence;
613}
614
616{
617 int count = 0;
618 for ( const QgsPolygon *patch : mPatches )
619 {
620 count += patch->nCoordinates();
621 }
622 return count;
623}
624
626{
627 if ( id.part < 0 || id.part >= partCount() )
628 return -1;
629
630 int number = 0;
631 for ( int i = 0; i < mPatches.count(); ++i )
632 {
633 if ( id.part == i )
634 {
635 int partNumber = mPatches.at( i )->vertexNumberFromVertexId( QgsVertexId( 0, id.ring, id.vertex ) );
636 if ( partNumber == -1 )
637 {
638 return -1;
639 }
640
641 return number + partNumber;
642 }
643 else
644 {
645 number += mPatches.at( i )->nCoordinates();
646 }
647 }
648
649 return -1; // should not happen
650}
651
653{
654 return mPatches.isEmpty();
655}
656
657double QgsPolyhedralSurface::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const
658{
659 QVector<QgsPolygon *> segmentList = mPatches;
660 return QgsGeometryUtils::closestSegmentFromComponents( segmentList, QgsGeometryUtils::Part, pt, segmentPt, vertexAfter, leftOf, epsilon );
661}
662
664{
665 if ( vId.part < 0 )
666 {
667 vId.part = 0;
668 vId.ring = -1;
669 vId.vertex = -1;
670 }
671
672 if ( isEmpty() || vId.part >= partCount() )
673 {
674 return false;
675 }
676
677 QgsPolygon *patch = mPatches[vId.part];
678 if ( patch->nextVertex( vId, vertex ) )
679 {
680 return true;
681 }
682
683 ++vId.part;
684 vId.ring = 0;
685 vId.vertex = -1;
686 if ( vId.part >= partCount() )
687 {
688 return false;
689 }
690 patch = mPatches[vId.part];
691 return patch->nextVertex( vId, vertex );
692}
693
695{
696 if ( vertex.part < 0 || vertex.part >= partCount() )
697 {
698 previousVertex = QgsVertexId();
700 return;
701 }
702
703 QgsPolygon *patch = mPatches[vertex.ring];
704 patch->adjacentVertices( QgsVertexId( 0, 0, vertex.vertex ), previousVertex, nextVertex );
705 return;
706}
707
709{
710 if ( vId.part < 0 || vId.part >= partCount() )
711 {
712 return false;
713 }
714
715 QgsPolygon *patch = mPatches.at( vId.part );
716 bool success = patch->insertVertex( QgsVertexId( 0, vId.ring, vId.vertex ), vertex );
717 if ( success )
718 {
719 clearCache();
720 }
721
722 return success;
723}
724
726{
727 if ( vId.part < 0 || vId.part >= partCount() )
728 {
729 return false;
730 }
731
732 QgsPolygon *patch = mPatches.at( vId.part );
733 bool success = patch->moveVertex( QgsVertexId( 0, vId.ring, vId.vertex ), newPos );
734 if ( success )
735 {
736 clearCache();
737 }
738
739 return success;
740}
741
742bool QgsPolyhedralSurface::deleteVertices( const QSet<QgsVertexId> &positions )
743{
744 QMap<int, QSet<QgsVertexId>> partVertices;
745 for ( QgsVertexId pos : positions )
746 {
747 if ( !hasVertex( pos ) )
748 {
749 return false;
750 }
751
752 partVertices[pos.part].insert( QgsVertexId( 0, pos.ring, pos.vertex ) );
753 }
754
755 QMapIterator<int, QSet<QgsVertexId>> partVerticesIt( partVertices );
756 partVerticesIt.toBack();
757 while ( partVerticesIt.hasPrevious() )
758 {
759 partVerticesIt.previous();
760
761 int part = partVerticesIt.key();
762 QSet<QgsVertexId> vertexMap = partVerticesIt.value();
763 QgsPolygon *patch = mPatches.at( part );
764
765 if ( !patch->deleteVertices( vertexMap ) )
766 {
767 Q_ASSERT( false );
768 return false;
769 }
770
771 if ( !patch->exteriorRing() )
772 {
773 delete mPatches.takeAt( part );
774 }
775 }
776
777 clearCache();
778 return true;
779}
780
782{
783 size_t parts = partCount();
784 if ( id.part < 0 || static_cast<size_t>( id.part ) >= parts )
785 return false;
786
787 QgsAbstractGeometry *geom = mPatches.at( id.part );
788 if ( !geom )
789 return false;
790
791 return geom->hasVertex( QgsVertexId( 0, id.ring, id.vertex ) );
792}
793
795{
796 return false;
797}
798
800{
801 // This is only used by curves
802 Q_UNUSED( tolerance )
803 Q_UNUSED( toleranceType )
804 return clone();
805}
806
808{
809 if ( vertex.part < 0 || vertex.part >= partCount() )
810 {
811 return 0.0;
812 }
813
814 QgsPolygon *patch = mPatches[vertex.part];
815 return patch->vertexAngle( QgsVertexId( 0, vertex.ring, vertex.vertex ) );
816}
817
818int QgsPolyhedralSurface::vertexCount( int part, int ring ) const
819{
820 if ( part < 0 || part >= partCount() )
821 {
822 return 0;
823 }
824
825 QgsPolygon *patchPolygon = mPatches[part];
826 QgsCurve *ringCurve = ring == 0 ? patchPolygon->exteriorRing() : patchPolygon->interiorRing( ring - 1 );
827 if ( ringCurve )
828 {
829 return ringCurve->vertexCount();
830 }
831
832 return 0;
833}
834
836{
837 if ( part < 0 || part >= partCount() )
838 return 0;
839
840 return mPatches[part]->ringCount();
841}
842
844{
845 return mPatches.size();
846}
847
849{
850 if ( id.part < 0 || id.part >= partCount() )
851 return QgsPoint();
852
853 return mPatches[id.part]->vertexAt( id );
854}
855
857{
858 if ( startVertex.part < 0 || startVertex.part >= partCount() )
859 {
860 return 0.0;
861 }
862
863 const QgsPolygon *patch = mPatches[startVertex.part];
864 return patch->segmentLength( QgsVertexId( 0, startVertex.ring, startVertex.vertex ) );
865}
866
868{
870 {
871 return false;
872 }
873
875
876 for ( QgsPolygon *patch : std::as_const( mPatches ) )
877 {
878 patch->addZValue( zValue );
879 }
880 clearCache();
881 return true;
882}
883
885{
887 {
888 return false;
889 }
890
892
893 for ( QgsPolygon *patch : std::as_const( mPatches ) )
894 {
895 patch->addMValue( mValue );
896 }
897 clearCache();
898 return true;
899}
900
902{
903 if ( !is3D() )
904 {
905 return false;
906 }
907
909 for ( QgsPolygon *patch : std::as_const( mPatches ) )
910 {
911 patch->dropZValue();
912 }
913 clearCache();
914 return true;
915}
916
918{
919 if ( !isMeasure() )
920 {
921 return false;
922 }
923
925 for ( QgsPolygon *patch : std::as_const( mPatches ) )
926 {
927 patch->dropMValue();
928 }
929 clearCache();
930 return true;
931}
932
934{
935 for ( QgsPolygon *patch : std::as_const( mPatches ) )
936 {
937 patch->swapXy();
938 }
939 clearCache();
940}
941
943{
944 auto multiSurface = std::make_unique< QgsMultiSurface >();
945 multiSurface->reserve( mPatches.size() );
946 for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
947 {
948 multiSurface->addGeometry( polygon->clone() );
949 }
950 return multiSurface.release();
951}
952
954{
955 if ( !transformer )
956 return false;
957
958 bool res = true;
959
960 for ( QgsPolygon *patch : std::as_const( mPatches ) )
961 {
962 res = patch->transform( transformer );
963 if ( feedback && feedback->isCanceled() )
964 res = false;
965
966 if ( !res )
967 break;
968 }
969
970 clearCache();
971 return res;
972}
973
975{
976 auto multiPolygon = std::make_unique< QgsMultiPolygon >();
977 multiPolygon->reserve( mPatches.size() );
978 for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
979 {
980 multiPolygon->addGeometry( polygon->clone() );
981 }
982 return multiPolygon.release();
983}
984
985void QgsPolyhedralSurface::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
986{
987 for ( QgsPolygon *patch : std::as_const( mPatches ) )
988 {
989 patch->filterVertices( filter );
990 }
991
992 clearCache();
993}
994
996{
997 for ( QgsPolygon *patch : std::as_const( mPatches ) )
998 {
999 patch->transformVertices( transform );
1000 }
1001
1002 clearCache();
1003}
1004
1006{
1007 return mPatches.count();
1008}
1009
1011{
1012 return mPatches.at( index );
1013}
1014
1016{
1018 if ( !otherPolySurface )
1019 return -1;
1020
1021 const int nPatches1 = mPatches.size();
1022 const int nPatches2 = otherPolySurface->mPatches.size();
1023 if ( nPatches1 < nPatches2 )
1024 {
1025 return -1;
1026 }
1027 if ( nPatches1 > nPatches2 )
1028 {
1029 return 1;
1030 }
1031
1032 for ( int i = 0; i < nPatches1; i++ )
1033 {
1034 const int polygonComp = mPatches.at( i )->compareTo( otherPolySurface->mPatches.at( i ) );
1035 if ( polygonComp != 0 )
1036 {
1037 return polygonComp;
1038 }
1039 }
1040
1041 return 0;
1042}
1043
1045{
1046 if ( flags == 0 && mHasCachedValidity )
1047 {
1048 // use cached validity results
1049 error = mValidityFailureReason;
1050 return error.isEmpty();
1051 }
1052
1053 if ( isEmpty() )
1054 return true;
1055
1056 error.clear();
1057
1058 // GEOS does not handle PolyhedralSurface, check the polygons one by one
1059 for ( int i = 0; i < mPatches.size(); ++i )
1060 {
1061 const QgsGeos geos( mPatches.at( i ), 0, Qgis::GeosCreationFlags() );
1062 const bool valid = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr );
1063 if ( !valid )
1064 {
1065 error = u"Polygon %1 is invalid: %2"_s.arg( QString::number( i ), error );
1066 break;
1067 }
1068 }
1069
1070 //TODO: Also check that the polyhedral surface is connected
1071 // For example, see SFCGAL implementation:
1072 // https://gitlab.com/sfcgal/SFCGAL/-/blob/19e3ff0c9057542a0e271edfee873d5f8b220871/src/algorithm/isValid.cpp#L469
1073
1074 const bool valid = error.isEmpty();
1075 if ( flags == 0 )
1076 {
1077 mValidityFailureReason = !valid ? error : QString();
1078 mHasCachedValidity = true;
1079 }
1080
1081 return valid;
1082}
@ AllowSelfTouchingHoles
Indicates that self-touching holes are permitted. OGC validity states that self-touching holes are NO...
Definition qgis.h:2242
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2246
QFlags< GeosCreationFlag > GeosCreationFlags
Geos geometry creation behavior flags.
Definition qgis.h:2342
GeoJsonProfile
GeoJson export Profile according to OGC Features and Geometries JSON - Part 1: Core https://docs....
Definition qgis.h:5134
@ Polygon
Polygons.
Definition qgis.h:402
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:314
@ Polygon
Polygon.
Definition qgis.h:318
@ PolyhedralSurface
PolyhedralSurface.
Definition qgis.h:329
TransformDirection
Indicates the direction (forward or inverse) of a transform.
Definition qgis.h:2882
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 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:192
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.
json asJsonObject(int precision=17, Qgis::GeoJsonProfile profile=Qgis::GeoJsonProfile::Legacy) const override
Returns a json object representation of the geometry with the given precision and profile.
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.
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.
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:129
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsRingSequence > QgsCoordinateSequence
#define QgsDebugError(str)
Definition qgslogger.h:71
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:96
int ring
Ring number.
Definition qgsvertexid.h:99