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