QGIS API Documentation 4.3.0-Master (bf28115e945)
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
743{
744 if ( vId.part < 0 || vId.part >= partCount() )
745 {
746 return false;
747 }
748
749 QgsPolygon *patch = mPatches.at( vId.part );
750 bool success = patch->deleteVertex( QgsVertexId( 0, vId.ring, vId.vertex ) );
751 if ( success )
752 {
753 // if the patch has lost its exterior ring, remove it
754 if ( !patch->exteriorRing() )
755 {
756 delete mPatches.takeAt( vId.part );
757 }
758 clearCache();
759 }
760
761 return success;
762}
763
764bool QgsPolyhedralSurface::deleteVertices( const QSet<QgsVertexId> &positions )
765{
766 QMap<int, QSet<QgsVertexId>> partVertices;
767 for ( QgsVertexId pos : positions )
768 {
769 if ( !hasVertex( pos ) )
770 {
771 return false;
772 }
773
774 partVertices[pos.part].insert( QgsVertexId( 0, pos.ring, pos.vertex ) );
775 }
776
777 QMapIterator<int, QSet<QgsVertexId>> partVerticesIt( partVertices );
778 partVerticesIt.toBack();
779 while ( partVerticesIt.hasPrevious() )
780 {
781 partVerticesIt.previous();
782
783 int part = partVerticesIt.key();
784 QSet<QgsVertexId> vertexMap = partVerticesIt.value();
785 QgsPolygon *patch = mPatches.at( part );
786
787 if ( !patch->deleteVertices( vertexMap ) )
788 {
789 Q_ASSERT( false );
790 return false;
791 }
792
793 if ( !patch->exteriorRing() )
794 {
795 delete mPatches.takeAt( part );
796 }
797 }
798
799 clearCache();
800 return true;
801}
802
804{
805 size_t parts = partCount();
806 if ( id.part < 0 || static_cast<size_t>( id.part ) >= parts )
807 return false;
808
809 QgsAbstractGeometry *geom = mPatches.at( id.part );
810 if ( !geom )
811 return false;
812
813 return geom->hasVertex( QgsVertexId( 0, id.ring, id.vertex ) );
814}
815
817{
818 return false;
819}
820
822{
823 // This is only used by curves
824 Q_UNUSED( tolerance )
825 Q_UNUSED( toleranceType )
826 return clone();
827}
828
830{
831 if ( vertex.part < 0 || vertex.part >= partCount() )
832 {
833 return 0.0;
834 }
835
836 QgsPolygon *patch = mPatches[vertex.part];
837 return patch->vertexAngle( QgsVertexId( 0, vertex.ring, vertex.vertex ) );
838}
839
840int QgsPolyhedralSurface::vertexCount( int part, int ring ) const
841{
842 if ( part < 0 || part >= partCount() )
843 {
844 return 0;
845 }
846
847 QgsPolygon *patchPolygon = mPatches[part];
848 QgsCurve *ringCurve = ring == 0 ? patchPolygon->exteriorRing() : patchPolygon->interiorRing( ring - 1 );
849 if ( ringCurve )
850 {
851 return ringCurve->vertexCount();
852 }
853
854 return 0;
855}
856
858{
859 if ( part < 0 || part >= partCount() )
860 return 0;
861
862 return mPatches[part]->ringCount();
863}
864
866{
867 return mPatches.size();
868}
869
871{
872 if ( id.part < 0 || id.part >= partCount() )
873 return QgsPoint();
874
875 return mPatches[id.part]->vertexAt( id );
876}
877
879{
880 if ( startVertex.part < 0 || startVertex.part >= partCount() )
881 {
882 return 0.0;
883 }
884
885 const QgsPolygon *patch = mPatches[startVertex.part];
886 return patch->segmentLength( QgsVertexId( 0, startVertex.ring, startVertex.vertex ) );
887}
888
890{
892 {
893 return false;
894 }
895
897
898 for ( QgsPolygon *patch : std::as_const( mPatches ) )
899 {
900 patch->addZValue( zValue );
901 }
902 clearCache();
903 return true;
904}
905
907{
909 {
910 return false;
911 }
912
914
915 for ( QgsPolygon *patch : std::as_const( mPatches ) )
916 {
917 patch->addMValue( mValue );
918 }
919 clearCache();
920 return true;
921}
922
924{
925 if ( !is3D() )
926 {
927 return false;
928 }
929
931 for ( QgsPolygon *patch : std::as_const( mPatches ) )
932 {
933 patch->dropZValue();
934 }
935 clearCache();
936 return true;
937}
938
940{
941 if ( !isMeasure() )
942 {
943 return false;
944 }
945
947 for ( QgsPolygon *patch : std::as_const( mPatches ) )
948 {
949 patch->dropMValue();
950 }
951 clearCache();
952 return true;
953}
954
956{
957 for ( QgsPolygon *patch : std::as_const( mPatches ) )
958 {
959 patch->swapXy();
960 }
961 clearCache();
962}
963
965{
966 auto multiSurface = std::make_unique< QgsMultiSurface >();
967 multiSurface->reserve( mPatches.size() );
968 for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
969 {
970 multiSurface->addGeometry( polygon->clone() );
971 }
972 return multiSurface.release();
973}
974
976{
977 if ( !transformer )
978 return false;
979
980 bool res = true;
981
982 for ( QgsPolygon *patch : std::as_const( mPatches ) )
983 {
984 res = patch->transform( transformer );
985 if ( feedback && feedback->isCanceled() )
986 res = false;
987
988 if ( !res )
989 break;
990 }
991
992 clearCache();
993 return res;
994}
995
997{
998 auto multiPolygon = std::make_unique< QgsMultiPolygon >();
999 multiPolygon->reserve( mPatches.size() );
1000 for ( const QgsPolygon *polygon : std::as_const( mPatches ) )
1001 {
1002 multiPolygon->addGeometry( polygon->clone() );
1003 }
1004 return multiPolygon.release();
1005}
1006
1007void QgsPolyhedralSurface::filterVertices( const std::function<bool( const QgsPoint & )> &filter )
1008{
1009 for ( QgsPolygon *patch : std::as_const( mPatches ) )
1010 {
1011 patch->filterVertices( filter );
1012 }
1013
1014 clearCache();
1015}
1016
1018{
1019 for ( QgsPolygon *patch : std::as_const( mPatches ) )
1020 {
1021 patch->transformVertices( transform );
1022 }
1023
1024 clearCache();
1025}
1026
1028{
1029 return mPatches.count();
1030}
1031
1033{
1034 return mPatches.at( index );
1035}
1036
1038{
1040 if ( !otherPolySurface )
1041 return -1;
1042
1043 const int nPatches1 = mPatches.size();
1044 const int nPatches2 = otherPolySurface->mPatches.size();
1045 if ( nPatches1 < nPatches2 )
1046 {
1047 return -1;
1048 }
1049 if ( nPatches1 > nPatches2 )
1050 {
1051 return 1;
1052 }
1053
1054 for ( int i = 0; i < nPatches1; i++ )
1055 {
1056 const int polygonComp = mPatches.at( i )->compareTo( otherPolySurface->mPatches.at( i ) );
1057 if ( polygonComp != 0 )
1058 {
1059 return polygonComp;
1060 }
1061 }
1062
1063 return 0;
1064}
1065
1067{
1068 if ( flags == 0 && mHasCachedValidity )
1069 {
1070 // use cached validity results
1071 error = mValidityFailureReason;
1072 return error.isEmpty();
1073 }
1074
1075 if ( isEmpty() )
1076 return true;
1077
1078 error.clear();
1079
1080 // GEOS does not handle PolyhedralSurface, check the polygons one by one
1081 for ( int i = 0; i < mPatches.size(); ++i )
1082 {
1083 const QgsGeos geos( mPatches.at( i ), 0, Qgis::GeosCreationFlags() );
1084 const bool valid = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr );
1085 if ( !valid )
1086 {
1087 error = u"Polygon %1 is invalid: %2"_s.arg( QString::number( i ), error );
1088 break;
1089 }
1090 }
1091
1092 //TODO: Also check that the polyhedral surface is connected
1093 // For example, see SFCGAL implementation:
1094 // https://gitlab.com/sfcgal/SFCGAL/-/blob/19e3ff0c9057542a0e271edfee873d5f8b220871/src/algorithm/isValid.cpp#L469
1095
1096 const bool valid = error.isEmpty();
1097 if ( flags == 0 )
1098 {
1099 mValidityFailureReason = !valid ? error : QString();
1100 mHasCachedValidity = true;
1101 }
1102
1103 return valid;
1104}
@ AllowSelfTouchingHoles
Indicates that self-touching holes are permitted. OGC validity states that self-touching holes are NO...
Definition qgis.h:2206
QFlags< GeometryValidityFlag > GeometryValidityFlags
Geometry validity flags.
Definition qgis.h:2210
QFlags< GeosCreationFlag > GeosCreationFlags
Geos geometry creation behavior flags.
Definition qgis.h:2305
GeoJsonProfile
GeoJson export Profile according to OGC Features and Geometries JSON - Part 1: Core https://docs....
Definition qgis.h:5045
@ 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:2845
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.
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.
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: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:94
int ring
Ring number.
Definition qgsvertexid.h:97