QGIS API Documentation 4.1.0-Master (60fea48833c)
Loading...
Searching...
No Matches
qgsgeos.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgeos.cpp
3 -------------------------------------------------------------------
4Date : 22 Sept 2014
5Copyright : (C) 2014 by Marco Hugentobler
6email : marco.hugentobler at sourcepole dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsgeos.h"
17
18#include <cstdio>
19#include <limits>
20#include <memory>
21
22#include "qgsabstractgeometry.h"
25#include "qgsgeometryfactory.h"
27#include "qgslinestring.h"
28#include "qgslogger.h"
29#include "qgsmulticurve.h"
30#include "qgsmultilinestring.h"
31#include "qgsmultipoint.h"
32#include "qgsmultipolygon.h"
33#include "qgspolygon.h"
35
36#include <QString>
37
38using namespace Qt::StringLiterals;
39
40#define DEFAULT_QUADRANT_SEGMENTS 8
41
42#define CATCH_GEOS( r ) \
43 catch ( QgsGeosException & ) \
44 { \
45 return r; \
46 }
47
48#define CATCH_GEOS_WITH_ERRMSG( r ) \
49 catch ( QgsGeosException & e ) \
50 { \
51 if ( errorMsg ) \
52 { \
53 *errorMsg = e.what(); \
54 } \
55 return r; \
56 }
57
59
60static void throwQgsGeosException( const char *fmt, ... )
61{
62 va_list ap;
63 char buffer[1024];
64
65 va_start( ap, fmt );
66 vsnprintf( buffer, sizeof buffer, fmt, ap );
67 va_end( ap );
68
69 QString message = QString::fromUtf8( buffer );
70
71#ifdef _MSC_VER
72 // stupid stupid MSVC, *SOMETIMES* raises it's own exception if we throw QgsGeosException, resulting in a crash!
73 // see https://github.com/qgis/QGIS/issues/22709
74 // if you want to test alternative fixes for this, run the testqgsexpression.cpp test suite - that will crash
75 // and burn on the "line_interpolate_point point" test if a QgsGeosException is thrown.
76 // TODO - find a real fix for the underlying issue
77 try
78 {
79 throw QgsGeosException( message );
80 }
81 catch ( ... )
82 {
83 // oops, msvc threw an exception when we tried to throw the exception!
84 // just throw nothing instead (except your mouse at your monitor)
85 }
86#else
87 throw QgsGeosException( message );
88#endif
89}
90
91
92static void printGEOSNotice( const char *fmt, ... )
93{
94#if defined( QGISDEBUG )
95 va_list ap;
96 char buffer[1024];
97
98 va_start( ap, fmt );
99 vsnprintf( buffer, sizeof buffer, fmt, ap );
100 va_end( ap );
101#else
102 Q_UNUSED( fmt )
103#endif
104}
105
106//
107// QgsGeosContext
108//
109
110#if defined( USE_THREAD_LOCAL ) && !defined( Q_OS_WIN )
111thread_local QgsGeosContext QgsGeosContext::sGeosContext;
112#else
113QThreadStorage< QgsGeosContext * > QgsGeosContext::sGeosContext;
114#endif
115
116
118{
119 mContext = GEOS_init_r();
120 GEOSContext_setNoticeHandler_r( mContext, printGEOSNotice );
121 GEOSContext_setErrorHandler_r( mContext, throwQgsGeosException );
122}
123
125{
126 GEOS_finish_r( mContext );
127}
128
129GEOSContextHandle_t QgsGeosContext::get()
130{
131#if defined( USE_THREAD_LOCAL ) && !defined( Q_OS_WIN )
132 return sGeosContext.mContext;
133#else
134 GEOSContextHandle_t gContext = nullptr;
135 if ( sGeosContext.hasLocalData() )
136 {
137 gContext = sGeosContext.localData()->mContext;
138 }
139 else
140 {
141 sGeosContext.setLocalData( new QgsGeosContext() );
142 gContext = sGeosContext.localData()->mContext;
143 }
144 return gContext;
145#endif
146}
147
148//
149// geos
150//
151
153{
154 GEOSGeom_destroy_r( QgsGeosContext::get(), geom );
155}
156
157void geos::GeosDeleter::operator()( const GEOSPreparedGeometry *geom ) const
158{
159 GEOSPreparedGeom_destroy_r( QgsGeosContext::get(), geom );
160}
161
162void geos::GeosDeleter::operator()( GEOSBufferParams *params ) const
163{
164 GEOSBufferParams_destroy_r( QgsGeosContext::get(), params );
165}
166
167void geos::GeosDeleter::operator()( GEOSCoordSequence *sequence ) const
168{
169 GEOSCoordSeq_destroy_r( QgsGeosContext::get(), sequence );
170}
171
172
174
175
176QgsGeos::QgsGeos( const QgsAbstractGeometry *geometry, double precision, Qgis::GeosCreationFlags flags )
177 : QgsGeometryEngine( geometry )
178 , mGeos( nullptr )
179 , mPrecision( precision )
180{
181 cacheGeos( flags );
182}
183
185{
187 GEOSGeom_destroy_r( QgsGeosContext::get(), geos );
188 return g;
189}
190
196
197std::unique_ptr<QgsAbstractGeometry> QgsGeos::makeValid( Qgis::MakeValidMethod method, bool keepCollapsed, QString *errorMsg ) const
198{
199 if ( !mGeos )
200 {
201 return nullptr;
202 }
203
204 GEOSContextHandle_t context = QgsGeosContext::get();
205
206#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 10
207 if ( method != Qgis::MakeValidMethod::Linework )
208 throw QgsNotSupportedException( QObject::tr( "The structured method to make geometries valid requires a QGIS build based on GEOS 3.10 or later" ) );
209
210 if ( keepCollapsed )
211 throw QgsNotSupportedException( QObject::tr( "The keep collapsed option for making geometries valid requires a QGIS build based on GEOS 3.10 or later" ) );
213 try
214 {
215 geos.reset( GEOSMakeValid_r( context, mGeos.get() ) );
216 }
217 CATCH_GEOS_WITH_ERRMSG( nullptr )
218#else
219
220 GEOSMakeValidParams *params = GEOSMakeValidParams_create_r( context );
221 switch ( method )
222 {
224 GEOSMakeValidParams_setMethod_r( context, params, GEOS_MAKE_VALID_LINEWORK );
225 break;
226
228 GEOSMakeValidParams_setMethod_r( context, params, GEOS_MAKE_VALID_STRUCTURE );
229 break;
230 }
231
232 GEOSMakeValidParams_setKeepCollapsed_r( context, params, keepCollapsed ? 1 : 0 );
233
235 try
236 {
237 geos.reset( GEOSMakeValidWithParams_r( context, mGeos.get(), params ) );
238 GEOSMakeValidParams_destroy_r( context, params );
239 }
240 catch ( QgsGeosException &e )
241 {
242 if ( errorMsg )
243 {
244 *errorMsg = e.what();
245 }
246 GEOSMakeValidParams_destroy_r( context, params );
247 return nullptr;
248 }
249#endif
250
251 return fromGeos( geos.get() );
252}
253
254geos::unique_ptr QgsGeos::asGeos( const QgsGeometry &geometry, double precision, Qgis::GeosCreationFlags flags )
255{
256 if ( geometry.isNull() )
257 {
258 return nullptr;
259 }
260
261 return asGeos( geometry.constGet(), precision, flags );
262}
263
265{
266 if ( geometry.isNull() )
267 {
269 }
270 if ( !newPart )
271 {
273 }
274
275 std::unique_ptr< QgsAbstractGeometry > geom = fromGeos( newPart );
276 return QgsGeometryEditUtils::addPart( geometry.get(), std::move( geom ) );
277}
278
280{
281 mGeos.reset();
282 mGeosPrepared.reset();
284}
285
287{
288 if ( mGeosPrepared )
289 {
290 // Already prepared
291 return;
292 }
293 if ( mGeos )
294 {
295 mGeosPrepared.reset( GEOSPrepare_r( QgsGeosContext::get(), mGeos.get() ) );
296 }
297}
298
299void QgsGeos::cacheGeos( Qgis::GeosCreationFlags flags ) const
300{
301 if ( mGeos )
302 {
303 // Already cached
304 return;
305 }
306 if ( !mGeometry )
307 {
308 return;
309 }
310
311 mGeos = asGeos( mGeometry, mPrecision, flags );
312}
313
314QgsAbstractGeometry *QgsGeos::intersection( const QgsAbstractGeometry *geom, QString *errorMsg, const QgsGeometryParameters &parameters ) const
315{
316 return overlay( geom, OverlayIntersection, errorMsg, parameters ).release();
317}
318
319QgsAbstractGeometry *QgsGeos::difference( const QgsAbstractGeometry *geom, QString *errorMsg, const QgsGeometryParameters &parameters ) const
320{
321 return overlay( geom, OverlayDifference, errorMsg, parameters ).release();
322}
323
324std::unique_ptr<QgsAbstractGeometry> QgsGeos::clip( const QgsRectangle &rect, QString *errorMsg ) const
325{
326 if ( !mGeos || rect.isNull() || rect.isEmpty() )
327 {
328 return nullptr;
329 }
330
331 try
332 {
333 geos::unique_ptr opGeom( GEOSClipByRect_r( QgsGeosContext::get(), mGeos.get(), rect.xMinimum(), rect.yMinimum(), rect.xMaximum(), rect.yMaximum() ) );
334 return fromGeos( opGeom.get() );
335 }
336 catch ( QgsGeosException &e )
337 {
338 logError( u"GEOS"_s, e.what() );
339 if ( errorMsg )
340 {
341 *errorMsg = e.what();
342 }
343 return nullptr;
344 }
345}
346
347void QgsGeos::subdivideRecursive( const GEOSGeometry *currentPart, int maxNodes, int depth, QgsGeometryCollection *parts, const QgsRectangle &clipRect, double gridSize ) const
348{
349 GEOSContextHandle_t context = QgsGeosContext::get();
350 int partType = GEOSGeomTypeId_r( context, currentPart );
351 if ( qgsDoubleNear( clipRect.width(), 0.0 ) && qgsDoubleNear( clipRect.height(), 0.0 ) )
352 {
353 if ( partType == GEOS_POINT )
354 {
355 parts->addGeometry( fromGeos( currentPart ).release() );
356 return;
357 }
358 else
359 {
360 return;
361 }
362 }
363
364 if ( partType == GEOS_MULTILINESTRING || partType == GEOS_MULTIPOLYGON || partType == GEOS_GEOMETRYCOLLECTION )
365 {
366 int partCount = GEOSGetNumGeometries_r( context, currentPart );
367 for ( int i = 0; i < partCount; ++i )
368 {
369 subdivideRecursive( GEOSGetGeometryN_r( context, currentPart, i ), maxNodes, depth, parts, clipRect, gridSize );
370 }
371 return;
372 }
373
374 if ( depth > 50 )
375 {
376 parts->addGeometry( fromGeos( currentPart ).release() );
377 return;
378 }
379
380 int vertexCount = GEOSGetNumCoordinates_r( context, currentPart );
381 if ( vertexCount == 0 )
382 {
383 return;
384 }
385 else if ( vertexCount < maxNodes )
386 {
387 parts->addGeometry( fromGeos( currentPart ).release() );
388 return;
389 }
390
391 // chop clipping rect in half by longest side
392 double width = clipRect.width();
393 double height = clipRect.height();
394 QgsRectangle halfClipRect1 = clipRect;
395 QgsRectangle halfClipRect2 = clipRect;
396 if ( width > height )
397 {
398 halfClipRect1.setXMaximum( clipRect.xMinimum() + width / 2.0 );
399 halfClipRect2.setXMinimum( halfClipRect1.xMaximum() );
400 }
401 else
402 {
403 halfClipRect1.setYMaximum( clipRect.yMinimum() + height / 2.0 );
404 halfClipRect2.setYMinimum( halfClipRect1.yMaximum() );
405 }
406
407 if ( height <= 0 )
408 {
409 halfClipRect1.setYMinimum( halfClipRect1.yMinimum() - std::numeric_limits<double>::epsilon() );
410 halfClipRect2.setYMinimum( halfClipRect2.yMinimum() - std::numeric_limits<double>::epsilon() );
411 halfClipRect1.setYMaximum( halfClipRect1.yMaximum() + std::numeric_limits<double>::epsilon() );
412 halfClipRect2.setYMaximum( halfClipRect2.yMaximum() + std::numeric_limits<double>::epsilon() );
413 }
414 if ( width <= 0 )
415 {
416 halfClipRect1.setXMinimum( halfClipRect1.xMinimum() - std::numeric_limits<double>::epsilon() );
417 halfClipRect2.setXMinimum( halfClipRect2.xMinimum() - std::numeric_limits<double>::epsilon() );
418 halfClipRect1.setXMaximum( halfClipRect1.xMaximum() + std::numeric_limits<double>::epsilon() );
419 halfClipRect2.setXMaximum( halfClipRect2.xMaximum() + std::numeric_limits<double>::epsilon() );
420 }
421
422 geos::unique_ptr clipPart1( GEOSClipByRect_r( context, currentPart, halfClipRect1.xMinimum(), halfClipRect1.yMinimum(), halfClipRect1.xMaximum(), halfClipRect1.yMaximum() ) );
423 geos::unique_ptr clipPart2( GEOSClipByRect_r( context, currentPart, halfClipRect2.xMinimum(), halfClipRect2.yMinimum(), halfClipRect2.xMaximum(), halfClipRect2.yMaximum() ) );
424
425 ++depth;
426
427 if ( clipPart1 )
428 {
429 if ( gridSize > 0 )
430 {
431 clipPart1.reset( GEOSIntersectionPrec_r( context, mGeos.get(), clipPart1.get(), gridSize ) );
432 }
433 subdivideRecursive( clipPart1.get(), maxNodes, depth, parts, halfClipRect1, gridSize );
434 }
435 if ( clipPart2 )
436 {
437 if ( gridSize > 0 )
438 {
439 clipPart2.reset( GEOSIntersectionPrec_r( context, mGeos.get(), clipPart2.get(), gridSize ) );
440 }
441 subdivideRecursive( clipPart2.get(), maxNodes, depth, parts, halfClipRect2, gridSize );
442 }
443}
444
445std::unique_ptr<QgsAbstractGeometry> QgsGeos::subdivide( int maxNodes, QString *errorMsg, const QgsGeometryParameters &parameters ) const
446{
447 if ( !mGeos )
448 {
449 return nullptr;
450 }
451
452 // minimum allowed max is 8
453 maxNodes = std::max( maxNodes, 8 );
454
455 std::unique_ptr< QgsGeometryCollection > parts = QgsGeometryFactory::createCollectionOfType( mGeometry->wkbType() );
456 try
457 {
458 subdivideRecursive( mGeos.get(), maxNodes, 0, parts.get(), mGeometry->boundingBox(), parameters.gridSize() );
459 }
460 CATCH_GEOS_WITH_ERRMSG( nullptr )
461
462 return std::move( parts );
463}
464
465QgsAbstractGeometry *QgsGeos::combine( const QgsAbstractGeometry *geom, QString *errorMsg, const QgsGeometryParameters &parameters ) const
466{
467 return overlay( geom, OverlayUnion, errorMsg, parameters ).release();
468}
469
470QgsAbstractGeometry *QgsGeos::combine( const QVector<QgsAbstractGeometry *> &geomList, QString *errorMsg, const QgsGeometryParameters &parameters ) const
471{
472 std::vector<geos::unique_ptr> geosGeometries;
473 geosGeometries.reserve( geomList.size() );
474 for ( const QgsAbstractGeometry *g : geomList )
475 {
476 if ( !g )
477 continue;
478
479 geosGeometries.emplace_back( asGeos( g, mPrecision ) );
480 }
481
482 GEOSContextHandle_t context = QgsGeosContext::get();
483 geos::unique_ptr geomUnion;
484 try
485 {
486 geos::unique_ptr geomCollection = createGeosCollection( GEOS_GEOMETRYCOLLECTION, geosGeometries );
487 if ( parameters.gridSize() > 0 )
488 {
489 geomUnion.reset( GEOSUnaryUnionPrec_r( context, geomCollection.get(), parameters.gridSize() ) );
490 }
491 else
492 {
493 geomUnion.reset( GEOSUnaryUnion_r( context, geomCollection.get() ) );
494 }
495 }
496 CATCH_GEOS_WITH_ERRMSG( nullptr )
497
498 std::unique_ptr< QgsAbstractGeometry > result = fromGeos( geomUnion.get() );
499 return result.release();
500}
501
502QgsAbstractGeometry *QgsGeos::combine( const QVector<QgsGeometry> &geomList, QString *errorMsg, const QgsGeometryParameters &parameters ) const
503{
504 std::vector<geos::unique_ptr> geosGeometries;
505 geosGeometries.reserve( geomList.size() );
506 for ( const QgsGeometry &g : geomList )
507 {
508 if ( g.isNull() )
509 continue;
510
511 geosGeometries.emplace_back( asGeos( g.constGet(), mPrecision ) );
512 }
513
514 GEOSContextHandle_t context = QgsGeosContext::get();
515 geos::unique_ptr geomUnion;
516 try
517 {
518 geos::unique_ptr geomCollection = createGeosCollection( GEOS_GEOMETRYCOLLECTION, geosGeometries );
519
520 if ( parameters.gridSize() > 0 )
521 {
522 geomUnion.reset( GEOSUnaryUnionPrec_r( context, geomCollection.get(), parameters.gridSize() ) );
523 }
524 else
525 {
526 geomUnion.reset( GEOSUnaryUnion_r( context, geomCollection.get() ) );
527 }
528 }
529 CATCH_GEOS_WITH_ERRMSG( nullptr )
530
531 std::unique_ptr< QgsAbstractGeometry > result = fromGeos( geomUnion.get() );
532 return result.release();
533}
534
535QgsAbstractGeometry *QgsGeos::symDifference( const QgsAbstractGeometry *geom, QString *errorMsg, const QgsGeometryParameters &parameters ) const
536{
537 return overlay( geom, OverlaySymDifference, errorMsg, parameters ).release();
538}
539
540static bool isZVerticalLine( const QgsAbstractGeometry *geom, double tolerance = 4 * std::numeric_limits<double>::epsilon() )
541{
542 // checks if the Geometry if a purely vertical 3D line LineString Z((X Y Z1, X Y Z2, ..., X Y Zn))
543 // This is needed because QgsGeos is not able to handle this type of geometry on distance computation.
544
546 {
547 return false;
548 }
549
550 bool isVertical = true;
551 if ( const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( geom ) )
552 {
553 const int nrPoints = line->numPoints();
554 if ( nrPoints == 1 )
555 {
556 return true;
557 }
558
559 // if the 2D part of two points of the line are different, this means
560 // that the line is not purely vertical
561 const double sqrTolerance = tolerance * tolerance;
562 const double *lineX = line->xData();
563 const double *lineY = line->yData();
564 for ( int iVert = nrPoints - 1, jVert = 0; jVert < nrPoints; iVert = jVert++ )
565 {
566 if ( QgsGeometryUtilsBase::sqrDistance2D( lineX[iVert], lineY[iVert], lineX[jVert], lineY[jVert] ) > sqrTolerance )
567 {
568 isVertical = false;
569 break;
570 }
571 }
572 }
573
574 return isVertical;
575}
576
577double QgsGeos::distance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
578{
579 double distance = -1.0;
580 if ( !mGeos )
581 {
582 return distance;
583 }
584
585 geos::unique_ptr otherGeosGeom;
586
587 // GEOSPreparedDistance_r is not able to properly compute the distance if one
588 // of the geometries if a vertical line (LineString Z((X Y Z1, X Y Z2, ..., X Y Zn))).
589 // In that case, replace `geom` by a single point.
590 // However, GEOSDistance_r works.
591 if ( mGeosPrepared && isZVerticalLine( geom->simplifiedTypeRef() ) )
592 {
593 QgsPoint firstPoint = geom->vertexAt( QgsVertexId( 0, 0, 0 ) );
594 otherGeosGeom = asGeos( &firstPoint, mPrecision );
595 }
596 else
597 {
598 otherGeosGeom = asGeos( geom, mPrecision );
599 }
600
601 if ( !otherGeosGeom )
602 {
603 return distance;
604 }
605
606 GEOSContextHandle_t context = QgsGeosContext::get();
607 try
608 {
609 if ( mGeosPrepared && !isZVerticalLine( mGeometry->simplifiedTypeRef() ) )
610 {
611 GEOSPreparedDistance_r( context, mGeosPrepared.get(), otherGeosGeom.get(), &distance );
612 }
613 else
614 {
615 GEOSDistance_r( context, mGeos.get(), otherGeosGeom.get(), &distance );
616 }
617 }
619
620 return distance;
621}
622
623double QgsGeos::distance( double x, double y, QString *errorMsg ) const
624{
625 double distance = -1.0;
626 if ( !mGeos )
627 {
628 return distance;
629 }
630
631 geos::unique_ptr point = createGeosPointXY( x, y, false, 0, false, 0, 2, 0 );
632 if ( !point )
633 return distance;
634
635 GEOSContextHandle_t context = QgsGeosContext::get();
636 try
637 {
638 if ( mGeosPrepared )
639 {
640 GEOSPreparedDistance_r( context, mGeosPrepared.get(), point.get(), &distance );
641 }
642 else
643 {
644 GEOSDistance_r( context, mGeos.get(), point.get(), &distance );
645 }
646 }
648
649 return distance;
650}
651
652bool QgsGeos::distanceWithin( const QgsAbstractGeometry *geom, double maxdist, QString *errorMsg ) const
653{
654 if ( !mGeos )
655 {
656 return false;
657 }
658
659 if ( qgsDoubleNear( maxdist, 0.0 ) )
660 {
661 return intersects( geom, errorMsg );
662 }
663
664 geos::unique_ptr otherGeosGeom;
665
666 // GEOSPreparedDistanceWithin_r GEOSPreparedDistance_r are not able to properly compute the distance if one
667 // of the geometries if a vertical line (LineString Z((X Y Z1, X Y Z2, ..., X Y Zn))).
668 // In that case, replace `geom` by a single point.
669 // However, GEOSDistanceWithin_r and GEOSDistance_r work.
670 if ( mGeosPrepared && isZVerticalLine( geom->simplifiedTypeRef() ) )
671 {
672 QgsPoint firstPoint = geom->vertexAt( QgsVertexId( 0, 0, 0 ) );
673 otherGeosGeom = asGeos( &firstPoint );
674 }
675 else
676 {
677 otherGeosGeom = asGeos( geom, mPrecision );
678 }
679
680 if ( !otherGeosGeom )
681 {
682 return false;
683 }
684
685 // TODO: optimize implementation of this function to early-exit if
686 // any part of othergeosGeom is found to be within the given
687 // distance
688 double distance;
689
690 GEOSContextHandle_t context = QgsGeosContext::get();
691 try
692 {
693 if ( mGeosPrepared && !isZVerticalLine( mGeometry->simplifiedTypeRef() ) )
694 {
695#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
696 return GEOSPreparedDistanceWithin_r( context, mGeosPrepared.get(), otherGeosGeom.get(), maxdist );
697#else
698 GEOSPreparedDistance_r( context, mGeosPrepared.get(), otherGeosGeom.get(), &distance );
699#endif
700 }
701 else
702 {
703#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
704 return GEOSDistanceWithin_r( context, mGeos.get(), otherGeosGeom.get(), maxdist );
705#else
706 GEOSDistance_r( context, mGeos.get(), otherGeosGeom.get(), &distance );
707#endif
708 }
709 }
711
712 return distance <= maxdist;
713}
714
715bool QgsGeos::contains( double x, double y, QString *errorMsg ) const
716{
717 bool result = false;
718 GEOSContextHandle_t context = QgsGeosContext::get();
719 try
720 {
721#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12 )
722 // defer point creation until after prepared geometry check, we may not need it
723#else
724 geos::unique_ptr point = createGeosPointXY( x, y, false, 0, false, 0, 2, 0 );
725 if ( !point )
726 return false;
727#endif
728 if ( mGeosPrepared ) //use faster version with prepared geometry
729 {
730#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12 )
731 return GEOSPreparedContainsXY_r( context, mGeosPrepared.get(), x, y ) == 1;
732#else
733 return GEOSPreparedContains_r( context, mGeosPrepared.get(), point.get() ) == 1;
734#endif
735 }
736
737#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12 )
738 geos::unique_ptr point = createGeosPointXY( x, y, false, 0, false, 0, 2, 0 );
739 if ( !point )
740 return false;
741#endif
742
743 result = ( GEOSContains_r( context, mGeos.get(), point.get() ) == 1 );
744 }
745 catch ( QgsGeosException &e )
746 {
747 logError( u"GEOS"_s, e.what() );
748 if ( errorMsg )
749 {
750 *errorMsg = e.what();
751 }
752 return false;
753 }
754
755 return result;
756}
757
758double QgsGeos::hausdorffDistance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
759{
760 double distance = -1.0;
761 if ( !mGeos )
762 {
763 return distance;
764 }
765
766 geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
767 if ( !otherGeosGeom )
768 {
769 return distance;
770 }
771
772 try
773 {
774 GEOSHausdorffDistance_r( QgsGeosContext::get(), mGeos.get(), otherGeosGeom.get(), &distance );
775 }
777
778 return distance;
779}
780
781double QgsGeos::hausdorffDistanceDensify( const QgsAbstractGeometry *geom, double densifyFraction, QString *errorMsg ) const
782{
783 double distance = -1.0;
784 if ( !mGeos )
785 {
786 return distance;
787 }
788
789 geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
790 if ( !otherGeosGeom )
791 {
792 return distance;
793 }
794
795 try
796 {
797 GEOSHausdorffDistanceDensify_r( QgsGeosContext::get(), mGeos.get(), otherGeosGeom.get(), densifyFraction, &distance );
798 }
800
801 return distance;
802}
803
804double QgsGeos::frechetDistance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
805{
806 double distance = -1.0;
807 if ( !mGeos )
808 {
809 return distance;
810 }
811
812 geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
813 if ( !otherGeosGeom )
814 {
815 return distance;
816 }
817
818 try
819 {
820 GEOSFrechetDistance_r( QgsGeosContext::get(), mGeos.get(), otherGeosGeom.get(), &distance );
821 }
823
824 return distance;
825}
826
827double QgsGeos::frechetDistanceDensify( const QgsAbstractGeometry *geom, double densifyFraction, QString *errorMsg ) const
828{
829 double distance = -1.0;
830 if ( !mGeos )
831 {
832 return distance;
833 }
834
835 geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
836 if ( !otherGeosGeom )
837 {
838 return distance;
839 }
840
841 try
842 {
843 GEOSFrechetDistanceDensify_r( QgsGeosContext::get(), mGeos.get(), otherGeosGeom.get(), densifyFraction, &distance );
844 }
846
847 return distance;
848}
849
850bool QgsGeos::intersects( const QgsAbstractGeometry *geom, QString *errorMsg ) const
851{
852 if ( !mGeos || !geom )
853 {
854 return false;
855 }
856
857#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12 )
858 // special optimised case for point intersects
860 {
861 if ( mGeosPrepared )
862 {
863 try
864 {
865 return GEOSPreparedIntersectsXY_r( QgsGeosContext::get(), mGeosPrepared.get(), point->x(), point->y() ) == 1;
866 }
867 catch ( QgsGeosException &e )
868 {
869 logError( u"GEOS"_s, e.what() );
870 if ( errorMsg )
871 {
872 *errorMsg = e.what();
873 }
874 return false;
875 }
876 }
877 }
878#endif
879
880 return relation( geom, RelationIntersects, errorMsg );
881}
882
883bool QgsGeos::touches( const QgsAbstractGeometry *geom, QString *errorMsg ) const
884{
885 return relation( geom, RelationTouches, errorMsg );
886}
887
888bool QgsGeos::crosses( const QgsAbstractGeometry *geom, QString *errorMsg ) const
889{
890 return relation( geom, RelationCrosses, errorMsg );
891}
892
893bool QgsGeos::within( const QgsAbstractGeometry *geom, QString *errorMsg ) const
894{
895 return relation( geom, RelationWithin, errorMsg );
896}
897
898bool QgsGeos::overlaps( const QgsAbstractGeometry *geom, QString *errorMsg ) const
899{
900 return relation( geom, RelationOverlaps, errorMsg );
901}
902
903bool QgsGeos::contains( const QgsAbstractGeometry *geom, QString *errorMsg ) const
904{
905 if ( !mGeos || !geom )
906 {
907 return false;
908 }
909
910#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12 )
911 // special optimised case for point containment
913 {
914 if ( mGeosPrepared )
915 {
916 try
917 {
918 return GEOSPreparedContainsXY_r( QgsGeosContext::get(), mGeosPrepared.get(), point->x(), point->y() ) == 1;
919 }
920 catch ( QgsGeosException &e )
921 {
922 logError( u"GEOS"_s, e.what() );
923 if ( errorMsg )
924 {
925 *errorMsg = e.what();
926 }
927 return false;
928 }
929 }
930 }
931#endif
932
933 return relation( geom, RelationContains, errorMsg );
934}
935
936bool QgsGeos::disjoint( const QgsAbstractGeometry *geom, QString *errorMsg ) const
937{
938 return relation( geom, RelationDisjoint, errorMsg );
939}
940
941QString QgsGeos::relate( const QgsAbstractGeometry *geom, QString *errorMsg ) const
942{
943 if ( !mGeos )
944 {
945 return QString();
946 }
947
948 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
949 if ( !geosGeom )
950 {
951 return QString();
952 }
953
954 QString result;
955 GEOSContextHandle_t context = QgsGeosContext::get();
956 try
957 {
958 char *r = GEOSRelate_r( context, mGeos.get(), geosGeom.get() );
959 if ( r )
960 {
961 result = QString( r );
962 GEOSFree_r( context, r );
963 }
964 }
965 catch ( QgsGeosException &e )
966 {
967 logError( u"GEOS"_s, e.what() );
968 if ( errorMsg )
969 {
970 *errorMsg = e.what();
971 }
972 }
973
974 return result;
975}
976
977bool QgsGeos::relatePattern( const QgsAbstractGeometry *geom, const QString &pattern, QString *errorMsg ) const
978{
979 if ( !mGeos || !geom )
980 {
981 return false;
982 }
983
984 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
985 if ( !geosGeom )
986 {
987 return false;
988 }
989
990 bool result = false;
991 GEOSContextHandle_t context = QgsGeosContext::get();
992 try
993 {
994 result = ( GEOSRelatePattern_r( context, mGeos.get(), geosGeom.get(), pattern.toLocal8Bit().constData() ) == 1 );
995 }
996 catch ( QgsGeosException &e )
997 {
998 logError( u"GEOS"_s, e.what() );
999 if ( errorMsg )
1000 {
1001 *errorMsg = e.what();
1002 }
1003 }
1004
1005 return result;
1006}
1007
1008double QgsGeos::area( QString *errorMsg ) const
1009{
1010 double area = -1.0;
1011 if ( !mGeos )
1012 {
1013 return area;
1014 }
1015
1016 try
1017 {
1018 if ( GEOSArea_r( QgsGeosContext::get(), mGeos.get(), &area ) != 1 )
1019 return -1.0;
1020 }
1022 return area;
1023}
1024
1025double QgsGeos::length( QString *errorMsg ) const
1026{
1027 double length = -1.0;
1028 if ( !mGeos )
1029 {
1030 return length;
1031 }
1032 try
1033 {
1034 if ( GEOSLength_r( QgsGeosContext::get(), mGeos.get(), &length ) != 1 )
1035 return -1.0;
1036 }
1038 return length;
1039}
1040
1042 const QgsLineString &splitLine, QVector<QgsGeometry> &newGeometries, bool topological, QgsPointSequence &topologyTestPoints, QString *errorMsg, bool skipIntersectionCheck
1043) const
1044{
1045 EngineOperationResult returnCode = Success;
1046 if ( !mGeos || !mGeometry )
1047 {
1048 return InvalidBaseGeometry;
1049 }
1050
1051 //return if this type is point/multipoint
1052 if ( mGeometry->dimension() == 0 )
1053 {
1054 return SplitCannotSplitPoint; //cannot split points
1055 }
1056
1057 GEOSContextHandle_t context = QgsGeosContext::get();
1058 if ( !GEOSisValid_r( context, mGeos.get() ) )
1059 return InvalidBaseGeometry;
1060
1061 //make sure splitLine is valid
1062 if ( ( mGeometry->dimension() == 1 && splitLine.numPoints() < 1 ) || ( mGeometry->dimension() == 2 && splitLine.numPoints() < 2 ) )
1063 return InvalidInput;
1064
1065 newGeometries.clear();
1066 geos::unique_ptr splitLineGeos;
1067
1068 try
1069 {
1070 if ( splitLine.numPoints() > 1 )
1071 {
1072 splitLineGeos = createGeosLinestring( &splitLine, mPrecision );
1073 }
1074 else if ( splitLine.numPoints() == 1 )
1075 {
1076 splitLineGeos = createGeosPointXY( splitLine.xAt( 0 ), splitLine.yAt( 0 ), false, 0, false, 0, 2, mPrecision );
1077 }
1078 else
1079 {
1080 return InvalidInput;
1081 }
1082
1083 if ( !GEOSisValid_r( context, splitLineGeos.get() ) || !GEOSisSimple_r( context, splitLineGeos.get() ) )
1084 {
1085 return InvalidInput;
1086 }
1087
1088 if ( topological )
1089 {
1090 //find out candidate points for topological corrections
1091 if ( !topologicalTestPointsSplit( splitLineGeos.get(), topologyTestPoints ) )
1092 {
1093 return InvalidInput; // TODO: is it really an invalid input?
1094 }
1095 }
1096
1097 //call split function depending on geometry type
1098 if ( mGeometry->dimension() == 1 )
1099 {
1100 returnCode = splitLinearGeometry( splitLineGeos.get(), newGeometries, skipIntersectionCheck );
1101 }
1102 else if ( mGeometry->dimension() == 2 )
1103 {
1104 returnCode = splitPolygonGeometry( splitLineGeos.get(), newGeometries, skipIntersectionCheck );
1105 }
1106 else
1107 {
1108 return InvalidInput;
1109 }
1110 }
1112
1113 return returnCode;
1114}
1115
1116
1117bool QgsGeos::topologicalTestPointsSplit( const GEOSGeometry *splitLine, QgsPointSequence &testPoints, QString *errorMsg ) const
1118{
1119 //Find out the intersection points between splitLineGeos and this geometry.
1120 //These points need to be tested for topological correctness by the calling function
1121 //if topological editing is enabled
1122
1123 if ( !mGeos )
1124 {
1125 return false;
1126 }
1127
1128 GEOSContextHandle_t context = QgsGeosContext::get();
1129 try
1130 {
1131 testPoints.clear();
1132 geos::unique_ptr intersectionGeom( GEOSIntersection_r( context, mGeos.get(), splitLine ) );
1133 if ( !intersectionGeom )
1134 return false;
1135
1136 bool simple = false;
1137 int nIntersectGeoms = 1;
1138 if ( GEOSGeomTypeId_r( context, intersectionGeom.get() ) == GEOS_LINESTRING || GEOSGeomTypeId_r( context, intersectionGeom.get() ) == GEOS_POINT )
1139 simple = true;
1140
1141 if ( !simple )
1142 nIntersectGeoms = GEOSGetNumGeometries_r( context, intersectionGeom.get() );
1143
1144 for ( int i = 0; i < nIntersectGeoms; ++i )
1145 {
1146 const GEOSGeometry *currentIntersectGeom = nullptr;
1147 if ( simple )
1148 currentIntersectGeom = intersectionGeom.get();
1149 else
1150 currentIntersectGeom = GEOSGetGeometryN_r( context, intersectionGeom.get(), i );
1151
1152 const GEOSCoordSequence *lineSequence = GEOSGeom_getCoordSeq_r( context, currentIntersectGeom );
1153 unsigned int sequenceSize = 0;
1154 double x, y, z;
1155 if ( GEOSCoordSeq_getSize_r( context, lineSequence, &sequenceSize ) != 0 )
1156 {
1157 for ( unsigned int i = 0; i < sequenceSize; ++i )
1158 {
1159 if ( GEOSCoordSeq_getXYZ_r( context, lineSequence, i, &x, &y, &z ) )
1160 {
1161 testPoints.push_back( QgsPoint( x, y, z ) );
1162 }
1163 }
1164 }
1165 }
1166 }
1168
1169 return true;
1170}
1171
1172geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) const
1173{
1174 GEOSContextHandle_t context = QgsGeosContext::get();
1175 int type = GEOSGeomTypeId_r( context, mGeos.get() );
1176
1177 std::unique_ptr< QgsMultiCurve > multiCurve;
1178 if ( type == GEOS_MULTILINESTRING )
1179 {
1180 multiCurve.reset( qgsgeometry_cast<QgsMultiCurve *>( mGeometry->clone() ) );
1181 }
1182 else if ( type == GEOS_LINESTRING )
1183 {
1184 multiCurve = std::make_unique<QgsMultiCurve>();
1185 multiCurve->addGeometry( mGeometry->clone() );
1186 }
1187 else
1188 {
1189 return nullptr;
1190 }
1191
1192 if ( !multiCurve )
1193 {
1194 return nullptr;
1195 }
1196
1197
1198 // we might have a point or a multipoint, depending on number of
1199 // intersections between the geometry and the split geometry
1200 std::unique_ptr< QgsMultiPoint > splitPoints;
1201 {
1202 std::unique_ptr< QgsAbstractGeometry > splitGeom( fromGeos( GEOSsplitPoint ) );
1203
1204 if ( qgsgeometry_cast<QgsMultiPoint *>( splitGeom.get() ) )
1205 {
1206 splitPoints.reset( qgis::down_cast<QgsMultiPoint *>( splitGeom.release() ) );
1207 }
1208 else if ( qgsgeometry_cast<QgsPoint *>( splitGeom.get() ) )
1209 {
1210 splitPoints = std::make_unique< QgsMultiPoint >();
1211 if ( qgsgeometry_cast<QgsPoint *>( splitGeom.get() ) )
1212 {
1213 splitPoints->addGeometry( qgis::down_cast<QgsPoint *>( splitGeom.release() ) );
1214 }
1215 }
1216 }
1217
1218 if ( !splitPoints )
1219 return nullptr;
1220
1221 QgsMultiCurve lines;
1222
1223 //For each part
1224 for ( int geometryIndex = 0; geometryIndex < multiCurve->numGeometries(); ++geometryIndex )
1225 {
1226 const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( multiCurve->geometryN( geometryIndex ) );
1227 if ( !line )
1228 {
1229 const QgsCurve *curve = qgsgeometry_cast<const QgsCurve *>( multiCurve->geometryN( geometryIndex ) );
1230 line = curve->curveToLine();
1231 }
1232 if ( !line )
1233 {
1234 return nullptr;
1235 }
1236 // we gather the intersection points and their distance from previous node grouped by segment
1237 QMap< int, QVector< QPair< double, QgsPoint > > > pointMap;
1238 for ( int splitPointIndex = 0; splitPointIndex < splitPoints->numGeometries(); ++splitPointIndex )
1239 {
1240 const QgsPoint *intersectionPoint = splitPoints->pointN( splitPointIndex );
1241
1242 QgsPoint segmentPoint2D;
1243 QgsVertexId nextVertex;
1244 // With closestSegment we only get a 2D point so we need to interpolate if we
1245 // don't want to lose Z data
1246 line->closestSegment( *intersectionPoint, segmentPoint2D, nextVertex );
1247
1248 // The intersection might belong to another part, skip it
1249 // Note: cannot test for equality because of Z
1250 if ( !qgsDoubleNear( intersectionPoint->x(), segmentPoint2D.x() ) || !qgsDoubleNear( intersectionPoint->y(), segmentPoint2D.y() ) )
1251 {
1252 continue;
1253 }
1254
1255 const QgsLineString segment = QgsLineString( line->pointN( nextVertex.vertex - 1 ), line->pointN( nextVertex.vertex ) );
1256 const double distance = segmentPoint2D.distance( line->pointN( nextVertex.vertex - 1 ) );
1257
1258 // Due to precision issues, distance can be a tad larger than the actual segment length, making interpolatePoint() return nullptr
1259 // In that case we'll use the segment's endpoint instead of interpolating
1260 std::unique_ptr< QgsPoint > correctSegmentPoint( distance > segment.length() ? segment.endPoint().clone() : segment.interpolatePoint( distance ) );
1261
1262 const QPair< double, QgsPoint > pair = qMakePair( distance, *correctSegmentPoint.get() );
1263 if ( pointMap.contains( nextVertex.vertex - 1 ) )
1264 pointMap[nextVertex.vertex - 1].append( pair );
1265 else
1266 pointMap[nextVertex.vertex - 1] = QVector< QPair< double, QgsPoint > >() << pair;
1267 }
1268
1269 // When we have more than one intersection point on a segment we need those points
1270 // to be sorted by their distance from the previous geometry vertex
1271 for ( auto &p : pointMap )
1272 {
1273 std::sort( p.begin(), p.end(), []( const QPair< double, QgsPoint > &a, const QPair< double, QgsPoint > &b ) { return a.first < b.first; } );
1274 }
1275
1276 //For each segment
1277 QgsLineString newLine;
1278 int nVertices = line->numPoints();
1279 QgsPoint splitPoint;
1280 for ( int vertexIndex = 0; vertexIndex < nVertices; ++vertexIndex )
1281 {
1282 QgsPoint currentPoint = line->pointN( vertexIndex );
1283 newLine.addVertex( currentPoint );
1284 if ( pointMap.contains( vertexIndex ) )
1285 {
1286 // For each intersecting point
1287 for ( int k = 0; k < pointMap[vertexIndex].size(); ++k )
1288 {
1289 splitPoint = pointMap[vertexIndex][k].second;
1290 if ( splitPoint == currentPoint )
1291 {
1292 lines.addGeometry( newLine.clone() );
1293 newLine = QgsLineString();
1294 newLine.addVertex( currentPoint );
1295 }
1296 else if ( splitPoint == line->pointN( vertexIndex + 1 ) )
1297 {
1298 newLine.addVertex( line->pointN( vertexIndex + 1 ) );
1299 lines.addGeometry( newLine.clone() );
1300 newLine = QgsLineString();
1301 }
1302 else
1303 {
1304 newLine.addVertex( splitPoint );
1305 lines.addGeometry( newLine.clone() );
1306 newLine = QgsLineString();
1307 newLine.addVertex( splitPoint );
1308 }
1309 }
1310 }
1311 }
1312 lines.addGeometry( newLine.clone() );
1313 }
1314
1315 return asGeos( &lines, mPrecision );
1316}
1317
1318QgsGeometryEngine::EngineOperationResult QgsGeos::splitLinearGeometry( const GEOSGeometry *splitLine, QVector<QgsGeometry> &newGeometries, bool skipIntersectionCheck ) const
1319{
1320 Q_UNUSED( skipIntersectionCheck )
1321 if ( !splitLine )
1322 return InvalidInput;
1323
1324 if ( !mGeos )
1325 return InvalidBaseGeometry;
1326
1327 GEOSContextHandle_t context = QgsGeosContext::get();
1328
1329 geos::unique_ptr intersectGeom( GEOSIntersection_r( context, splitLine, mGeos.get() ) );
1330 if ( !intersectGeom || GEOSisEmpty_r( context, intersectGeom.get() ) )
1331 return NothingHappened;
1332
1333 //check that split line has no linear intersection
1334 const int linearIntersect = GEOSRelatePattern_r( context, mGeos.get(), splitLine, "1********" );
1335 if ( linearIntersect > 0 )
1336 return InvalidInput;
1337
1338 geos::unique_ptr splitGeom = linePointDifference( intersectGeom.get() );
1339
1340 if ( !splitGeom )
1341 return InvalidBaseGeometry;
1342
1343 std::vector<geos::unique_ptr> lineGeoms;
1344
1345 const int splitType = GEOSGeomTypeId_r( context, splitGeom.get() );
1346 if ( splitType == GEOS_MULTILINESTRING )
1347 {
1348 const int nGeoms = GEOSGetNumGeometries_r( context, splitGeom.get() );
1349 lineGeoms.reserve( nGeoms );
1350 for ( int i = 0; i < nGeoms; ++i )
1351 lineGeoms.emplace_back( GEOSGeom_clone_r( context, GEOSGetGeometryN_r( context, splitGeom.get(), i ) ) );
1352 }
1353 else
1354 {
1355 lineGeoms.emplace_back( GEOSGeom_clone_r( context, splitGeom.get() ) );
1356 }
1357
1358 mergeGeometriesMultiTypeSplit( lineGeoms );
1359
1360 for ( geos::unique_ptr &lineGeom : lineGeoms )
1361 {
1362 newGeometries << QgsGeometry( fromGeos( lineGeom.get() ) );
1363 }
1364
1365 return Success;
1366}
1367
1368QgsGeometryEngine::EngineOperationResult QgsGeos::splitPolygonGeometry( const GEOSGeometry *splitLine, QVector<QgsGeometry> &newGeometries, bool skipIntersectionCheck ) const
1369{
1370 if ( !splitLine )
1371 return InvalidInput;
1372
1373 if ( !mGeos )
1374 return InvalidBaseGeometry;
1375
1376 // we will need prepared geometry for intersection tests
1377 const_cast<QgsGeos *>( this )->prepareGeometry();
1378 if ( !mGeosPrepared )
1379 return EngineError;
1380
1381 GEOSContextHandle_t context = QgsGeosContext::get();
1382
1383 //first test if linestring intersects geometry. If not, return straight away
1384 if ( !skipIntersectionCheck && !GEOSPreparedIntersects_r( context, mGeosPrepared.get(), splitLine ) )
1385 return NothingHappened;
1386
1387 //first union all the polygon rings together (to get them noded, see JTS developer guide)
1388 geos::unique_ptr nodedGeometry = nodeGeometries( splitLine, mGeos.get() );
1389 if ( !nodedGeometry )
1390 return NodedGeometryError; //an error occurred during noding
1391
1392 const GEOSGeometry *noded = nodedGeometry.get();
1393 geos::unique_ptr polygons( GEOSPolygonize_r( context, &noded, 1 ) );
1394 if ( !polygons )
1395 {
1396 return InvalidBaseGeometry;
1397 }
1398 const int numberOfGeometriesPolygon = numberOfGeometries( polygons.get() );
1399 if ( numberOfGeometriesPolygon == 0 )
1400 {
1401 return InvalidBaseGeometry;
1402 }
1403
1404 //test every polygon is contained in original geometry
1405 //include in result if yes
1406 std::vector<geos::unique_ptr> testedGeometries;
1407
1408 // test whether the polygon parts returned from polygonize algorithm actually
1409 // belong to the source polygon geometry (if the source polygon contains some holes,
1410 // those would be also returned by polygonize and we need to skip them)
1411 for ( int i = 0; i < numberOfGeometriesPolygon; i++ )
1412 {
1413 const GEOSGeometry *polygon = GEOSGetGeometryN_r( context, polygons.get(), i );
1414
1415 geos::unique_ptr pointOnSurface( GEOSPointOnSurface_r( context, polygon ) );
1416 if ( pointOnSurface && GEOSPreparedIntersects_r( context, mGeosPrepared.get(), pointOnSurface.get() ) )
1417 testedGeometries.emplace_back( GEOSGeom_clone_r( context, polygon ) );
1418 }
1419
1420 const size_t nGeometriesThis = numberOfGeometries( mGeos.get() ); //original number of geometries
1421 if ( testedGeometries.empty() || testedGeometries.size() == nGeometriesThis )
1422 {
1423 //no split done, preserve original geometry
1424 return NothingHappened;
1425 }
1426
1427 // For multi-part geometries, try to identify parts that have been unchanged and try to merge them back
1428 // to a single multi-part geometry. For example, if there's a multi-polygon with three parts, but only
1429 // one part is being split, this function makes sure that the other two parts will be kept in a multi-part
1430 // geometry rather than being separated into two single-part geometries.
1431 mergeGeometriesMultiTypeSplit( testedGeometries );
1432
1433 size_t i;
1434 for ( i = 0; i < testedGeometries.size() && GEOSisValid_r( context, testedGeometries[i].get() ); ++i )
1435 ;
1436
1437 if ( i < testedGeometries.size() )
1438 {
1439 return InvalidBaseGeometry;
1440 }
1441
1442 for ( geos::unique_ptr &testedGeometry : testedGeometries )
1443 {
1444 newGeometries << QgsGeometry( fromGeos( testedGeometry.get() ) );
1445 }
1446
1447 return Success;
1448}
1449
1450geos::unique_ptr QgsGeos::nodeGeometries( const GEOSGeometry *splitLine, const GEOSGeometry *geom )
1451{
1452 if ( !splitLine || !geom )
1453 return nullptr;
1454
1455 geos::unique_ptr geometryBoundary;
1456 GEOSContextHandle_t context = QgsGeosContext::get();
1457 if ( GEOSGeom_getDimensions_r( context, geom ) == 2 )
1458 geometryBoundary.reset( GEOSBoundary_r( context, geom ) );
1459 else
1460 geometryBoundary.reset( GEOSGeom_clone_r( context, geom ) );
1461
1462 geos::unique_ptr splitLineClone( GEOSGeom_clone_r( context, splitLine ) );
1463 geos::unique_ptr unionGeometry( GEOSUnion_r( context, splitLineClone.get(), geometryBoundary.get() ) );
1464
1465 return unionGeometry;
1466}
1467
1468int QgsGeos::mergeGeometriesMultiTypeSplit( std::vector<geos::unique_ptr> &splitResult ) const
1469{
1470 if ( !mGeos )
1471 return 1;
1472
1473 //convert mGeos to geometry collection
1474 GEOSContextHandle_t context = QgsGeosContext::get();
1475 int type = GEOSGeomTypeId_r( context, mGeos.get() );
1476 if ( type != GEOS_GEOMETRYCOLLECTION && type != GEOS_MULTILINESTRING && type != GEOS_MULTIPOLYGON && type != GEOS_MULTIPOINT )
1477 return 0;
1478
1479 //collect all the geometries that belong to the initial multifeature
1480 std::vector<geos::unique_ptr> unionGeom;
1481
1482 std::vector<geos::unique_ptr> newSplitResult;
1483
1484 for ( size_t i = 0; i < splitResult.size(); ++i )
1485 {
1486 //is this geometry a part of the original multitype?
1487 bool isPart = false;
1488 for ( int j = 0; j < GEOSGetNumGeometries_r( context, mGeos.get() ); j++ )
1489 {
1490 if ( GEOSEquals_r( context, splitResult[i].get(), GEOSGetGeometryN_r( context, mGeos.get(), j ) ) )
1491 {
1492 isPart = true;
1493 break;
1494 }
1495 }
1496
1497 if ( isPart )
1498 {
1499 unionGeom.emplace_back( std::move( splitResult[i] ) );
1500 }
1501 else
1502 {
1503 std::vector<geos::unique_ptr> geomVector;
1504 geomVector.emplace_back( std::move( splitResult[i] ) );
1505
1506 if ( type == GEOS_MULTILINESTRING )
1507 newSplitResult.emplace_back( createGeosCollection( GEOS_MULTILINESTRING, geomVector ) );
1508 else if ( type == GEOS_MULTIPOLYGON )
1509 newSplitResult.emplace_back( createGeosCollection( GEOS_MULTIPOLYGON, geomVector ) );
1510 }
1511 }
1512
1513 splitResult = std::move( newSplitResult );
1514
1515 //make multifeature out of unionGeom
1516 if ( !unionGeom.empty() )
1517 {
1518 if ( type == GEOS_MULTILINESTRING )
1519 splitResult.emplace_back( createGeosCollection( GEOS_MULTILINESTRING, unionGeom ) );
1520 else if ( type == GEOS_MULTIPOLYGON )
1521 splitResult.emplace_back( createGeosCollection( GEOS_MULTIPOLYGON, unionGeom ) );
1522 }
1523
1524 return 0;
1525}
1526
1527geos::unique_ptr QgsGeos::createGeosCollection( int typeId, std::vector<geos::unique_ptr> &geoms )
1528{
1529 std::vector<GEOSGeometry *> geomarr;
1530 geomarr.reserve( geoms.size() );
1531
1532 GEOSContextHandle_t context = QgsGeosContext::get();
1533 for ( geos::unique_ptr &geomUniquePtr : geoms )
1534 {
1535 if ( geomUniquePtr )
1536 {
1537 if ( !GEOSisEmpty_r( context, geomUniquePtr.get() ) )
1538 {
1539 // don't add empty parts to a geos collection, it can cause crashes in GEOS
1540 // transfer ownership of the geometries to GEOSGeom_createCollection_r()
1541 geomarr.emplace_back( geomUniquePtr.release() );
1542 }
1543 }
1544 }
1545 geos::unique_ptr geomRes;
1546
1547 try
1548 {
1549 geomRes.reset( GEOSGeom_createCollection_r( context, typeId, geomarr.data(), geomarr.size() ) );
1550 }
1551 catch ( QgsGeosException & )
1552 {
1553 for ( GEOSGeometry *geom : geomarr )
1554 {
1555 GEOSGeom_destroy_r( context, geom );
1556 }
1557 }
1558
1559 return geomRes;
1560}
1561
1562std::unique_ptr<QgsAbstractGeometry> QgsGeos::fromGeos( const GEOSGeometry *geos )
1563{
1564 if ( !geos )
1565 {
1566 return nullptr;
1567 }
1568
1569 GEOSContextHandle_t context = QgsGeosContext::get();
1570 int nCoordDims = GEOSGeom_getCoordinateDimension_r( context, geos );
1571 int nDims = GEOSGeom_getDimensions_r( context, geos );
1572 bool hasZ = ( nCoordDims == 3 );
1573 bool hasM = ( ( nDims - nCoordDims ) == 1 );
1574
1575 switch ( GEOSGeomTypeId_r( context, geos ) )
1576 {
1577 case GEOS_POINT: // a point
1578 {
1579 if ( GEOSisEmpty_r( context, geos ) )
1580 return nullptr;
1581
1582 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( context, geos );
1583 unsigned int nPoints = 0;
1584 GEOSCoordSeq_getSize_r( context, cs, &nPoints );
1585 if ( nPoints == 0 )
1586 {
1587 return nullptr;
1588 }
1589 // Since GEOS 3.13, Points with NAN coordinates are not considered empty anymore
1590 // See: https://github.com/libgeos/geos/pull/927
1591 // Handle this change by checking if QgsPoint is empty
1592 const QgsPoint point = coordSeqPoint( cs, 0, hasZ, hasM );
1593 return !point.isEmpty() ? std::unique_ptr<QgsAbstractGeometry>( point.clone() ) : nullptr;
1594 }
1595 case GEOS_LINESTRING:
1596 {
1597 return sequenceToLinestring( geos, hasZ, hasM );
1598 }
1599 case GEOS_POLYGON:
1600 {
1601 return fromGeosPolygon( geos );
1602 }
1603 case GEOS_MULTIPOINT:
1604 {
1605 auto multiPoint = std::make_unique<QgsMultiPoint>();
1606 int nParts = GEOSGetNumGeometries_r( context, geos );
1607 multiPoint->reserve( nParts );
1608 for ( int i = 0; i < nParts; ++i )
1609 {
1610 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( context, GEOSGetGeometryN_r( context, geos, i ) );
1611 if ( cs )
1612 {
1613 unsigned int nPoints = 0;
1614 GEOSCoordSeq_getSize_r( context, cs, &nPoints );
1615 if ( nPoints > 0 )
1616 multiPoint->addGeometry( coordSeqPoint( cs, 0, hasZ, hasM ).clone() );
1617 }
1618 }
1619 return std::move( multiPoint );
1620 }
1621 case GEOS_MULTILINESTRING:
1622 {
1623 auto multiLineString = std::make_unique<QgsMultiLineString>();
1624 int nParts = GEOSGetNumGeometries_r( context, geos );
1625 multiLineString->reserve( nParts );
1626 for ( int i = 0; i < nParts; ++i )
1627 {
1628 std::unique_ptr< QgsLineString > line( sequenceToLinestring( GEOSGetGeometryN_r( context, geos, i ), hasZ, hasM ) );
1629 if ( line )
1630 {
1631 multiLineString->addGeometry( line.release() );
1632 }
1633 }
1634 return std::move( multiLineString );
1635 }
1636 case GEOS_MULTIPOLYGON:
1637 {
1638 auto multiPolygon = std::make_unique<QgsMultiPolygon>();
1639
1640 int nParts = GEOSGetNumGeometries_r( context, geos );
1641 multiPolygon->reserve( nParts );
1642 for ( int i = 0; i < nParts; ++i )
1643 {
1644 std::unique_ptr< QgsPolygon > poly = fromGeosPolygon( GEOSGetGeometryN_r( context, geos, i ) );
1645 if ( poly )
1646 {
1647 multiPolygon->addGeometry( poly.release() );
1648 }
1649 }
1650 return std::move( multiPolygon );
1651 }
1652 case GEOS_GEOMETRYCOLLECTION:
1653 {
1654 auto geomCollection = std::make_unique<QgsGeometryCollection>();
1655 int nParts = GEOSGetNumGeometries_r( context, geos );
1656 geomCollection->reserve( nParts );
1657 for ( int i = 0; i < nParts; ++i )
1658 {
1659 std::unique_ptr< QgsAbstractGeometry > geom( fromGeos( GEOSGetGeometryN_r( context, geos, i ) ) );
1660 if ( geom )
1661 {
1662 geomCollection->addGeometry( geom.release() );
1663 }
1664 }
1665 return std::move( geomCollection );
1666 }
1667 }
1668 return nullptr;
1669}
1670
1671std::unique_ptr<QgsPolygon> QgsGeos::fromGeosPolygon( const GEOSGeometry *geos )
1672{
1673 GEOSContextHandle_t context = QgsGeosContext::get();
1674 if ( GEOSGeomTypeId_r( context, geos ) != GEOS_POLYGON )
1675 {
1676 return nullptr;
1677 }
1678
1679 int nCoordDims = GEOSGeom_getCoordinateDimension_r( context, geos );
1680 int nDims = GEOSGeom_getDimensions_r( context, geos );
1681 bool hasZ = ( nCoordDims == 3 );
1682 bool hasM = ( ( nDims - nCoordDims ) == 1 );
1683
1684 auto polygon = std::make_unique<QgsPolygon>();
1685
1686 const GEOSGeometry *ring = GEOSGetExteriorRing_r( context, geos );
1687 if ( ring )
1688 {
1689 polygon->setExteriorRing( sequenceToLinestring( ring, hasZ, hasM ).release() );
1690 }
1691
1692 QVector<QgsCurve *> interiorRings;
1693 const int ringCount = GEOSGetNumInteriorRings_r( context, geos );
1694 interiorRings.reserve( ringCount );
1695 for ( int i = 0; i < ringCount; ++i )
1696 {
1697 ring = GEOSGetInteriorRingN_r( context, geos, i );
1698 if ( ring )
1699 {
1700 interiorRings.push_back( sequenceToLinestring( ring, hasZ, hasM ).release() );
1701 }
1702 }
1703 polygon->setInteriorRings( interiorRings );
1704
1705 return polygon;
1706}
1707
1708std::unique_ptr<QgsLineString> QgsGeos::sequenceToLinestring( const GEOSGeometry *geos, bool hasZ, bool hasM )
1709{
1710 GEOSContextHandle_t context = QgsGeosContext::get();
1711 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( context, geos );
1712
1713 unsigned int nPoints;
1714 GEOSCoordSeq_getSize_r( context, cs, &nPoints );
1715
1716 QVector< double > xOut( nPoints );
1717 QVector< double > yOut( nPoints );
1718 QVector< double > zOut;
1719 if ( hasZ )
1720 zOut.resize( nPoints );
1721 QVector< double > mOut;
1722 if ( hasM )
1723 mOut.resize( nPoints );
1724
1725 double *x = xOut.data();
1726 double *y = yOut.data();
1727 double *z = zOut.data();
1728 double *m = mOut.data();
1729
1730#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
1731 GEOSCoordSeq_copyToArrays_r( context, cs, x, y, hasZ ? z : nullptr, hasM ? m : nullptr );
1732#else
1733 for ( unsigned int i = 0; i < nPoints; ++i )
1734 {
1735 if ( hasZ )
1736 GEOSCoordSeq_getXYZ_r( context, cs, i, x++, y++, z++ );
1737 else
1738 GEOSCoordSeq_getXY_r( context, cs, i, x++, y++ );
1739 if ( hasM )
1740 {
1741 GEOSCoordSeq_getOrdinate_r( context, cs, i, 3, m++ );
1742 }
1743 }
1744#endif
1745 auto line = std::make_unique<QgsLineString>( xOut, yOut, zOut, mOut );
1746 return line;
1747}
1748
1749int QgsGeos::numberOfGeometries( GEOSGeometry *g )
1750{
1751 if ( !g )
1752 return 0;
1753
1754 GEOSContextHandle_t context = QgsGeosContext::get();
1755 int geometryType = GEOSGeomTypeId_r( context, g );
1756 if ( geometryType == GEOS_POINT || geometryType == GEOS_LINESTRING || geometryType == GEOS_LINEARRING || geometryType == GEOS_POLYGON )
1757 return 1;
1758
1759 //calling GEOSGetNumGeometries is save for multi types and collections also in geos2
1760 return GEOSGetNumGeometries_r( context, g );
1761}
1762
1763QgsPoint QgsGeos::coordSeqPoint( const GEOSCoordSequence *cs, int i, bool hasZ, bool hasM )
1764{
1765 if ( !cs )
1766 {
1767 return QgsPoint();
1768 }
1769
1770 GEOSContextHandle_t context = QgsGeosContext::get();
1771
1772 double x, y;
1773 double z = 0;
1774 double m = 0;
1775 if ( hasZ )
1776 GEOSCoordSeq_getXYZ_r( context, cs, i, &x, &y, &z );
1777 else
1778 GEOSCoordSeq_getXY_r( context, cs, i, &x, &y );
1779 if ( hasM )
1780 {
1781 GEOSCoordSeq_getOrdinate_r( context, cs, i, 3, &m );
1782 }
1783
1785 if ( hasZ && hasM )
1786 {
1788 }
1789 else if ( hasZ )
1790 {
1792 }
1793 else if ( hasM )
1794 {
1796 }
1797 return QgsPoint( t, x, y, z, m );
1798}
1799
1801{
1802 if ( !geom )
1803 return nullptr;
1804
1805 int coordDims = 2;
1806 if ( geom->is3D() )
1807 {
1808 ++coordDims;
1809 }
1810 if ( geom->isMeasure() )
1811 {
1812 ++coordDims;
1813 }
1814
1816 {
1817 int geosType = GEOS_GEOMETRYCOLLECTION;
1818
1820 {
1821 switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
1822 {
1824 geosType = GEOS_MULTIPOINT;
1825 break;
1826
1828 geosType = GEOS_MULTILINESTRING;
1829 break;
1830
1832 geosType = GEOS_MULTIPOLYGON;
1833 break;
1834
1837 return nullptr;
1838 }
1839 }
1840
1841
1843
1844 if ( !c )
1845 return nullptr;
1846
1847 std::vector<geos::unique_ptr> geomVector;
1848 geomVector.reserve( c->numGeometries() );
1849 for ( int i = 0; i < c->numGeometries(); ++i )
1850 {
1851 geos::unique_ptr geosGeom = asGeos( c->geometryN( i ), precision, flags );
1852 if ( flags & Qgis::GeosCreationFlag::RejectOnInvalidSubGeometry && !geosGeom )
1853 {
1854 return nullptr;
1855 }
1856 geomVector.emplace_back( std::move( geosGeom ) );
1857 }
1858 return createGeosCollection( geosType, geomVector );
1859 }
1861 {
1862 // PolyhedralSurface and TIN support
1863 // convert it to a geos MultiPolygon
1865 if ( !polyhedralSurface )
1866 return nullptr;
1867
1868 std::vector<geos::unique_ptr> geomVector;
1869 geomVector.reserve( polyhedralSurface->numPatches() );
1870 for ( int i = 0; i < polyhedralSurface->numPatches(); ++i )
1871 {
1872 geos::unique_ptr geosPolygon = createGeosPolygon( polyhedralSurface->patchN( i ), precision );
1873 if ( flags & Qgis::GeosCreationFlag::RejectOnInvalidSubGeometry && !geosPolygon )
1874 {
1875 return nullptr;
1876 }
1877 geomVector.emplace_back( std::move( geosPolygon ) );
1878 }
1879
1880 return createGeosCollection( GEOS_MULTIPOLYGON, geomVector );
1881 }
1882 else
1883 {
1884 switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
1885 {
1887 return createGeosPoint( static_cast<const QgsPoint *>( geom ), coordDims, precision, flags );
1888
1890 return createGeosLinestring( static_cast<const QgsLineString *>( geom ), precision, flags );
1891
1893 return createGeosPolygon( static_cast<const QgsPolygon *>( geom ), precision, flags );
1894
1897 return nullptr;
1898 }
1899 }
1900 return nullptr;
1901}
1902
1903std::unique_ptr<QgsAbstractGeometry> QgsGeos::overlay( const QgsAbstractGeometry *geom, Overlay op, QString *errorMsg, const QgsGeometryParameters &parameters ) const
1904{
1905 if ( !mGeos || !geom )
1906 {
1907 return nullptr;
1908 }
1909
1910 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
1911 if ( !geosGeom )
1912 {
1913 return nullptr;
1914 }
1915
1916 const double gridSize = parameters.gridSize();
1917
1918 GEOSContextHandle_t context = QgsGeosContext::get();
1919 try
1920 {
1921 geos::unique_ptr opGeom;
1922 switch ( op )
1923 {
1924 case OverlayIntersection:
1925 if ( gridSize > 0 )
1926 {
1927 opGeom.reset( GEOSIntersectionPrec_r( context, mGeos.get(), geosGeom.get(), gridSize ) );
1928 }
1929 else
1930 {
1931 opGeom.reset( GEOSIntersection_r( context, mGeos.get(), geosGeom.get() ) );
1932 }
1933 break;
1934
1935 case OverlayDifference:
1936 if ( gridSize > 0 )
1937 {
1938 opGeom.reset( GEOSDifferencePrec_r( context, mGeos.get(), geosGeom.get(), gridSize ) );
1939 }
1940 else
1941 {
1942 opGeom.reset( GEOSDifference_r( context, mGeos.get(), geosGeom.get() ) );
1943 }
1944 break;
1945
1946 case OverlayUnion:
1947 {
1948 geos::unique_ptr unionGeometry;
1949 if ( gridSize > 0 )
1950 {
1951 unionGeometry.reset( GEOSUnionPrec_r( context, mGeos.get(), geosGeom.get(), gridSize ) );
1952 }
1953 else
1954 {
1955 unionGeometry.reset( GEOSUnion_r( context, mGeos.get(), geosGeom.get() ) );
1956 }
1957
1958 if ( unionGeometry && GEOSGeomTypeId_r( context, unionGeometry.get() ) == GEOS_MULTILINESTRING )
1959 {
1960 geos::unique_ptr mergedLines( GEOSLineMerge_r( context, unionGeometry.get() ) );
1961 if ( mergedLines )
1962 {
1963 unionGeometry = std::move( mergedLines );
1964 }
1965 }
1966
1967 opGeom = std::move( unionGeometry );
1968 }
1969 break;
1970
1971 case OverlaySymDifference:
1972 if ( gridSize > 0 )
1973 {
1974 opGeom.reset( GEOSSymDifferencePrec_r( context, mGeos.get(), geosGeom.get(), gridSize ) );
1975 }
1976 else
1977 {
1978 opGeom.reset( GEOSSymDifference_r( context, mGeos.get(), geosGeom.get() ) );
1979 }
1980 break;
1981 }
1982 return fromGeos( opGeom.get() );
1983 }
1984 catch ( QgsGeosException &e )
1985 {
1986 logError( u"GEOS"_s, e.what() );
1987 if ( errorMsg )
1988 {
1989 *errorMsg = e.what();
1990 }
1991 return nullptr;
1992 }
1993}
1994
1995bool QgsGeos::relation( const QgsAbstractGeometry *geom, Relation r, QString *errorMsg ) const
1996{
1997 if ( !mGeos || !geom )
1998 {
1999 return false;
2000 }
2001
2002 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2003 if ( !geosGeom )
2004 {
2005 return false;
2006 }
2007
2008 GEOSContextHandle_t context = QgsGeosContext::get();
2009 bool result = false;
2010 try
2011 {
2012 if ( mGeosPrepared ) //use faster version with prepared geometry
2013 {
2014 switch ( r )
2015 {
2016 case RelationIntersects:
2017 result = ( GEOSPreparedIntersects_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2018 break;
2019 case RelationTouches:
2020 result = ( GEOSPreparedTouches_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2021 break;
2022 case RelationCrosses:
2023 result = ( GEOSPreparedCrosses_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2024 break;
2025 case RelationWithin:
2026 result = ( GEOSPreparedWithin_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2027 break;
2028 case RelationContains:
2029 result = ( GEOSPreparedContains_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2030 break;
2031 case RelationDisjoint:
2032 result = ( GEOSPreparedDisjoint_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2033 break;
2034 case RelationOverlaps:
2035 result = ( GEOSPreparedOverlaps_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2036 break;
2037 }
2038 return result;
2039 }
2040
2041 switch ( r )
2042 {
2043 case RelationIntersects:
2044 result = ( GEOSIntersects_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2045 break;
2046 case RelationTouches:
2047 result = ( GEOSTouches_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2048 break;
2049 case RelationCrosses:
2050 result = ( GEOSCrosses_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2051 break;
2052 case RelationWithin:
2053 result = ( GEOSWithin_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2054 break;
2055 case RelationContains:
2056 result = ( GEOSContains_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2057 break;
2058 case RelationDisjoint:
2059 result = ( GEOSDisjoint_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2060 break;
2061 case RelationOverlaps:
2062 result = ( GEOSOverlaps_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2063 break;
2064 }
2065 }
2066 catch ( QgsGeosException &e )
2067 {
2068 logError( u"GEOS"_s, e.what() );
2069 if ( errorMsg )
2070 {
2071 *errorMsg = e.what();
2072 }
2073 return false;
2074 }
2075
2076 return result;
2077}
2078
2079QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, QString *errorMsg ) const
2080{
2081 if ( !mGeos )
2082 {
2083 return nullptr;
2084 }
2085
2087 try
2088 {
2089 geos.reset( GEOSBuffer_r( QgsGeosContext::get(), mGeos.get(), distance, segments ) );
2090 }
2091 CATCH_GEOS_WITH_ERRMSG( nullptr )
2092 return fromGeos( geos.get() ).release();
2093}
2094
2095QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, Qgis::EndCapStyle endCapStyle, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg ) const
2096{
2097 geos::unique_ptr geos = buffer( mGeos.get(), distance, segments, endCapStyle, joinStyle, miterLimit, errorMsg );
2098 return fromGeos( geos.get() ).release();
2099}
2100
2101geos::unique_ptr QgsGeos::buffer( const GEOSGeometry *geometry, double distance, int segments, Qgis::EndCapStyle endCapStyle, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg )
2102{
2103 if ( !geometry )
2104 {
2105 return nullptr;
2106 }
2107
2109 try
2110 {
2111 geos.reset( GEOSBufferWithStyle_r( QgsGeosContext::get(), geometry, distance, segments, static_cast< int >( endCapStyle ), static_cast< int >( joinStyle ), miterLimit ) );
2112 }
2113 CATCH_GEOS_WITH_ERRMSG( nullptr )
2114 return geos;
2115}
2116
2117QgsAbstractGeometry *QgsGeos::simplify( double tolerance, QString *errorMsg ) const
2118{
2119 if ( !mGeos )
2120 {
2121 return nullptr;
2122 }
2124 try
2125 {
2126 geos.reset( GEOSTopologyPreserveSimplify_r( QgsGeosContext::get(), mGeos.get(), tolerance ) );
2127 }
2128 CATCH_GEOS_WITH_ERRMSG( nullptr )
2129 return fromGeos( geos.get() ).release();
2130}
2131
2132QgsAbstractGeometry *QgsGeos::interpolate( double distance, QString *errorMsg ) const
2133{
2134 if ( !mGeos )
2135 {
2136 return nullptr;
2137 }
2139 try
2140 {
2141 geos.reset( GEOSInterpolate_r( QgsGeosContext::get(), mGeos.get(), distance ) );
2142 }
2143 CATCH_GEOS_WITH_ERRMSG( nullptr )
2144 return fromGeos( geos.get() ).release();
2145}
2146
2147QgsPoint *QgsGeos::centroid( QString *errorMsg ) const
2148{
2149 if ( !mGeos )
2150 {
2151 return nullptr;
2152 }
2153
2155 double x;
2156 double y;
2157
2158 GEOSContextHandle_t context = QgsGeosContext::get();
2159 try
2160 {
2161 geos.reset( GEOSGetCentroid_r( context, mGeos.get() ) );
2162
2163 if ( !geos )
2164 return nullptr;
2165
2166 GEOSGeomGetX_r( context, geos.get(), &x );
2167 GEOSGeomGetY_r( context, geos.get(), &y );
2168 }
2169 CATCH_GEOS_WITH_ERRMSG( nullptr )
2170
2171 return new QgsPoint( x, y );
2172}
2173
2174QgsAbstractGeometry *QgsGeos::envelope( QString *errorMsg ) const
2175{
2176 if ( !mGeos )
2177 {
2178 return nullptr;
2179 }
2181 try
2182 {
2183 geos.reset( GEOSEnvelope_r( QgsGeosContext::get(), mGeos.get() ) );
2184 }
2185 CATCH_GEOS_WITH_ERRMSG( nullptr )
2186 return fromGeos( geos.get() ).release();
2187}
2188
2189QgsPoint *QgsGeos::pointOnSurface( QString *errorMsg ) const
2190{
2191 if ( !mGeos )
2192 {
2193 return nullptr;
2194 }
2195
2196 double x;
2197 double y;
2198
2199 GEOSContextHandle_t context = QgsGeosContext::get();
2201 try
2202 {
2203 geos.reset( GEOSPointOnSurface_r( context, mGeos.get() ) );
2204
2205 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
2206 {
2207 return nullptr;
2208 }
2209
2210 GEOSGeomGetX_r( context, geos.get(), &x );
2211 GEOSGeomGetY_r( context, geos.get(), &y );
2212 }
2213 CATCH_GEOS_WITH_ERRMSG( nullptr )
2214
2215 return new QgsPoint( x, y );
2216}
2217
2218QgsAbstractGeometry *QgsGeos::convexHull( QString *errorMsg ) const
2219{
2220 if ( !mGeos )
2221 {
2222 return nullptr;
2223 }
2224
2225 try
2226 {
2227 geos::unique_ptr cHull( GEOSConvexHull_r( QgsGeosContext::get(), mGeos.get() ) );
2228 std::unique_ptr< QgsAbstractGeometry > cHullGeom = fromGeos( cHull.get() );
2229 return cHullGeom.release();
2230 }
2231 CATCH_GEOS_WITH_ERRMSG( nullptr )
2232}
2233
2234std::unique_ptr< QgsAbstractGeometry > QgsGeos::concaveHull( double targetPercent, bool allowHoles, QString *errorMsg ) const
2235{
2236#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 11
2237 ( void ) allowHoles;
2238 ( void ) targetPercent;
2239 ( void ) errorMsg;
2240 throw QgsNotSupportedException( QObject::tr( "Calculating concaveHull requires a QGIS build based on GEOS 3.11 or later" ) );
2241#else
2242 if ( !mGeos )
2243 {
2244 return nullptr;
2245 }
2246
2247 try
2248 {
2249 geos::unique_ptr concaveHull( GEOSConcaveHull_r( QgsGeosContext::get(), mGeos.get(), targetPercent, allowHoles ) );
2250 std::unique_ptr< QgsAbstractGeometry > concaveHullGeom = fromGeos( concaveHull.get() );
2251 return concaveHullGeom;
2252 }
2253 CATCH_GEOS_WITH_ERRMSG( nullptr )
2254#endif
2255}
2256
2257Qgis::CoverageValidityResult QgsGeos::validateCoverage( double gapWidth, std::unique_ptr<QgsAbstractGeometry> *invalidEdges, QString *errorMsg ) const
2258{
2259#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 12
2260 ( void ) gapWidth;
2261 ( void ) invalidEdges;
2262 ( void ) errorMsg;
2263 throw QgsNotSupportedException( QObject::tr( "Validating coverages requires a QGIS build based on GEOS 3.12 or later" ) );
2264#else
2265 if ( !mGeos )
2266 {
2267 if ( errorMsg )
2268 *errorMsg = u"Input geometry was not set"_s;
2270 }
2271
2272 GEOSContextHandle_t context = QgsGeosContext::get();
2273 try
2274 {
2275 GEOSGeometry *invalidEdgesGeos = nullptr;
2276 const int result = GEOSCoverageIsValid_r( context, mGeos.get(), gapWidth, invalidEdges ? &invalidEdgesGeos : nullptr );
2277 if ( invalidEdges && invalidEdgesGeos )
2278 {
2279 *invalidEdges = fromGeos( invalidEdgesGeos );
2280 }
2281 if ( invalidEdgesGeos )
2282 {
2283 GEOSGeom_destroy_r( context, invalidEdgesGeos );
2284 invalidEdgesGeos = nullptr;
2285 }
2286
2287 switch ( result )
2288 {
2289 case 0:
2291 case 1:
2293 case 2:
2294 break;
2295 }
2297 }
2299#endif
2300}
2301
2302std::unique_ptr<QgsAbstractGeometry> QgsGeos::simplifyCoverageVW( double tolerance, bool preserveBoundary, QString *errorMsg ) const
2303{
2304#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 12
2305 ( void ) tolerance;
2306 ( void ) preserveBoundary;
2307 ( void ) errorMsg;
2308 throw QgsNotSupportedException( QObject::tr( "Simplifying coverages requires a QGIS build based on GEOS 3.12 or later" ) );
2309#else
2310 if ( !mGeos )
2311 {
2312 if ( errorMsg )
2313 *errorMsg = u"Input geometry was not set"_s;
2314 return nullptr;
2315 }
2316
2317 try
2318 {
2319 geos::unique_ptr simplified( GEOSCoverageSimplifyVW_r( QgsGeosContext::get(), mGeos.get(), tolerance, preserveBoundary ? 1 : 0 ) );
2320 std::unique_ptr< QgsAbstractGeometry > simplifiedGeom = fromGeos( simplified.get() );
2321 return simplifiedGeom;
2322 }
2323 CATCH_GEOS_WITH_ERRMSG( nullptr )
2324#endif
2325}
2326
2327std::unique_ptr<QgsAbstractGeometry> QgsGeos::unionCoverage( QString *errorMsg ) const
2328{
2329 if ( !mGeos )
2330 {
2331 if ( errorMsg )
2332 *errorMsg = u"Input geometry was not set"_s;
2333 return nullptr;
2334 }
2335
2336 try
2337 {
2338 geos::unique_ptr unioned( GEOSCoverageUnion_r( QgsGeosContext::get(), mGeos.get() ) );
2339 std::unique_ptr< QgsAbstractGeometry > result = fromGeos( unioned.get() );
2340 return result;
2341 }
2342 CATCH_GEOS_WITH_ERRMSG( nullptr )
2343}
2344
2345bool QgsGeos::isValid( QString *errorMsg, const bool allowSelfTouchingHoles, QgsGeometry *errorLoc ) const
2346{
2347 if ( !mGeos )
2348 {
2349 if ( errorMsg )
2350 *errorMsg = QObject::tr( "QGIS geometry cannot be converted to a GEOS geometry", "GEOS Error" );
2351 return false;
2352 }
2353
2354 GEOSContextHandle_t context = QgsGeosContext::get();
2355 try
2356 {
2357 GEOSGeometry *g1 = nullptr;
2358 char *r = nullptr;
2359 char res = GEOSisValidDetail_r( context, mGeos.get(), allowSelfTouchingHoles ? GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE : 0, &r, &g1 );
2360 const bool invalid = res != 1;
2361
2362 QString error;
2363 if ( r )
2364 {
2365 error = QString( r );
2366 GEOSFree_r( context, r );
2367 }
2368
2369 if ( invalid && errorMsg )
2370 {
2371 // Copied from https://github.com/libgeos/geos/blob/main/src/operation/valid/TopologyValidationError.cpp
2372 static const std::map< QString, QString > sTranslatedErrors {
2373 { u"topology validation error"_s, QObject::tr( "Topology validation error", "GEOS Error" ) },
2374 { u"repeated point"_s, QObject::tr( "Repeated point", "GEOS Error" ) },
2375 { u"hole lies outside shell"_s, QObject::tr( "Hole lies outside shell", "GEOS Error" ) },
2376 { u"holes are nested"_s, QObject::tr( "Holes are nested", "GEOS Error" ) },
2377 { u"interior is disconnected"_s, QObject::tr( "Interior is disconnected", "GEOS Error" ) },
2378 { u"self-intersection"_s, QObject::tr( "Self-intersection", "GEOS Error" ) },
2379 { u"ring self-intersection"_s, QObject::tr( "Ring self-intersection", "GEOS Error" ) },
2380 { u"nested shells"_s, QObject::tr( "Nested shells", "GEOS Error" ) },
2381 { u"duplicate rings"_s, QObject::tr( "Duplicate rings", "GEOS Error" ) },
2382 { u"too few points in geometry component"_s, QObject::tr( "Too few points in geometry component", "GEOS Error" ) },
2383 { u"invalid coordinate"_s, QObject::tr( "Invalid coordinate", "GEOS Error" ) },
2384 { u"ring is not closed"_s, QObject::tr( "Ring is not closed", "GEOS Error" ) },
2385 };
2386
2387 const auto translatedError = sTranslatedErrors.find( error.toLower() );
2388 if ( translatedError != sTranslatedErrors.end() )
2389 *errorMsg = translatedError->second;
2390 else
2391 *errorMsg = error;
2392
2393 if ( g1 && errorLoc )
2394 {
2395 *errorLoc = geometryFromGeos( g1 );
2396 }
2397 else if ( g1 )
2398 {
2399 GEOSGeom_destroy_r( context, g1 );
2400 }
2401 }
2402 return !invalid;
2403 }
2404 CATCH_GEOS_WITH_ERRMSG( false )
2405}
2406
2407bool QgsGeos::isEqual( const QgsAbstractGeometry *geom, QString *errorMsg ) const
2408{
2409 if ( !mGeos || !geom )
2410 {
2411 return false;
2412 }
2413
2414 try
2415 {
2416 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2417 if ( !geosGeom )
2418 {
2419 return false;
2420 }
2421 bool equal = GEOSEquals_r( QgsGeosContext::get(), mGeos.get(), geosGeom.get() );
2422 return equal;
2423 }
2424 CATCH_GEOS_WITH_ERRMSG( false )
2425}
2426
2427bool QgsGeos::isEmpty( QString *errorMsg ) const
2428{
2429 if ( !mGeos )
2430 {
2431 return false;
2432 }
2433
2434 try
2435 {
2436 return GEOSisEmpty_r( QgsGeosContext::get(), mGeos.get() );
2437 }
2438 CATCH_GEOS_WITH_ERRMSG( false )
2439}
2440
2441bool QgsGeos::isSimple( QString *errorMsg ) const
2442{
2443 if ( !mGeos )
2444 {
2445 return false;
2446 }
2447
2448 try
2449 {
2450 return GEOSisSimple_r( QgsGeosContext::get(), mGeos.get() );
2451 }
2452 CATCH_GEOS_WITH_ERRMSG( false )
2453}
2454
2455GEOSCoordSequence *QgsGeos::createCoordinateSequence( const QgsCurve *curve, double precision, bool forceClose )
2456{
2457 GEOSContextHandle_t context = QgsGeosContext::get();
2458
2459 std::unique_ptr< QgsLineString > segmentized;
2461
2462 if ( !line )
2463 {
2464 segmentized.reset( curve->curveToLine() );
2465 line = segmentized.get();
2466 }
2467
2468 if ( !line )
2469 {
2470 return nullptr;
2471 }
2472 GEOSCoordSequence *coordSeq = nullptr;
2473
2474 const int numPoints = line->numPoints();
2475
2476 const bool hasZ = line->is3D();
2477
2478#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
2479 if ( qgsDoubleNear( precision, 0 ) )
2480 {
2481 if ( !forceClose || ( line->pointN( 0 ) == line->pointN( numPoints - 1 ) ) )
2482 {
2483 // use optimised method if we don't have to force close an open ring
2484 try
2485 {
2486 coordSeq = GEOSCoordSeq_copyFromArrays_r( context, line->xData(), line->yData(), line->zData(), nullptr, numPoints );
2487 if ( !coordSeq )
2488 {
2489 QgsDebugError( u"GEOS Exception: Could not create coordinate sequence for %1 points"_s.arg( numPoints ) );
2490 return nullptr;
2491 }
2492 }
2493 CATCH_GEOS( nullptr )
2494 }
2495 else
2496 {
2497 QVector< double > x = line->xVector();
2498 if ( numPoints > 0 )
2499 x.append( x.at( 0 ) );
2500 QVector< double > y = line->yVector();
2501 if ( numPoints > 0 )
2502 y.append( y.at( 0 ) );
2503 QVector< double > z = line->zVector();
2504 if ( hasZ && numPoints > 0 )
2505 z.append( z.at( 0 ) );
2506 try
2507 {
2508 coordSeq = GEOSCoordSeq_copyFromArrays_r( context, x.constData(), y.constData(), !hasZ ? nullptr : z.constData(), nullptr, numPoints + 1 );
2509 if ( !coordSeq )
2510 {
2511 QgsDebugError( u"GEOS Exception: Could not create closed coordinate sequence for %1 points"_s.arg( numPoints + 1 ) );
2512 return nullptr;
2513 }
2514 }
2515 CATCH_GEOS( nullptr )
2516 }
2517 return coordSeq;
2518 }
2519#endif
2520
2521 int coordDims = 2;
2522 const bool hasM = false; //line->isMeasure(); //disabled until geos supports m-coordinates
2523
2524 if ( hasZ )
2525 {
2526 ++coordDims;
2527 }
2528 if ( hasM )
2529 {
2530 ++coordDims;
2531 }
2532
2533 int numOutPoints = numPoints;
2534 if ( forceClose && ( line->pointN( 0 ) != line->pointN( numPoints - 1 ) ) )
2535 {
2536 ++numOutPoints;
2537 }
2538
2539 try
2540 {
2541 coordSeq = GEOSCoordSeq_create_r( context, numOutPoints, coordDims );
2542 if ( !coordSeq )
2543 {
2544 QgsDebugError( u"GEOS Exception: Could not create coordinate sequence for %1 points in %2 dimensions"_s.arg( numPoints ).arg( coordDims ) );
2545 return nullptr;
2546 }
2547
2548 const double *xData = line->xData();
2549 const double *yData = line->yData();
2550 const double *zData = hasZ ? line->zData() : nullptr;
2551 const double *mData = hasM ? line->mData() : nullptr;
2552
2553 if ( precision > 0. )
2554 {
2555 for ( int i = 0; i < numOutPoints; ++i )
2556 {
2557 if ( i >= numPoints )
2558 {
2559 // start reading back from start of line
2560 xData = line->xData();
2561 yData = line->yData();
2562 zData = hasZ ? line->zData() : nullptr;
2563 mData = hasM ? line->mData() : nullptr;
2564 }
2565 if ( hasZ )
2566 {
2567 GEOSCoordSeq_setXYZ_r( context, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision, std::round( *zData++ / precision ) * precision );
2568 }
2569 else
2570 {
2571 GEOSCoordSeq_setXY_r( context, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision );
2572 }
2573 if ( hasM )
2574 {
2575 GEOSCoordSeq_setOrdinate_r( context, coordSeq, i, 3, *mData++ );
2576 }
2577 }
2578 }
2579 else
2580 {
2581 for ( int i = 0; i < numOutPoints; ++i )
2582 {
2583 if ( i >= numPoints )
2584 {
2585 // start reading back from start of line
2586 xData = line->xData();
2587 yData = line->yData();
2588 zData = hasZ ? line->zData() : nullptr;
2589 mData = hasM ? line->mData() : nullptr;
2590 }
2591 if ( hasZ )
2592 {
2593 GEOSCoordSeq_setXYZ_r( context, coordSeq, i, *xData++, *yData++, *zData++ );
2594 }
2595 else
2596 {
2597 GEOSCoordSeq_setXY_r( context, coordSeq, i, *xData++, *yData++ );
2598 }
2599 if ( hasM )
2600 {
2601 GEOSCoordSeq_setOrdinate_r( context, coordSeq, i, 3, *mData++ );
2602 }
2603 }
2604 }
2605 }
2606 CATCH_GEOS( nullptr )
2607
2608 return coordSeq;
2609}
2610
2611geos::unique_ptr QgsGeos::createGeosPoint( const QgsAbstractGeometry *point, int coordDims, double precision, Qgis::GeosCreationFlags )
2612{
2613 const QgsPoint *pt = qgsgeometry_cast<const QgsPoint *>( point );
2614 if ( !pt )
2615 return nullptr;
2616
2617 return createGeosPointXY( pt->x(), pt->y(), pt->is3D(), pt->z(), pt->isMeasure(), pt->m(), coordDims, precision );
2618}
2619
2620geos::unique_ptr QgsGeos::createGeosPointXY( double x, double y, bool hasZ, double z, bool hasM, double m, int coordDims, double precision, Qgis::GeosCreationFlags )
2621{
2622 Q_UNUSED( hasM )
2623 Q_UNUSED( m )
2624
2625 geos::unique_ptr geosPoint;
2626 GEOSContextHandle_t context = QgsGeosContext::get();
2627 try
2628 {
2629 if ( coordDims == 2 )
2630 {
2631 // optimised constructor
2632 if ( precision > 0. )
2633 geosPoint.reset( GEOSGeom_createPointFromXY_r( context, std::round( x / precision ) * precision, std::round( y / precision ) * precision ) );
2634 else
2635 geosPoint.reset( GEOSGeom_createPointFromXY_r( context, x, y ) );
2636 return geosPoint;
2637 }
2638
2639 GEOSCoordSequence *coordSeq = GEOSCoordSeq_create_r( context, 1, coordDims );
2640 if ( !coordSeq )
2641 {
2642 QgsDebugError( u"GEOS Exception: Could not create coordinate sequence for point with %1 dimensions"_s.arg( coordDims ) );
2643 return nullptr;
2644 }
2645 if ( precision > 0. )
2646 {
2647 GEOSCoordSeq_setX_r( context, coordSeq, 0, std::round( x / precision ) * precision );
2648 GEOSCoordSeq_setY_r( context, coordSeq, 0, std::round( y / precision ) * precision );
2649 if ( hasZ )
2650 {
2651 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 2, std::round( z / precision ) * precision );
2652 }
2653 }
2654 else
2655 {
2656 GEOSCoordSeq_setX_r( context, coordSeq, 0, x );
2657 GEOSCoordSeq_setY_r( context, coordSeq, 0, y );
2658 if ( hasZ )
2659 {
2660 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 2, z );
2661 }
2662 }
2663#if 0 //disabled until geos supports m-coordinates
2664 if ( hasM )
2665 {
2666 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 3, m );
2667 }
2668#endif
2669 geosPoint.reset( GEOSGeom_createPoint_r( context, coordSeq ) );
2670 }
2671 CATCH_GEOS( nullptr )
2672 return geosPoint;
2673}
2674
2675geos::unique_ptr QgsGeos::createGeosLinestring( const QgsAbstractGeometry *curve, double precision, Qgis::GeosCreationFlags )
2676{
2677 const QgsCurve *c = qgsgeometry_cast<const QgsCurve *>( curve );
2678 if ( !c )
2679 return nullptr;
2680
2681 GEOSCoordSequence *coordSeq = createCoordinateSequence( c, precision );
2682 if ( !coordSeq )
2683 return nullptr;
2684
2685 geos::unique_ptr geosGeom;
2686 try
2687 {
2688 geosGeom.reset( GEOSGeom_createLineString_r( QgsGeosContext::get(), coordSeq ) );
2689 }
2690 CATCH_GEOS( nullptr )
2691 return geosGeom;
2692}
2693
2694geos::unique_ptr QgsGeos::createGeosPolygon( const QgsAbstractGeometry *poly, double precision, Qgis::GeosCreationFlags flags )
2695{
2696 const QgsCurvePolygon *polygon = qgsgeometry_cast<const QgsCurvePolygon *>( poly );
2697 if ( !polygon )
2698 return nullptr;
2699
2700 const QgsCurve *exteriorRing = polygon->exteriorRing();
2701 if ( !exteriorRing )
2702 {
2703 return nullptr;
2704 }
2705
2706 GEOSContextHandle_t context = QgsGeosContext::get();
2707 geos::unique_ptr geosPolygon;
2708 try
2709 {
2710 geos::unique_ptr exteriorRingGeos( GEOSGeom_createLinearRing_r( context, createCoordinateSequence( exteriorRing, precision, true ) ) );
2711
2712 const int nInteriorRings = polygon->numInteriorRings();
2713 QList< const QgsCurve * > holesToExport;
2714 holesToExport.reserve( nInteriorRings );
2715 for ( int i = 0; i < nInteriorRings; ++i )
2716 {
2717 const QgsCurve *interiorRing = polygon->interiorRing( i );
2718 if ( !( flags & Qgis::GeosCreationFlag::SkipEmptyInteriorRings ) || !interiorRing->isEmpty() )
2719 {
2720 holesToExport << interiorRing;
2721 }
2722 }
2723
2724 GEOSGeometry **holes = nullptr;
2725 if ( !holesToExport.empty() )
2726 {
2727 holes = new GEOSGeometry *[holesToExport.size()];
2728 for ( int i = 0; i < holesToExport.size(); ++i )
2729 {
2730 holes[i] = GEOSGeom_createLinearRing_r( context, createCoordinateSequence( holesToExport[i], precision, true ) );
2731 }
2732 }
2733
2734 geosPolygon.reset( GEOSGeom_createPolygon_r( context, exteriorRingGeos.release(), holes, holesToExport.size() ) );
2735 delete[] holes;
2736 }
2737 CATCH_GEOS( nullptr )
2738
2739 return geosPolygon;
2740}
2741
2742geos::unique_ptr QgsGeos::offsetCurve( const GEOSGeometry *geometry, double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg )
2743{
2744 if ( !geometry )
2745 return nullptr;
2746
2747 geos::unique_ptr offset;
2748 try
2749 {
2750 // Force quadrant segments to be at least 8, see
2751 // https://github.com/qgis/QGIS/issues/53165#issuecomment-1563470832
2752 if ( segments < 8 )
2753 segments = 8;
2754 offset.reset( GEOSOffsetCurve_r( QgsGeosContext::get(), geometry, distance, segments, static_cast< int >( joinStyle ), miterLimit ) );
2755 }
2756 CATCH_GEOS_WITH_ERRMSG( nullptr )
2757 return offset;
2758}
2759
2760QgsAbstractGeometry *QgsGeos::offsetCurve( double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg ) const
2761{
2762 geos::unique_ptr res = offsetCurve( mGeos.get(), distance, segments, joinStyle, miterLimit, errorMsg );
2763 if ( !res )
2764 return nullptr;
2765
2766 return fromGeos( res.get() ).release();
2767}
2768
2769std::unique_ptr<QgsAbstractGeometry> QgsGeos::singleSidedBuffer( double distance, int segments, Qgis::BufferSide side, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg ) const
2770{
2771 if ( !mGeos )
2772 {
2773 return nullptr;
2774 }
2775
2777 GEOSContextHandle_t context = QgsGeosContext::get();
2778 try
2779 {
2780 geos::buffer_params_unique_ptr bp( GEOSBufferParams_create_r( context ) );
2781 GEOSBufferParams_setSingleSided_r( context, bp.get(), 1 );
2782 GEOSBufferParams_setQuadrantSegments_r( context, bp.get(), segments );
2783 GEOSBufferParams_setJoinStyle_r( context, bp.get(), static_cast< int >( joinStyle ) );
2784 GEOSBufferParams_setMitreLimit_r( context, bp.get(), miterLimit ); //#spellok
2785
2786 if ( side == Qgis::BufferSide::Right )
2787 {
2788 distance = -distance;
2789 }
2790 geos.reset( GEOSBufferWithParams_r( context, mGeos.get(), bp.get(), distance ) );
2791 }
2792 CATCH_GEOS_WITH_ERRMSG( nullptr )
2793 return fromGeos( geos.get() );
2794}
2795
2796std::unique_ptr<QgsAbstractGeometry> QgsGeos::maximumInscribedCircle( double tolerance, QString *errorMsg ) const
2797{
2798 if ( !mGeos )
2799 {
2800 return nullptr;
2801 }
2802
2804 try
2805 {
2806 geos.reset( GEOSMaximumInscribedCircle_r( QgsGeosContext::get(), mGeos.get(), tolerance ) );
2807 }
2808 CATCH_GEOS_WITH_ERRMSG( nullptr )
2809 return fromGeos( geos.get() );
2810}
2811
2812std::unique_ptr<QgsAbstractGeometry> QgsGeos::largestEmptyCircle( double tolerance, const QgsAbstractGeometry *boundary, QString *errorMsg ) const
2813{
2814 if ( !mGeos )
2815 {
2816 return nullptr;
2817 }
2818
2820 try
2821 {
2822 geos::unique_ptr boundaryGeos;
2823 if ( boundary )
2824 boundaryGeos = asGeos( boundary );
2825
2826 geos.reset( GEOSLargestEmptyCircle_r( QgsGeosContext::get(), mGeos.get(), boundaryGeos.get(), tolerance ) );
2827 }
2828 CATCH_GEOS_WITH_ERRMSG( nullptr )
2829 return fromGeos( geos.get() );
2830}
2831
2832std::unique_ptr<QgsAbstractGeometry> QgsGeos::minimumWidth( QString *errorMsg ) const
2833{
2834 if ( !mGeos )
2835 {
2836 return nullptr;
2837 }
2838
2840 try
2841 {
2842 geos.reset( GEOSMinimumWidth_r( QgsGeosContext::get(), mGeos.get() ) );
2843 }
2844 CATCH_GEOS_WITH_ERRMSG( nullptr )
2845 return fromGeos( geos.get() );
2846}
2847
2848double QgsGeos::minimumClearance( QString *errorMsg ) const
2849{
2850 if ( !mGeos )
2851 {
2852 return std::numeric_limits< double >::quiet_NaN();
2853 }
2854
2856 double res = 0;
2857 try
2858 {
2859 if ( GEOSMinimumClearance_r( QgsGeosContext::get(), mGeos.get(), &res ) != 0 )
2860 return std::numeric_limits< double >::quiet_NaN();
2861 }
2862 CATCH_GEOS_WITH_ERRMSG( std::numeric_limits< double >::quiet_NaN() )
2863 return res;
2864}
2865
2866std::unique_ptr<QgsAbstractGeometry> QgsGeos::minimumClearanceLine( QString *errorMsg ) const
2867{
2868 if ( !mGeos )
2869 {
2870 return nullptr;
2871 }
2872
2874 try
2875 {
2876 geos.reset( GEOSMinimumClearanceLine_r( QgsGeosContext::get(), mGeos.get() ) );
2877 }
2878 CATCH_GEOS_WITH_ERRMSG( nullptr )
2879 return fromGeos( geos.get() );
2880}
2881
2882std::unique_ptr<QgsAbstractGeometry> QgsGeos::node( QString *errorMsg ) const
2883{
2884 if ( !mGeos )
2885 {
2886 return nullptr;
2887 }
2888
2890 try
2891 {
2892 geos.reset( GEOSNode_r( QgsGeosContext::get(), mGeos.get() ) );
2893 }
2894 CATCH_GEOS_WITH_ERRMSG( nullptr )
2895 return fromGeos( geos.get() );
2896}
2897
2898std::unique_ptr<QgsAbstractGeometry> QgsGeos::sharedPaths( const QgsAbstractGeometry *other, QString *errorMsg ) const
2899{
2900 if ( !mGeos || !other )
2901 {
2902 return nullptr;
2903 }
2904
2906 try
2907 {
2908 geos::unique_ptr otherGeos = asGeos( other );
2909 if ( !otherGeos )
2910 return nullptr;
2911
2912 geos.reset( GEOSSharedPaths_r( QgsGeosContext::get(), mGeos.get(), otherGeos.get() ) );
2913 }
2914 CATCH_GEOS_WITH_ERRMSG( nullptr )
2915 return fromGeos( geos.get() );
2916}
2917
2918std::unique_ptr<QgsAbstractGeometry> QgsGeos::reshapeGeometry( const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg ) const
2919{
2920 if ( !mGeos || mGeometry->dimension() == 0 )
2921 {
2922 if ( errorCode )
2923 {
2924 *errorCode = InvalidBaseGeometry;
2925 }
2926 return nullptr;
2927 }
2928
2929 if ( reshapeWithLine.numPoints() < 2 )
2930 {
2931 if ( errorCode )
2932 {
2933 *errorCode = InvalidInput;
2934 }
2935 return nullptr;
2936 }
2937
2938 geos::unique_ptr reshapeLineGeos = createGeosLinestring( &reshapeWithLine, mPrecision );
2939
2940 GEOSContextHandle_t context = QgsGeosContext::get();
2941 //single or multi?
2942 int numGeoms = GEOSGetNumGeometries_r( context, mGeos.get() );
2943 if ( numGeoms == -1 )
2944 {
2945 if ( errorCode )
2946 {
2947 *errorCode = InvalidBaseGeometry;
2948 }
2949 return nullptr;
2950 }
2951
2952 bool isMultiGeom = false;
2953 int geosTypeId = GEOSGeomTypeId_r( context, mGeos.get() );
2954 if ( geosTypeId == GEOS_MULTILINESTRING || geosTypeId == GEOS_MULTIPOLYGON )
2955 isMultiGeom = true;
2956
2957 bool isLine = ( mGeometry->dimension() == 1 );
2958
2959 if ( !isMultiGeom )
2960 {
2961 geos::unique_ptr reshapedGeometry;
2962 if ( isLine )
2963 {
2964 reshapedGeometry = reshapeLine( mGeos.get(), reshapeLineGeos.get(), mPrecision );
2965 }
2966 else
2967 {
2968 reshapedGeometry = reshapePolygon( mGeos.get(), reshapeLineGeos.get(), mPrecision );
2969 }
2970
2971 if ( errorCode )
2972 {
2973 if ( reshapedGeometry )
2974 *errorCode = Success;
2975 else
2976 *errorCode = NothingHappened;
2977 }
2978
2979 std::unique_ptr< QgsAbstractGeometry > reshapeResult = fromGeos( reshapedGeometry.get() );
2980 return reshapeResult;
2981 }
2982 else
2983 {
2984 try
2985 {
2986 //call reshape for each geometry part and replace mGeos with new geometry if reshape took place
2987 bool reshapeTookPlace = false;
2988
2989 geos::unique_ptr currentReshapeGeometry;
2990 GEOSGeometry **newGeoms = new GEOSGeometry *[numGeoms];
2991
2992 for ( int i = 0; i < numGeoms; ++i )
2993 {
2994 if ( isLine )
2995 currentReshapeGeometry = reshapeLine( GEOSGetGeometryN_r( context, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
2996 else
2997 currentReshapeGeometry = reshapePolygon( GEOSGetGeometryN_r( context, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
2998
2999 if ( currentReshapeGeometry )
3000 {
3001 newGeoms[i] = currentReshapeGeometry.release();
3002 reshapeTookPlace = true;
3003 }
3004 else
3005 {
3006 newGeoms[i] = GEOSGeom_clone_r( context, GEOSGetGeometryN_r( context, mGeos.get(), i ) );
3007 }
3008 }
3009
3010 geos::unique_ptr newMultiGeom;
3011 if ( isLine )
3012 {
3013 newMultiGeom.reset( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, newGeoms, numGeoms ) );
3014 }
3015 else //multipolygon
3016 {
3017 newMultiGeom.reset( GEOSGeom_createCollection_r( context, GEOS_MULTIPOLYGON, newGeoms, numGeoms ) );
3018 }
3019
3020 delete[] newGeoms;
3021 if ( !newMultiGeom )
3022 {
3023 if ( errorCode )
3024 {
3025 *errorCode = EngineError;
3026 }
3027 return nullptr;
3028 }
3029
3030 if ( reshapeTookPlace )
3031 {
3032 if ( errorCode )
3033 *errorCode = Success;
3034 std::unique_ptr< QgsAbstractGeometry > reshapedMultiGeom = fromGeos( newMultiGeom.get() );
3035 return reshapedMultiGeom;
3036 }
3037 else
3038 {
3039 if ( errorCode )
3040 {
3041 *errorCode = NothingHappened;
3042 }
3043 return nullptr;
3044 }
3045 }
3046 CATCH_GEOS_WITH_ERRMSG( nullptr )
3047 }
3048}
3049
3050std::unique_ptr< QgsAbstractGeometry > QgsGeos::mergeLines( QString *errorMsg, const QgsGeometryParameters &parameters ) const
3051{
3052 if ( !mGeos )
3053 {
3054 return nullptr;
3055 }
3056
3057 GEOSContextHandle_t context = QgsGeosContext::get();
3058 if ( GEOSGeomTypeId_r( context, mGeos.get() ) != GEOS_MULTILINESTRING )
3059 return nullptr;
3060
3062 try
3063 {
3064 double gridSize = parameters.gridSize();
3065 if ( gridSize > 0 )
3066 {
3067 geos::unique_ptr geosFixedSize( GEOSGeom_setPrecision_r( context, mGeos.get(), gridSize, 0 ) );
3068 geos.reset( GEOSLineMerge_r( context, geosFixedSize.get() ) );
3069 }
3070 else
3071 geos.reset( GEOSLineMerge_r( context, mGeos.get() ) );
3072 }
3073 CATCH_GEOS_WITH_ERRMSG( nullptr )
3074 return fromGeos( geos.get() );
3075}
3076
3077std::unique_ptr<QgsAbstractGeometry> QgsGeos::closestPoint( const QgsGeometry &other, QString *errorMsg ) const
3078{
3079 if ( !mGeos || isEmpty() || other.isEmpty() )
3080 {
3081 return nullptr;
3082 }
3083
3084 geos::unique_ptr otherGeom( asGeos( other.constGet(), mPrecision ) );
3085 if ( !otherGeom )
3086 {
3087 return nullptr;
3088 }
3089
3090 GEOSContextHandle_t context = QgsGeosContext::get();
3091 double nx = 0.0;
3092 double ny = 0.0;
3093 try
3094 {
3096 if ( mGeosPrepared ) // use faster version with prepared geometry
3097 {
3098 nearestCoord.reset( GEOSPreparedNearestPoints_r( context, mGeosPrepared.get(), otherGeom.get() ) );
3099 }
3100 else
3101 {
3102 nearestCoord.reset( GEOSNearestPoints_r( context, mGeos.get(), otherGeom.get() ) );
3103 }
3104
3105 ( void ) GEOSCoordSeq_getX_r( context, nearestCoord.get(), 0, &nx );
3106 ( void ) GEOSCoordSeq_getY_r( context, nearestCoord.get(), 0, &ny );
3107 }
3108 catch ( QgsGeosException &e )
3109 {
3110 logError( u"GEOS"_s, e.what() );
3111 if ( errorMsg )
3112 {
3113 *errorMsg = e.what();
3114 }
3115 return nullptr;
3116 }
3117
3118 return std::make_unique< QgsPoint >( nx, ny );
3119}
3120
3121std::unique_ptr<QgsAbstractGeometry> QgsGeos::shortestLine( const QgsGeometry &other, QString *errorMsg ) const
3122{
3123 if ( !mGeos || other.isEmpty() )
3124 {
3125 return nullptr;
3126 }
3127
3128 return shortestLine( other.constGet(), errorMsg );
3129}
3130
3131std::unique_ptr< QgsAbstractGeometry > QgsGeos::shortestLine( const QgsAbstractGeometry *other, QString *errorMsg ) const
3132{
3133 if ( !other || other->isEmpty() )
3134 return nullptr;
3135
3136 geos::unique_ptr otherGeom( asGeos( other, mPrecision ) );
3137 if ( !otherGeom )
3138 {
3139 return nullptr;
3140 }
3141
3142 GEOSContextHandle_t context = QgsGeosContext::get();
3143 double nx1 = 0.0;
3144 double ny1 = 0.0;
3145 double nx2 = 0.0;
3146 double ny2 = 0.0;
3147 try
3148 {
3149 geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( context, mGeos.get(), otherGeom.get() ) );
3150
3151 if ( !nearestCoord )
3152 {
3153 if ( errorMsg )
3154 *errorMsg = u"GEOS returned no nearest points"_s;
3155 return nullptr;
3156 }
3157
3158 ( void ) GEOSCoordSeq_getX_r( context, nearestCoord.get(), 0, &nx1 );
3159 ( void ) GEOSCoordSeq_getY_r( context, nearestCoord.get(), 0, &ny1 );
3160 ( void ) GEOSCoordSeq_getX_r( context, nearestCoord.get(), 1, &nx2 );
3161 ( void ) GEOSCoordSeq_getY_r( context, nearestCoord.get(), 1, &ny2 );
3162 }
3163 catch ( QgsGeosException &e )
3164 {
3165 logError( u"GEOS"_s, e.what() );
3166 if ( errorMsg )
3167 {
3168 *errorMsg = e.what();
3169 }
3170 return nullptr;
3171 }
3172
3173 auto line = std::make_unique< QgsLineString >();
3174 line->addVertex( QgsPoint( nx1, ny1 ) );
3175 line->addVertex( QgsPoint( nx2, ny2 ) );
3176 return line;
3177}
3178
3179double QgsGeos::lineLocatePoint( const QgsPoint &point, QString *errorMsg ) const
3180{
3181 if ( !mGeos )
3182 {
3183 return -1;
3184 }
3185
3186 geos::unique_ptr otherGeom( asGeos( &point, mPrecision ) );
3187 if ( !otherGeom )
3188 {
3189 return -1;
3190 }
3191
3192 double distance = -1;
3193 try
3194 {
3195 distance = GEOSProject_r( QgsGeosContext::get(), mGeos.get(), otherGeom.get() );
3196 }
3197 catch ( QgsGeosException &e )
3198 {
3199 logError( u"GEOS"_s, e.what() );
3200 if ( errorMsg )
3201 {
3202 *errorMsg = e.what();
3203 }
3204 return -1;
3205 }
3206
3207 return distance;
3208}
3209
3210double QgsGeos::lineLocatePoint( double x, double y, QString *errorMsg ) const
3211{
3212 if ( !mGeos )
3213 {
3214 return -1;
3215 }
3216
3217 geos::unique_ptr point = createGeosPointXY( x, y, false, 0, false, 0, 2, 0 );
3218 if ( !point )
3219 return false;
3220
3221 double distance = -1;
3222 try
3223 {
3224 distance = GEOSProject_r( QgsGeosContext::get(), mGeos.get(), point.get() );
3225 }
3226 catch ( QgsGeosException &e )
3227 {
3228 logError( u"GEOS"_s, e.what() );
3229 if ( errorMsg )
3230 {
3231 *errorMsg = e.what();
3232 }
3233 return -1;
3234 }
3235
3236 return distance;
3237}
3238
3239QgsGeometry QgsGeos::polygonize( const QVector<const QgsAbstractGeometry *> &geometries, QString *errorMsg )
3240{
3241 GEOSGeometry **const lineGeosGeometries = new GEOSGeometry *[geometries.size()];
3242 int validLines = 0;
3243 for ( const QgsAbstractGeometry *g : geometries )
3244 {
3245 geos::unique_ptr l = asGeos( g );
3246 if ( l )
3247 {
3248 lineGeosGeometries[validLines] = l.release();
3249 validLines++;
3250 }
3251 }
3252
3253 GEOSContextHandle_t context = QgsGeosContext::get();
3254 try
3255 {
3256 geos::unique_ptr result( GEOSPolygonize_r( context, lineGeosGeometries, validLines ) );
3257 for ( int i = 0; i < validLines; ++i )
3258 {
3259 GEOSGeom_destroy_r( context, lineGeosGeometries[i] );
3260 }
3261 delete[] lineGeosGeometries;
3262 return QgsGeometry( fromGeos( result.get() ) );
3263 }
3264 catch ( QgsGeosException &e )
3265 {
3266 if ( errorMsg )
3267 {
3268 *errorMsg = e.what();
3269 }
3270 for ( int i = 0; i < validLines; ++i )
3271 {
3272 GEOSGeom_destroy_r( context, lineGeosGeometries[i] );
3273 }
3274 delete[] lineGeosGeometries;
3275 return QgsGeometry();
3276 }
3277}
3278
3279std::unique_ptr<QgsAbstractGeometry> QgsGeos::voronoiDiagram( const QgsAbstractGeometry *extent, double tolerance, bool edgesOnly, QString *errorMsg ) const
3280{
3281 if ( !mGeos )
3282 {
3283 return nullptr;
3284 }
3285
3286 geos::unique_ptr extentGeosGeom;
3287 if ( extent )
3288 {
3289 extentGeosGeom = asGeos( extent, mPrecision );
3290 if ( !extentGeosGeom )
3291 {
3292 return nullptr;
3293 }
3294 }
3295
3297 GEOSContextHandle_t context = QgsGeosContext::get();
3298 try
3299 {
3300 geos.reset( GEOSVoronoiDiagram_r( context, mGeos.get(), extentGeosGeom.get(), tolerance, edgesOnly ) );
3301
3302 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3303 {
3304 return nullptr;
3305 }
3306
3307 return fromGeos( geos.get() );
3308 }
3309 CATCH_GEOS_WITH_ERRMSG( nullptr )
3310}
3311
3312std::unique_ptr<QgsAbstractGeometry> QgsGeos::delaunayTriangulation( double tolerance, bool edgesOnly, QString *errorMsg ) const
3313{
3314 if ( !mGeos )
3315 {
3316 return nullptr;
3317 }
3318
3319 GEOSContextHandle_t context = QgsGeosContext::get();
3321 try
3322 {
3323 geos.reset( GEOSDelaunayTriangulation_r( context, mGeos.get(), tolerance, edgesOnly ) );
3324
3325 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3326 {
3327 return nullptr;
3328 }
3329
3330 return fromGeos( geos.get() );
3331 }
3332 CATCH_GEOS_WITH_ERRMSG( nullptr )
3333}
3334
3335std::unique_ptr<QgsAbstractGeometry> QgsGeos::constrainedDelaunayTriangulation( QString *errorMsg ) const
3336{
3337#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 11
3338 ( void ) errorMsg;
3339 throw QgsNotSupportedException( QObject::tr( "Calculating constrainedDelaunayTriangulation requires a QGIS build based on GEOS 3.11 or later" ) );
3340#else
3341 if ( !mGeos )
3342 {
3343 return nullptr;
3344 }
3345
3347 GEOSContextHandle_t context = QgsGeosContext::get();
3348 try
3349 {
3350 geos.reset( GEOSConstrainedDelaunayTriangulation_r( context, mGeos.get() ) );
3351
3352 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3353 {
3354 return nullptr;
3355 }
3356
3357 std::unique_ptr< QgsAbstractGeometry > res = fromGeos( geos.get() );
3358 if ( const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( res.get() ) )
3359 {
3360 return std::unique_ptr< QgsAbstractGeometry >( collection->extractPartsByType( Qgis::WkbType::Polygon, true ) );
3361 }
3362 else
3363 {
3364 return res;
3365 }
3366 }
3367 CATCH_GEOS_WITH_ERRMSG( nullptr )
3368#endif
3369}
3370
3372static bool _linestringEndpoints( const GEOSGeometry *linestring, double &x1, double &y1, double &x2, double &y2 )
3373{
3374 GEOSContextHandle_t context = QgsGeosContext::get();
3375 const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( context, linestring );
3376 if ( !coordSeq )
3377 return false;
3378
3379 unsigned int coordSeqSize;
3380 if ( GEOSCoordSeq_getSize_r( context, coordSeq, &coordSeqSize ) == 0 )
3381 return false;
3382
3383 if ( coordSeqSize < 2 )
3384 return false;
3385
3386 GEOSCoordSeq_getX_r( context, coordSeq, 0, &x1 );
3387 GEOSCoordSeq_getY_r( context, coordSeq, 0, &y1 );
3388 GEOSCoordSeq_getX_r( context, coordSeq, coordSeqSize - 1, &x2 );
3389 GEOSCoordSeq_getY_r( context, coordSeq, coordSeqSize - 1, &y2 );
3390 return true;
3391}
3392
3393
3395static geos::unique_ptr _mergeLinestrings( const GEOSGeometry *line1, const GEOSGeometry *line2, const QgsPointXY &intersectionPoint )
3396{
3397 double x1, y1, x2, y2;
3398 if ( !_linestringEndpoints( line1, x1, y1, x2, y2 ) )
3399 return nullptr;
3400
3401 double rx1, ry1, rx2, ry2;
3402 if ( !_linestringEndpoints( line2, rx1, ry1, rx2, ry2 ) )
3403 return nullptr;
3404
3405 bool intersectionAtOrigLineEndpoint = ( intersectionPoint.x() == x1 && intersectionPoint.y() == y1 ) != ( intersectionPoint.x() == x2 && intersectionPoint.y() == y2 );
3406 bool intersectionAtReshapeLineEndpoint = ( intersectionPoint.x() == rx1 && intersectionPoint.y() == ry1 ) || ( intersectionPoint.x() == rx2 && intersectionPoint.y() == ry2 );
3407
3408 GEOSContextHandle_t context = QgsGeosContext::get();
3409 // the intersection must be at the begin/end of both lines
3410 if ( intersectionAtOrigLineEndpoint && intersectionAtReshapeLineEndpoint )
3411 {
3412 geos::unique_ptr g1( GEOSGeom_clone_r( context, line1 ) );
3413 geos::unique_ptr g2( GEOSGeom_clone_r( context, line2 ) );
3414 GEOSGeometry *geoms[2] = { g1.release(), g2.release() };
3415 geos::unique_ptr multiGeom( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, geoms, 2 ) );
3416 geos::unique_ptr res( GEOSLineMerge_r( context, multiGeom.get() ) );
3417
3418 //keep the original orientation if the result has a start or end point in common with the original line
3419 //and this point is not the start or the end point for both lines
3420 double x1res, y1res, x2res, y2res;
3421 if ( !_linestringEndpoints( res.get(), x1res, y1res, x2res, y2res ) )
3422 return nullptr;
3423 if ( ( x1res == x2 && y1res == y2 ) || ( x2res == x1 && y2res == y1 ) )
3424 res.reset( GEOSReverse_r( context, res.get() ) );
3425
3426 return res;
3427 }
3428 else
3429 return nullptr;
3430}
3431
3432
3433geos::unique_ptr QgsGeos::reshapeLine( const GEOSGeometry *line, const GEOSGeometry *reshapeLineGeos, double precision )
3434{
3435 if ( !line || !reshapeLineGeos )
3436 return nullptr;
3437
3438 bool atLeastTwoIntersections = false;
3439 bool oneIntersection = false;
3440 QgsPointXY oneIntersectionPoint;
3441
3442 GEOSContextHandle_t context = QgsGeosContext::get();
3443 try
3444 {
3445 //make sure there are at least two intersection between line and reshape geometry
3446 geos::unique_ptr intersectGeom( GEOSIntersection_r( context, line, reshapeLineGeos ) );
3447 if ( intersectGeom )
3448 {
3449 const int geomType = GEOSGeomTypeId_r( context, intersectGeom.get() );
3450 atLeastTwoIntersections = ( geomType == GEOS_MULTIPOINT && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 1 )
3451 || ( geomType == GEOS_GEOMETRYCOLLECTION && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 0 ) // a collection implies at least two points!
3452 || ( geomType == GEOS_MULTILINESTRING && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 0 );
3453 // one point is enough when extending line at its endpoint
3454 if ( GEOSGeomTypeId_r( context, intersectGeom.get() ) == GEOS_POINT )
3455 {
3456 const GEOSCoordSequence *intersectionCoordSeq = GEOSGeom_getCoordSeq_r( context, intersectGeom.get() );
3457 double xi, yi;
3458 GEOSCoordSeq_getX_r( context, intersectionCoordSeq, 0, &xi );
3459 GEOSCoordSeq_getY_r( context, intersectionCoordSeq, 0, &yi );
3460 oneIntersection = true;
3461 oneIntersectionPoint = QgsPointXY( xi, yi );
3462 }
3463 }
3464 }
3465 catch ( QgsGeosException & )
3466 {
3467 atLeastTwoIntersections = false;
3468 }
3469
3470 // special case when extending line at its endpoint
3471 if ( oneIntersection )
3472 return _mergeLinestrings( line, reshapeLineGeos, oneIntersectionPoint );
3473
3474 if ( !atLeastTwoIntersections )
3475 return nullptr;
3476
3477 //begin and end point of original line
3478 double x1, y1, x2, y2;
3479 if ( !_linestringEndpoints( line, x1, y1, x2, y2 ) )
3480 return nullptr;
3481
3482 geos::unique_ptr beginLineVertex = createGeosPointXY( x1, y1, false, 0, false, 0, 2, precision );
3483 geos::unique_ptr endLineVertex = createGeosPointXY( x2, y2, false, 0, false, 0, 2, precision );
3484
3485 bool isRing = false;
3486 if ( GEOSGeomTypeId_r( context, line ) == GEOS_LINEARRING || GEOSEquals_r( context, beginLineVertex.get(), endLineVertex.get() ) == 1 )
3487 isRing = true;
3488
3489 //node line and reshape line
3490 geos::unique_ptr nodedGeometry = nodeGeometries( reshapeLineGeos, line );
3491 if ( !nodedGeometry )
3492 {
3493 return nullptr;
3494 }
3495
3496 //and merge them together
3497 geos::unique_ptr mergedLines( GEOSLineMerge_r( context, nodedGeometry.get() ) );
3498 if ( !mergedLines )
3499 {
3500 return nullptr;
3501 }
3502
3503 int numMergedLines = GEOSGetNumGeometries_r( context, mergedLines.get() );
3504 if ( numMergedLines < 2 ) //some special cases. Normally it is >2
3505 {
3506 if ( numMergedLines == 1 ) //reshape line is from begin to endpoint. So we keep the reshapeline
3507 {
3508 geos::unique_ptr result( GEOSGeom_clone_r( context, reshapeLineGeos ) );
3509 return result;
3510 }
3511 else
3512 return nullptr;
3513 }
3514
3515 QVector<GEOSGeometry *> resultLineParts; //collection with the line segments that will be contained in result
3516 QVector<GEOSGeometry *> probableParts; //parts where we can decide on inclusion only after going through all the candidates
3517
3518 for ( int i = 0; i < numMergedLines; ++i )
3519 {
3520 const GEOSGeometry *currentGeom = GEOSGetGeometryN_r( context, mergedLines.get(), i );
3521
3522 // have we already added this part?
3523 bool alreadyAdded = false;
3524 double distance = 0;
3525 double bufferDistance = std::pow( 10.0L, geomDigits( currentGeom ) - 11 );
3526 for ( const GEOSGeometry *other : std::as_const( resultLineParts ) )
3527 {
3528 GEOSHausdorffDistance_r( context, currentGeom, other, &distance );
3529 if ( distance < bufferDistance )
3530 {
3531 alreadyAdded = true;
3532 break;
3533 }
3534 }
3535 if ( alreadyAdded )
3536 continue;
3537
3538 const GEOSCoordSequence *currentCoordSeq = GEOSGeom_getCoordSeq_r( context, currentGeom );
3539 unsigned int currentCoordSeqSize;
3540 GEOSCoordSeq_getSize_r( context, currentCoordSeq, &currentCoordSeqSize );
3541 if ( currentCoordSeqSize < 2 )
3542 continue;
3543
3544 //get the two endpoints of the current line merge result
3545 double xBegin, xEnd, yBegin, yEnd;
3546 GEOSCoordSeq_getX_r( context, currentCoordSeq, 0, &xBegin );
3547 GEOSCoordSeq_getY_r( context, currentCoordSeq, 0, &yBegin );
3548 GEOSCoordSeq_getX_r( context, currentCoordSeq, currentCoordSeqSize - 1, &xEnd );
3549 GEOSCoordSeq_getY_r( context, currentCoordSeq, currentCoordSeqSize - 1, &yEnd );
3550 geos::unique_ptr beginCurrentGeomVertex = createGeosPointXY( xBegin, yBegin, false, 0, false, 0, 2, precision );
3551 geos::unique_ptr endCurrentGeomVertex = createGeosPointXY( xEnd, yEnd, false, 0, false, 0, 2, precision );
3552
3553 //check how many endpoints of the line merge result are on the (original) line
3554 int nEndpointsOnOriginalLine = 0;
3555 if ( pointContainedInLine( beginCurrentGeomVertex.get(), line ) == 1 )
3556 nEndpointsOnOriginalLine += 1;
3557
3558 if ( pointContainedInLine( endCurrentGeomVertex.get(), line ) == 1 )
3559 nEndpointsOnOriginalLine += 1;
3560
3561 //check how many endpoints equal the endpoints of the original line
3562 int nEndpointsSameAsOriginalLine = 0;
3563 if ( GEOSEquals_r( context, beginCurrentGeomVertex.get(), beginLineVertex.get() ) == 1 || GEOSEquals_r( context, beginCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
3564 nEndpointsSameAsOriginalLine += 1;
3565
3566 if ( GEOSEquals_r( context, endCurrentGeomVertex.get(), beginLineVertex.get() ) == 1 || GEOSEquals_r( context, endCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
3567 nEndpointsSameAsOriginalLine += 1;
3568
3569 //check if the current geometry overlaps the original geometry (GEOSOverlap does not seem to work with linestrings)
3570 bool currentGeomOverlapsOriginalGeom = false;
3571 bool currentGeomOverlapsReshapeLine = false;
3572 if ( lineContainedInLine( currentGeom, line ) == 1 )
3573 currentGeomOverlapsOriginalGeom = true;
3574
3575 if ( lineContainedInLine( currentGeom, reshapeLineGeos ) == 1 )
3576 currentGeomOverlapsReshapeLine = true;
3577
3578 //logic to decide if this part belongs to the result
3579 if ( !isRing && nEndpointsSameAsOriginalLine == 1 && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
3580 {
3581 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3582 }
3583 //for closed rings, we take one segment from the candidate list
3584 else if ( isRing && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
3585 {
3586 probableParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3587 }
3588 else if ( nEndpointsOnOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
3589 {
3590 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3591 }
3592 else if ( nEndpointsSameAsOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
3593 {
3594 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3595 }
3596 else if ( currentGeomOverlapsOriginalGeom && currentGeomOverlapsReshapeLine )
3597 {
3598 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3599 }
3600 }
3601
3602 //add the longest segment from the probable list for rings (only used for polygon rings)
3603 if ( isRing && !probableParts.isEmpty() )
3604 {
3605 geos::unique_ptr maxGeom; //the longest geometry in the probabla list
3606 GEOSGeometry *currentGeom = nullptr;
3607 double maxLength = -std::numeric_limits<double>::max();
3608 double currentLength = 0;
3609 for ( int i = 0; i < probableParts.size(); ++i )
3610 {
3611 currentGeom = probableParts.at( i );
3612 GEOSLength_r( context, currentGeom, &currentLength );
3613 if ( currentLength > maxLength )
3614 {
3615 maxLength = currentLength;
3616 maxGeom.reset( currentGeom );
3617 }
3618 else
3619 {
3620 GEOSGeom_destroy_r( context, currentGeom );
3621 }
3622 }
3623 resultLineParts.push_back( maxGeom.release() );
3624 }
3625
3626 geos::unique_ptr result;
3627 if ( resultLineParts.empty() )
3628 return nullptr;
3629
3630 if ( resultLineParts.size() == 1 ) //the whole result was reshaped
3631 {
3632 result.reset( resultLineParts[0] );
3633 }
3634 else //>1
3635 {
3636 GEOSGeometry **lineArray = new GEOSGeometry *[resultLineParts.size()];
3637 for ( int i = 0; i < resultLineParts.size(); ++i )
3638 {
3639 lineArray[i] = resultLineParts[i];
3640 }
3641
3642 //create multiline from resultLineParts
3643 geos::unique_ptr multiLineGeom( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, lineArray, resultLineParts.size() ) );
3644 delete[] lineArray;
3645
3646 //then do a linemerge with the newly combined partstrings
3647 result.reset( GEOSLineMerge_r( context, multiLineGeom.get() ) );
3648 }
3649
3650 //now test if the result is a linestring. Otherwise something went wrong
3651 if ( GEOSGeomTypeId_r( context, result.get() ) != GEOS_LINESTRING )
3652 {
3653 return nullptr;
3654 }
3655
3656 //keep the original orientation
3657 bool reverseLine = false;
3658 if ( isRing )
3659 {
3660 //for closed linestring check clockwise/counter-clockwise
3661 char isResultCCW = 0, isOriginCCW = 0;
3662 if ( GEOSCoordSeq_isCCW_r( context, GEOSGeom_getCoordSeq_r( context, result.get() ), &isResultCCW ) && GEOSCoordSeq_isCCW_r( context, GEOSGeom_getCoordSeq_r( context, line ), &isOriginCCW ) )
3663 {
3664 //reverse line if orientations are different
3665 reverseLine = ( isOriginCCW == 1 && isResultCCW == 0 ) || ( isOriginCCW == 0 && isResultCCW == 1 );
3666 }
3667 }
3668 else
3669 {
3670 //for linestring, check if the result has a start or end point in common with the original line
3671 double x1res, y1res, x2res, y2res;
3672 if ( !_linestringEndpoints( result.get(), x1res, y1res, x2res, y2res ) )
3673 return nullptr;
3674 geos::unique_ptr beginResultLineVertex = createGeosPointXY( x1res, y1res, false, 0, false, 0, 2, precision );
3675 geos::unique_ptr endResultLineVertex = createGeosPointXY( x2res, y2res, false, 0, false, 0, 2, precision );
3676 reverseLine = GEOSEquals_r( context, beginLineVertex.get(), endResultLineVertex.get() ) == 1 || GEOSEquals_r( context, endLineVertex.get(), beginResultLineVertex.get() ) == 1;
3677 }
3678 if ( reverseLine )
3679 result.reset( GEOSReverse_r( context, result.get() ) );
3680
3681 return result;
3682}
3683
3684geos::unique_ptr QgsGeos::reshapePolygon( const GEOSGeometry *polygon, const GEOSGeometry *reshapeLineGeos, double precision )
3685{
3686 //go through outer shell and all inner rings and check if there is exactly one intersection of a ring and the reshape line
3687 int nIntersections = 0;
3688 int lastIntersectingRing = -2;
3689 const GEOSGeometry *lastIntersectingGeom = nullptr;
3690
3691 GEOSContextHandle_t context = QgsGeosContext::get();
3692 int nRings = GEOSGetNumInteriorRings_r( context, polygon );
3693 if ( nRings < 0 )
3694 return nullptr;
3695
3696 //does outer ring intersect?
3697 const GEOSGeometry *outerRing = GEOSGetExteriorRing_r( context, polygon );
3698 if ( GEOSIntersects_r( context, outerRing, reshapeLineGeos ) == 1 )
3699 {
3700 ++nIntersections;
3701 lastIntersectingRing = -1;
3702 lastIntersectingGeom = outerRing;
3703 }
3704
3705 //do inner rings intersect?
3706 const GEOSGeometry **innerRings = new const GEOSGeometry *[nRings];
3707
3708 try
3709 {
3710 for ( int i = 0; i < nRings; ++i )
3711 {
3712 innerRings[i] = GEOSGetInteriorRingN_r( context, polygon, i );
3713 if ( GEOSIntersects_r( context, innerRings[i], reshapeLineGeos ) == 1 )
3714 {
3715 ++nIntersections;
3716 lastIntersectingRing = i;
3717 lastIntersectingGeom = innerRings[i];
3718 }
3719 }
3720 }
3721 catch ( QgsGeosException & )
3722 {
3723 nIntersections = 0;
3724 }
3725
3726 if ( nIntersections != 1 ) //reshape line is only allowed to intersect one ring
3727 {
3728 delete[] innerRings;
3729 return nullptr;
3730 }
3731
3732 //we have one intersecting ring, let's try to reshape it
3733 geos::unique_ptr reshapeResult = reshapeLine( lastIntersectingGeom, reshapeLineGeos, precision );
3734 if ( !reshapeResult )
3735 {
3736 delete[] innerRings;
3737 return nullptr;
3738 }
3739
3740 //if reshaping took place, we need to reassemble the polygon and its rings
3741 GEOSGeometry *newRing = nullptr;
3742 const GEOSCoordSequence *reshapeSequence = GEOSGeom_getCoordSeq_r( context, reshapeResult.get() );
3743 GEOSCoordSequence *newCoordSequence = GEOSCoordSeq_clone_r( context, reshapeSequence );
3744
3745 reshapeResult.reset();
3746
3747 try
3748 {
3749 newRing = GEOSGeom_createLinearRing_r( context, newCoordSequence );
3750 }
3751 catch ( QgsGeosException & )
3752 {
3753 // nothing to do: on exception newRing will be null
3754 }
3755
3756 if ( !newRing )
3757 {
3758 delete[] innerRings;
3759 return nullptr;
3760 }
3761
3762 GEOSGeometry *newOuterRing = nullptr;
3763 if ( lastIntersectingRing == -1 )
3764 newOuterRing = newRing;
3765 else
3766 newOuterRing = GEOSGeom_clone_r( context, outerRing );
3767
3768 //check if all the rings are still inside the outer boundary
3769 QVector<GEOSGeometry *> ringList;
3770 if ( nRings > 0 )
3771 {
3772 GEOSGeometry *outerRingPoly = GEOSGeom_createPolygon_r( context, GEOSGeom_clone_r( context, newOuterRing ), nullptr, 0 );
3773 if ( outerRingPoly )
3774 {
3775 ringList.reserve( nRings );
3776 GEOSGeometry *currentRing = nullptr;
3777 for ( int i = 0; i < nRings; ++i )
3778 {
3779 if ( lastIntersectingRing == i )
3780 currentRing = newRing;
3781 else
3782 currentRing = GEOSGeom_clone_r( context, innerRings[i] );
3783
3784 //possibly a ring is no longer contained in the result polygon after reshape
3785 if ( GEOSContains_r( context, outerRingPoly, currentRing ) == 1 )
3786 ringList.push_back( currentRing );
3787 else
3788 GEOSGeom_destroy_r( context, currentRing );
3789 }
3790 }
3791 GEOSGeom_destroy_r( context, outerRingPoly );
3792 }
3793
3794 GEOSGeometry **newInnerRings = new GEOSGeometry *[ringList.size()];
3795 for ( int i = 0; i < ringList.size(); ++i )
3796 newInnerRings[i] = ringList.at( i );
3797
3798 delete[] innerRings;
3799
3800 geos::unique_ptr reshapedPolygon( GEOSGeom_createPolygon_r( context, newOuterRing, newInnerRings, ringList.size() ) );
3801 delete[] newInnerRings;
3802
3803 return reshapedPolygon;
3804}
3805
3806int QgsGeos::lineContainedInLine( const GEOSGeometry *line1, const GEOSGeometry *line2 )
3807{
3808 if ( !line1 || !line2 )
3809 {
3810 return -1;
3811 }
3812
3813 double bufferDistance = std::pow( 10.0L, geomDigits( line2 ) - 11 );
3814
3815 GEOSContextHandle_t context = QgsGeosContext::get();
3816 geos::unique_ptr bufferGeom( GEOSBuffer_r( context, line2, bufferDistance, DEFAULT_QUADRANT_SEGMENTS ) );
3817 if ( !bufferGeom )
3818 return -2;
3819
3820 geos::unique_ptr intersectionGeom( GEOSIntersection_r( context, bufferGeom.get(), line1 ) );
3821
3822 //compare ratio between line1Length and intersectGeomLength (usually close to 1 if line1 is contained in line2)
3823 double intersectGeomLength;
3824 double line1Length;
3825
3826 GEOSLength_r( context, intersectionGeom.get(), &intersectGeomLength );
3827 GEOSLength_r( context, line1, &line1Length );
3828
3829 double intersectRatio = line1Length / intersectGeomLength;
3830 if ( intersectRatio > 0.9 && intersectRatio < 1.1 )
3831 return 1;
3832
3833 return 0;
3834}
3835
3836int QgsGeos::pointContainedInLine( const GEOSGeometry *point, const GEOSGeometry *line )
3837{
3838 if ( !point || !line )
3839 return -1;
3840
3841 double bufferDistance = std::pow( 10.0L, geomDigits( line ) - 11 );
3842
3843 GEOSContextHandle_t context = QgsGeosContext::get();
3844 geos::unique_ptr lineBuffer( GEOSBuffer_r( context, line, bufferDistance, 8 ) );
3845 if ( !lineBuffer )
3846 return -2;
3847
3848 bool contained = false;
3849 if ( GEOSContains_r( context, lineBuffer.get(), point ) == 1 )
3850 contained = true;
3851
3852 return contained;
3853}
3854
3855int QgsGeos::geomDigits( const GEOSGeometry *geom )
3856{
3857 GEOSContextHandle_t context = QgsGeosContext::get();
3858 geos::unique_ptr bbox( GEOSEnvelope_r( context, geom ) );
3859 if ( !bbox )
3860 return -1;
3861
3862 const GEOSGeometry *bBoxRing = GEOSGetExteriorRing_r( context, bbox.get() );
3863 if ( !bBoxRing )
3864 return -1;
3865
3866 const GEOSCoordSequence *bBoxCoordSeq = GEOSGeom_getCoordSeq_r( context, bBoxRing );
3867
3868 if ( !bBoxCoordSeq )
3869 return -1;
3870
3871 unsigned int nCoords = 0;
3872 if ( !GEOSCoordSeq_getSize_r( context, bBoxCoordSeq, &nCoords ) )
3873 return -1;
3874
3875 int maxDigits = -1;
3876 for ( unsigned int i = 0; i < nCoords - 1; ++i )
3877 {
3878 double t;
3879 GEOSCoordSeq_getX_r( context, bBoxCoordSeq, i, &t );
3880
3881 int digits;
3882 digits = std::ceil( std::log10( std::fabs( t ) ) );
3883 if ( digits > maxDigits )
3884 maxDigits = digits;
3885
3886 GEOSCoordSeq_getY_r( context, bBoxCoordSeq, i, &t );
3887 digits = std::ceil( std::log10( std::fabs( t ) ) );
3888 if ( digits > maxDigits )
3889 maxDigits = digits;
3890 }
3891
3892 return maxDigits;
3893}
Provides global constants and enumerations for use throughout the application.
Definition qgis.h:62
BufferSide
Side of line to buffer.
Definition qgis.h:2176
@ Right
Buffer to right of line.
Definition qgis.h:2178
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:2121
@ AddPartNotMultiGeometry
The source geometry is not multi.
Definition qgis.h:2132
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
Definition qgis.h:2124
@ RejectOnInvalidSubGeometry
Don't allow geometries with invalid sub-geometries to be created.
Definition qgis.h:2228
@ SkipEmptyInteriorRings
Skip any empty polygon interior ring.
Definition qgis.h:2229
QFlags< GeosCreationFlag > GeosCreationFlags
Geos geometry creation behavior flags.
Definition qgis.h:2238
@ Point
Points.
Definition qgis.h:380
@ Line
Lines.
Definition qgis.h:381
@ Polygon
Polygons.
Definition qgis.h:382
@ Unknown
Unknown types.
Definition qgis.h:383
@ Null
No geometry.
Definition qgis.h:384
JoinStyle
Join styles for buffers.
Definition qgis.h:2201
EndCapStyle
End cap styles for buffers.
Definition qgis.h:2188
CoverageValidityResult
Coverage validity results.
Definition qgis.h:2247
@ Valid
Coverage is valid.
Definition qgis.h:2249
@ Invalid
Coverage is invalid. Invalidity includes polygons that overlap, that have gaps smaller than the gap w...
Definition qgis.h:2248
@ Error
An exception occurred while determining validity.
Definition qgis.h:2250
MakeValidMethod
Algorithms to use when repairing invalid geometries.
Definition qgis.h:2260
@ Linework
Combines all rings into a set of noded lines and then extracts valid polygons from that linework.
Definition qgis.h:2261
@ Structure
Structured method, first makes all rings valid and then merges shells and subtracts holes from shells...
Definition qgis.h:2262
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:294
@ Point
Point.
Definition qgis.h:296
@ TIN
TIN.
Definition qgis.h:310
@ LineStringZM
LineStringZM.
Definition qgis.h:346
@ Polygon
Polygon.
Definition qgis.h:298
@ PointM
PointM.
Definition qgis.h:329
@ PointZ
PointZ.
Definition qgis.h:313
@ GeometryCollection
GeometryCollection.
Definition qgis.h:303
@ PointZM
PointZM.
Definition qgis.h:345
@ LineStringZ
LineStringZ.
Definition qgis.h:314
@ PolyhedralSurface
PolyhedralSurface.
Definition qgis.h:309
Abstract base class for all geometries.
virtual const QgsAbstractGeometry * simplifiedTypeRef() const
Returns a reference to the simplest lossless representation of this geometry, e.g.
bool isMeasure() const
Returns true if the geometry contains m values.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual QgsPoint vertexAt(QgsVertexId id) const =0
Returns the point corresponding to a specified vertex id.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual bool isEmpty() const
Returns true if the geometry is empty.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
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.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
virtual QgsLineString * curveToLine(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const =0
Returns a new line string geometry corresponding to a segmentized approximation of the curve.
virtual bool addGeometry(QgsAbstractGeometry *g)
Adds a geometry and takes ownership. Returns true in case of success.
static Qgis::GeometryOperationResult addPart(QgsAbstractGeometry *geometry, std::unique_ptr< QgsAbstractGeometry > part)
Add a part to multi type geometry.
const QgsAbstractGeometry * mGeometry
EngineOperationResult
Success or failure of a geometry operation.
@ NothingHappened
Nothing happened, without any error.
@ InvalidBaseGeometry
The geometry on which the operation occurs is not valid.
@ InvalidInput
The input is not valid.
@ NodedGeometryError
Error occurred while creating a noded geometry.
@ EngineError
Error occurred in the geometry engine.
@ SplitCannotSplitPoint
Points cannot be split.
@ Success
Operation succeeded.
QgsGeometryEngine(const QgsAbstractGeometry *geometry)
void logError(const QString &engineName, const QString &message) const
Logs an error message encountered during an operation.
static std::unique_ptr< QgsGeometryCollection > createCollectionOfType(Qgis::WkbType type)
Returns a new geometry collection matching a specified WKB type.
Encapsulates parameters under which a geometry operation is performed.
double gridSize() const
Returns the grid size which will be used to snap vertices of a geometry.
static double sqrDistance2D(double x1, double y1, double x2, double y2)
Returns the squared 2D distance between (x1, y1) and (x2, y2).
A geometry is the spatial representation of a feature.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
Used to create and store a proj context object, correctly freeing the context upon destruction.
Definition qgsgeos.h:46
static GEOSContextHandle_t get()
Returns a thread local instance of a GEOS context, safe for use in the current thread.
double frechetDistanceDensify(const QgsAbstractGeometry *geometry, double densifyFraction, QString *errorMsg=nullptr) const
Returns the Fréchet distance between this geometry and another geometry, restricted to discrete point...
Definition qgsgeos.cpp:827
double hausdorffDistanceDensify(const QgsAbstractGeometry *geometry, double densifyFraction, QString *errorMsg=nullptr) const
Returns the Hausdorff distance between this geometry and another geometry.
Definition qgsgeos.cpp:781
bool touches(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom touches this.
Definition qgsgeos.cpp:883
static geos::unique_ptr offsetCurve(const GEOSGeometry *geometry, double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg=nullptr)
Directly calculates the offset curve for a GEOS geometry object and returns a GEOS geometry result.
Definition qgsgeos.cpp:2742
QgsAbstractGeometry * symDifference(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const override
Calculate the symmetric difference of this and geom.
Definition qgsgeos.cpp:535
bool isValid(QString *errorMsg=nullptr, bool allowSelfTouchingHoles=false, QgsGeometry *errorLoc=nullptr) const override
Returns true if the geometry is valid.
Definition qgsgeos.cpp:2345
std::unique_ptr< QgsAbstractGeometry > subdivide(int maxNodes, QString *errorMsg=nullptr, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const
Subdivides the geometry.
Definition qgsgeos.cpp:445
QgsAbstractGeometry * intersection(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const override
Calculate the intersection of this and geom.
Definition qgsgeos.cpp:314
std::unique_ptr< QgsAbstractGeometry > constrainedDelaunayTriangulation(QString *errorMsg=nullptr) const
Returns a constrained Delaunay triangulation for the vertices of the geometry.
Definition qgsgeos.cpp:3335
QgsAbstractGeometry * buffer(double distance, int segments, QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2079
std::unique_ptr< QgsAbstractGeometry > closestPoint(const QgsGeometry &other, QString *errorMsg=nullptr) const
Returns the closest point on the geometry to the other geometry.
Definition qgsgeos.cpp:3077
std::unique_ptr< QgsAbstractGeometry > reshapeGeometry(const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg=nullptr) const
Reshapes the geometry using a line.
Definition qgsgeos.cpp:2918
std::unique_ptr< QgsAbstractGeometry > maximumInscribedCircle(double tolerance, QString *errorMsg=nullptr) const
Returns the maximum inscribed circle.
Definition qgsgeos.cpp:2796
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlags())
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:254
QgsAbstractGeometry * difference(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const override
Calculate the difference of this and geom.
Definition qgsgeos.cpp:319
EngineOperationResult splitGeometry(const QgsLineString &splitLine, QVector< QgsGeometry > &newGeometries, bool topological, QgsPointSequence &topologyTestPoints, QString *errorMsg=nullptr, bool skipIntersectionCheck=false) const override
Splits this geometry according to a given line.
Definition qgsgeos.cpp:1041
std::unique_ptr< QgsAbstractGeometry > sharedPaths(const QgsAbstractGeometry *other, QString *errorMsg=nullptr) const
Find paths shared between the two given lineal geometries (this and other).
Definition qgsgeos.cpp:2898
std::unique_ptr< QgsAbstractGeometry > clip(const QgsRectangle &rectangle, QString *errorMsg=nullptr) const
Performs a fast, non-robust intersection between the geometry and a rectangle.
Definition qgsgeos.cpp:324
std::unique_ptr< QgsAbstractGeometry > node(QString *errorMsg=nullptr) const
Returns a (Multi)LineString representing the fully noded version of a collection of linestrings.
Definition qgsgeos.cpp:2882
double minimumClearance(QString *errorMsg=nullptr) const
Computes the minimum clearance of a geometry.
Definition qgsgeos.cpp:2848
std::unique_ptr< QgsAbstractGeometry > singleSidedBuffer(double distance, int segments, Qgis::BufferSide side, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg=nullptr) const
Returns a single sided buffer for a geometry.
Definition qgsgeos.cpp:2769
bool disjoint(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom is disjoint from this.
Definition qgsgeos.cpp:936
std::unique_ptr< QgsAbstractGeometry > mergeLines(QString *errorMsg=nullptr, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
Definition qgsgeos.cpp:3050
std::unique_ptr< QgsAbstractGeometry > makeValid(Qgis::MakeValidMethod method=Qgis::MakeValidMethod::Linework, bool keepCollapsed=false, QString *errorMsg=nullptr) const
Repairs the geometry using GEOS make valid routine.
Definition qgsgeos.cpp:197
std::unique_ptr< QgsAbstractGeometry > voronoiDiagram(const QgsAbstractGeometry *extent=nullptr, double tolerance=0.0, bool edgesOnly=false, QString *errorMsg=nullptr) const
Creates a Voronoi diagram for the nodes contained within the geometry.
Definition qgsgeos.cpp:3279
QgsAbstractGeometry * simplify(double tolerance, QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2117
std::unique_ptr< QgsAbstractGeometry > unionCoverage(QString *errorMsg=nullptr) const
Optimized union algorithm for polygonal inputs that are correctly noded and do not overlap.
Definition qgsgeos.cpp:2327
static std::unique_ptr< QgsPolygon > fromGeosPolygon(const GEOSGeometry *geos)
Definition qgsgeos.cpp:1671
double hausdorffDistance(const QgsAbstractGeometry *geometry, QString *errorMsg=nullptr) const
Returns the Hausdorff distance between this geometry and another geometry.
Definition qgsgeos.cpp:758
std::unique_ptr< QgsAbstractGeometry > minimumClearanceLine(QString *errorMsg=nullptr) const
Returns a LineString whose endpoints define the minimum clearance of a geometry.
Definition qgsgeos.cpp:2866
QgsAbstractGeometry * envelope(QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2174
QString relate(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Returns the Dimensional Extended 9 Intersection Model (DE-9IM) representation of the relationship bet...
Definition qgsgeos.cpp:941
bool crosses(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom crosses this.
Definition qgsgeos.cpp:888
QgsAbstractGeometry * convexHull(QString *errorMsg=nullptr) const override
Calculate the convex hull of this.
Definition qgsgeos.cpp:2218
double distance(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Calculates the distance between this and geom.
Definition qgsgeos.cpp:577
std::unique_ptr< QgsAbstractGeometry > minimumWidth(QString *errorMsg=nullptr) const
Returns a linestring geometry which represents the minimum diameter of the geometry.
Definition qgsgeos.cpp:2832
bool isSimple(QString *errorMsg=nullptr) const override
Determines whether the geometry is simple (according to OGC definition).
Definition qgsgeos.cpp:2441
QgsGeos(const QgsAbstractGeometry *geometry, double precision=0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
GEOS geometry engine constructor.
Definition qgsgeos.cpp:176
QgsAbstractGeometry * combine(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const override
Calculate the combination of this and geom.
Definition qgsgeos.cpp:465
bool within(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom is within this.
Definition qgsgeos.cpp:893
std::unique_ptr< QgsAbstractGeometry > shortestLine(const QgsGeometry &other, QString *errorMsg=nullptr) const
Returns the shortest line joining this geometry to the other geometry.
Definition qgsgeos.cpp:3121
std::unique_ptr< QgsAbstractGeometry > concaveHull(double targetPercent, bool allowHoles=false, QString *errorMsg=nullptr) const
Returns a possibly concave geometry that encloses the input geometry.
Definition qgsgeos.cpp:2234
void prepareGeometry() override
Prepares the geometry, so that subsequent calls to spatial relation methods are much faster.
Definition qgsgeos.cpp:286
static std::unique_ptr< QgsAbstractGeometry > fromGeos(const GEOSGeometry *geos)
Create a geometry from a GEOSGeometry.
Definition qgsgeos.cpp:1562
QgsAbstractGeometry * interpolate(double distance, QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2132
bool distanceWithin(const QgsAbstractGeometry *geom, double maxdistance, QString *errorMsg=nullptr) const override
Checks if geom is within maxdistance distance from this geometry.
Definition qgsgeos.cpp:652
bool isEqual(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if this is equal to geom.
Definition qgsgeos.cpp:2407
bool contains(double x, double y, QString *errorMsg=nullptr) const
Returns true if the geometry contains the point at (x, y).
Definition qgsgeos.cpp:715
static QgsGeometry polygonize(const QVector< const QgsAbstractGeometry * > &geometries, QString *errorMsg=nullptr)
Creates a GeometryCollection geometry containing possible polygons formed from the constituent linewo...
Definition qgsgeos.cpp:3239
double frechetDistance(const QgsAbstractGeometry *geometry, QString *errorMsg=nullptr) const
Returns the Fréchet distance between this geometry and another geometry, restricted to discrete point...
Definition qgsgeos.cpp:804
bool relatePattern(const QgsAbstractGeometry *geom, const QString &pattern, QString *errorMsg=nullptr) const override
Tests whether two geometries are related by a specified Dimensional Extended 9 Intersection Model (DE...
Definition qgsgeos.cpp:977
QgsPoint * centroid(QString *errorMsg=nullptr) const override
Calculates the centroid of this.
Definition qgsgeos.cpp:2147
double lineLocatePoint(const QgsPoint &point, QString *errorMsg=nullptr) const
Returns a distance representing the location along this linestring of the closest point on this lines...
Definition qgsgeos.cpp:3179
std::unique_ptr< QgsAbstractGeometry > simplifyCoverageVW(double tolerance, bool preserveBoundary, QString *errorMsg=nullptr) const
Operates on a coverage (represented as a list of polygonal geometry with exactly matching edge geomet...
Definition qgsgeos.cpp:2302
bool isEmpty(QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2427
static Qgis::GeometryOperationResult addPart(QgsGeometry &geometry, GEOSGeometry *newPart)
Adds a new island polygon to a multipolygon feature.
Definition qgsgeos.cpp:264
bool intersects(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom intersects this.
Definition qgsgeos.cpp:850
void geometryChanged() override
Should be called whenever the geometry associated with the engine has been modified and the engine mu...
Definition qgsgeos.cpp:279
bool overlaps(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom overlaps this.
Definition qgsgeos.cpp:898
double area(QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:1008
std::unique_ptr< QgsAbstractGeometry > delaunayTriangulation(double tolerance=0.0, bool edgesOnly=false, QString *errorMsg=nullptr) const
Returns the Delaunay triangulation for the vertices of the geometry.
Definition qgsgeos.cpp:3312
double length(QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:1025
std::unique_ptr< QgsAbstractGeometry > largestEmptyCircle(double tolerance, const QgsAbstractGeometry *boundary=nullptr, QString *errorMsg=nullptr) const
Constructs the Largest Empty Circle for a set of obstacle geometries, up to a specified tolerance.
Definition qgsgeos.cpp:2812
Qgis::CoverageValidityResult validateCoverage(double gapWidth, std::unique_ptr< QgsAbstractGeometry > *invalidEdges, QString *errorMsg=nullptr) const
Analyze a coverage (represented as a collection of polygonal geometry with exactly matching edge geom...
Definition qgsgeos.cpp:2257
static QgsPoint coordSeqPoint(const GEOSCoordSequence *cs, int i, bool hasZ, bool hasM)
Definition qgsgeos.cpp:1763
QgsPoint * pointOnSurface(QString *errorMsg=nullptr) const override
Calculate a point that is guaranteed to be on the surface of this.
Definition qgsgeos.cpp:2189
static QgsGeometry geometryFromGeos(GEOSGeometry *geos)
Creates a new QgsGeometry object, feeding in a geometry in GEOS format.
Definition qgsgeos.cpp:184
Line string geometry type, with support for z-dimension and m-values.
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
QVector< double > xVector() const
Returns the x vertex values as a vector.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
int numPoints() const override
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
QVector< double > yVector() const
Returns the y vertex values as a vector.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
QVector< double > zVector() const
Returns the z vertex values as a vector.
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.
const double * mData() const
Returns a const pointer to the m vertex data, or nullptr if the linestring does not have m values.
void addVertex(const QgsPoint &pt)
Adds a new vertex to the end of the line string.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Custom exception class which is raised when an operation is not supported.
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
QgsPoint * clone() const override
Clones the geometry by performing a deep copy.
Definition qgspoint.cpp:138
double z
Definition qgspoint.h:58
double x
Definition qgspoint.h:56
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:766
double distance(double x, double y) const
Returns the Cartesian 2D distance between this point and a specified x, y coordinate.
Definition qgspoint.h:466
double m
Definition qgspoint.h:59
double y
Definition qgspoint.h:57
Polygon geometry type.
Definition qgspolygon.h:37
Polyhedral surface geometry type.
int numPatches() const
Returns the number of patches contained with the polyhedral surface.
const QgsPolygon * patchN(int i) const
Retrieves a patch from the polyhedral surface.
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
double yMaximum
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 flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
static Q_INVOKABLE bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi type.
Contains geos related utilities and functions.
Definition qgsgeos.h:76
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:112
std::unique_ptr< GEOSCoordSequence, GeosDeleter > coord_sequence_unique_ptr
Scoped GEOS coordinate sequence pointer.
Definition qgsgeos.h:127
std::unique_ptr< GEOSBufferParams, GeosDeleter > buffer_params_unique_ptr
Scoped GEOS buffer params pointer.
Definition qgsgeos.h:122
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6975
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPoint > QgsPointSequence
#define CATCH_GEOS(r)
Definition qgsgeos.cpp:42
#define DEFAULT_QUADRANT_SEGMENTS
Definition qgsgeos.cpp:40
#define CATCH_GEOS_WITH_ERRMSG(r)
Definition qgsgeos.cpp:48
#define QgsDebugError(str)
Definition qgslogger.h:59
QLineF segment(int index, QRectF rect, double radius)
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:34
int vertex
Vertex number.
Definition qgsvertexid.h:99
void CORE_EXPORT operator()(GEOSGeometry *geom) const
Destroys the GEOS geometry geom, using the static QGIS geos context.
struct GEOSGeom_t GEOSGeometry
Definition util.h:41