QGIS API Documentation 4.3.0-Master (59e977eef4b)
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 ( void ) feedback;
2416 throw QgsNotSupportedException( QObject::tr( "Cleaning coverages requires a QGIS build based on GEOS 3.14 or later" ) );
2417#else
2418 if ( !mGeos )
2419 {
2420 if ( errorMsg )
2421 *errorMsg = u"Input geometry was not set"_s;
2422 return nullptr;
2423 }
2424
2425 GEOSCoverageCleanParams *params = nullptr;
2426 try
2427 {
2428 params = GEOSCoverageCleanParams_create_r( QgsGeosContext::get() );
2429 if ( parameters.snappingDistance() >= 0 )
2430 {
2431 GEOSCoverageCleanParams_setSnappingDistance_r( QgsGeosContext::get(), params, parameters.snappingDistance() );
2432 }
2433 GEOSCoverageCleanParams_setGapMaximumWidth_r( QgsGeosContext::get(), params, parameters.maximumGapWidth() );
2434 switch ( parameters.overlapMergeStrategy() )
2435 {
2437 GEOSCoverageCleanParams_setOverlapMergeStrategy_r( QgsGeosContext::get(), params, 0 );
2438 break;
2440 GEOSCoverageCleanParams_setOverlapMergeStrategy_r( QgsGeosContext::get(), params, 1 );
2441 break;
2443 GEOSCoverageCleanParams_setOverlapMergeStrategy_r( QgsGeosContext::get(), params, 2 );
2444 break;
2446 GEOSCoverageCleanParams_setOverlapMergeStrategy_r( QgsGeosContext::get(), params, 3 );
2447 break;
2448 }
2449
2450 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2451 geos::unique_ptr cleaned( GEOSCoverageCleanWithParams_r( QgsGeosContext::get(), mGeos.get(), params ) );
2452 GEOSCoverageCleanParams_destroy_r( QgsGeosContext::get(), params );
2453
2454 std::unique_ptr< QgsAbstractGeometry> cleanedGeom = fromGeos( cleaned.get() );
2455
2456 return cleanedGeom;
2457 }
2458 catch ( QgsGeosException &e )
2459 {
2460 if ( params )
2461 {
2462 GEOSCoverageCleanParams_destroy_r( QgsGeosContext::get(), params );
2463 params = nullptr;
2464 }
2465
2466 if ( errorMsg )
2467 {
2468 *errorMsg = e.what();
2469 if ( errorMsg->startsWith( "InterruptedException"_L1, Qt::CaseInsensitive ) )
2470 {
2471 errorMsg->clear();
2472 }
2473 }
2474 return nullptr;
2475 }
2476#endif
2477}
2478
2479bool QgsGeos::isValid( QString *errorMsg, const bool allowSelfTouchingHoles, QgsGeometry *errorLoc, QgsFeedback *feedback ) const
2480{
2481 if ( !mGeos )
2482 {
2483 if ( errorMsg )
2484 *errorMsg = QObject::tr( "QGIS geometry cannot be converted to a GEOS geometry", "GEOS Error" );
2485 return false;
2486 }
2487
2488 GEOSContextHandle_t context = QgsGeosContext::get();
2489 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2490
2491 try
2492 {
2493 GEOSGeometry *g1 = nullptr;
2494 char *r = nullptr;
2495 char res = GEOSisValidDetail_r( context, mGeos.get(), allowSelfTouchingHoles ? GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE : 0, &r, &g1 );
2496 const bool invalid = res != 1;
2497
2498 QString error;
2499 if ( r )
2500 {
2501 error = QString( r );
2502 GEOSFree_r( context, r );
2503 }
2504
2505 if ( invalid && errorMsg )
2506 {
2507 // Copied from https://github.com/libgeos/geos/blob/main/src/operation/valid/TopologyValidationError.cpp
2508 static const std::map< QString, QString > sTranslatedErrors {
2509 { u"topology validation error"_s, QObject::tr( "Topology validation error", "GEOS Error" ) },
2510 { u"repeated point"_s, QObject::tr( "Repeated point", "GEOS Error" ) },
2511 { u"hole lies outside shell"_s, QObject::tr( "Hole lies outside shell", "GEOS Error" ) },
2512 { u"holes are nested"_s, QObject::tr( "Holes are nested", "GEOS Error" ) },
2513 { u"interior is disconnected"_s, QObject::tr( "Interior is disconnected", "GEOS Error" ) },
2514 { u"self-intersection"_s, QObject::tr( "Self-intersection", "GEOS Error" ) },
2515 { u"ring self-intersection"_s, QObject::tr( "Ring self-intersection", "GEOS Error" ) },
2516 { u"nested shells"_s, QObject::tr( "Nested shells", "GEOS Error" ) },
2517 { u"duplicate rings"_s, QObject::tr( "Duplicate rings", "GEOS Error" ) },
2518 { u"too few points in geometry component"_s, QObject::tr( "Too few points in geometry component", "GEOS Error" ) },
2519 { u"invalid coordinate"_s, QObject::tr( "Invalid coordinate", "GEOS Error" ) },
2520 { u"ring is not closed"_s, QObject::tr( "Ring is not closed", "GEOS Error" ) },
2521 };
2522
2523 const auto translatedError = sTranslatedErrors.find( error.toLower() );
2524 if ( translatedError != sTranslatedErrors.end() )
2525 *errorMsg = translatedError->second;
2526 else
2527 *errorMsg = error;
2528
2529 if ( g1 && errorLoc )
2530 {
2531 *errorLoc = geometryFromGeos( g1 );
2532 }
2533 else if ( g1 )
2534 {
2535 GEOSGeom_destroy_r( context, g1 );
2536 }
2537 }
2538 return !invalid;
2539 }
2540 CATCH_GEOS_WITH_ERRMSG( false )
2541}
2542
2543bool QgsGeos::isEqual( const QgsAbstractGeometry *geom, QString *errorMsg, QgsFeedback *feedback ) const
2544{
2545 if ( !mGeos || !geom )
2546 {
2547 return false;
2548 }
2549
2550 try
2551 {
2552 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2553 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2554 if ( !geosGeom )
2555 {
2556 return false;
2557 }
2558 bool equal = GEOSEquals_r( QgsGeosContext::get(), mGeos.get(), geosGeom.get() );
2559 return equal;
2560 }
2561 CATCH_GEOS_WITH_ERRMSG( false )
2562}
2563
2564bool QgsGeos::isFuzzyEqual( const QgsAbstractGeometry *geom, double epsilon, QString *errorMsg, QgsFeedback *feedback ) const
2565{
2566 if ( !mGeos || !geom )
2567 {
2568 return false;
2569 }
2570
2571 try
2572 {
2573 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2574
2575 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2576 if ( !geosGeom )
2577 {
2578 return false;
2579 }
2580 bool equal = GEOSEqualsExact_r( QgsGeosContext::get(), mGeos.get(), geosGeom.get(), epsilon );
2581 return equal;
2582 }
2583 CATCH_GEOS_WITH_ERRMSG( false )
2584}
2585
2586bool QgsGeos::isEmpty( QString *errorMsg ) const
2587{
2588 if ( !mGeos )
2589 {
2590 return false;
2591 }
2592
2593 try
2594 {
2595 return GEOSisEmpty_r( QgsGeosContext::get(), mGeos.get() );
2596 }
2597 CATCH_GEOS_WITH_ERRMSG( false )
2598}
2599
2600bool QgsGeos::isSimple( QString *errorMsg ) const
2601{
2602 if ( !mGeos )
2603 {
2604 return false;
2605 }
2606
2607 try
2608 {
2609 return GEOSisSimple_r( QgsGeosContext::get(), mGeos.get() );
2610 }
2611 CATCH_GEOS_WITH_ERRMSG( false )
2612}
2613
2614GEOSCoordSequence *QgsGeos::createCoordinateSequence( const QgsCurve *curve, double precision, bool forceClose )
2615{
2616 GEOSContextHandle_t context = QgsGeosContext::get();
2617
2618 std::unique_ptr< QgsLineString > segmentized;
2620
2621 if ( !line )
2622 {
2623 segmentized.reset( curve->curveToLine() );
2624 line = segmentized.get();
2625 }
2626
2627 if ( !line )
2628 {
2629 return nullptr;
2630 }
2631 GEOSCoordSequence *coordSeq = nullptr;
2632
2633 const int numPoints = line->numPoints();
2634
2635 const bool hasZ = line->is3D();
2636
2637#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
2638 if ( qgsDoubleNear( precision, 0 ) )
2639 {
2640 if ( !forceClose || ( line->pointN( 0 ) == line->pointN( numPoints - 1 ) ) )
2641 {
2642 // use optimised method if we don't have to force close an open ring
2643 try
2644 {
2645 coordSeq = GEOSCoordSeq_copyFromArrays_r( context, line->xData(), line->yData(), line->zData(), nullptr, numPoints );
2646 if ( !coordSeq )
2647 {
2648 QgsDebugError( u"GEOS Exception: Could not create coordinate sequence for %1 points"_s.arg( numPoints ) );
2649 return nullptr;
2650 }
2651 }
2652 CATCH_GEOS( nullptr )
2653 }
2654 else
2655 {
2656 QVector< double > x = line->xVector();
2657 if ( numPoints > 0 )
2658 x.append( x.at( 0 ) );
2659 QVector< double > y = line->yVector();
2660 if ( numPoints > 0 )
2661 y.append( y.at( 0 ) );
2662 QVector< double > z = line->zVector();
2663 if ( hasZ && numPoints > 0 )
2664 z.append( z.at( 0 ) );
2665 try
2666 {
2667 coordSeq = GEOSCoordSeq_copyFromArrays_r( context, x.constData(), y.constData(), !hasZ ? nullptr : z.constData(), nullptr, numPoints + 1 );
2668 if ( !coordSeq )
2669 {
2670 QgsDebugError( u"GEOS Exception: Could not create closed coordinate sequence for %1 points"_s.arg( numPoints + 1 ) );
2671 return nullptr;
2672 }
2673 }
2674 CATCH_GEOS( nullptr )
2675 }
2676 return coordSeq;
2677 }
2678#endif
2679
2680 int coordDims = 2;
2681 const bool hasM = false; //line->isMeasure(); //disabled until geos supports m-coordinates
2682
2683 if ( hasZ )
2684 {
2685 ++coordDims;
2686 }
2687 if ( hasM )
2688 {
2689 ++coordDims;
2690 }
2691
2692 int numOutPoints = numPoints;
2693 if ( forceClose && ( line->pointN( 0 ) != line->pointN( numPoints - 1 ) ) )
2694 {
2695 ++numOutPoints;
2696 }
2697
2698 try
2699 {
2700 coordSeq = GEOSCoordSeq_create_r( context, numOutPoints, coordDims );
2701 if ( !coordSeq )
2702 {
2703 QgsDebugError( u"GEOS Exception: Could not create coordinate sequence for %1 points in %2 dimensions"_s.arg( numPoints ).arg( coordDims ) );
2704 return nullptr;
2705 }
2706
2707 const double *xData = line->xData();
2708 const double *yData = line->yData();
2709 const double *zData = hasZ ? line->zData() : nullptr;
2710 const double *mData = hasM ? line->mData() : nullptr;
2711
2712 if ( precision > 0. )
2713 {
2714 for ( int i = 0; i < numOutPoints; ++i )
2715 {
2716 if ( i >= numPoints )
2717 {
2718 // start reading back from start of line
2719 xData = line->xData();
2720 yData = line->yData();
2721 zData = hasZ ? line->zData() : nullptr;
2722 mData = hasM ? line->mData() : nullptr;
2723 }
2724 if ( hasZ )
2725 {
2726 GEOSCoordSeq_setXYZ_r( context, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision, std::round( *zData++ / precision ) * precision );
2727 }
2728 else
2729 {
2730 GEOSCoordSeq_setXY_r( context, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision );
2731 }
2732 if ( hasM )
2733 {
2734 GEOSCoordSeq_setOrdinate_r( context, coordSeq, i, 3, *mData++ );
2735 }
2736 }
2737 }
2738 else
2739 {
2740 for ( int i = 0; i < numOutPoints; ++i )
2741 {
2742 if ( i >= numPoints )
2743 {
2744 // start reading back from start of line
2745 xData = line->xData();
2746 yData = line->yData();
2747 zData = hasZ ? line->zData() : nullptr;
2748 mData = hasM ? line->mData() : nullptr;
2749 }
2750 if ( hasZ )
2751 {
2752 GEOSCoordSeq_setXYZ_r( context, coordSeq, i, *xData++, *yData++, *zData++ );
2753 }
2754 else
2755 {
2756 GEOSCoordSeq_setXY_r( context, coordSeq, i, *xData++, *yData++ );
2757 }
2758 if ( hasM )
2759 {
2760 GEOSCoordSeq_setOrdinate_r( context, coordSeq, i, 3, *mData++ );
2761 }
2762 }
2763 }
2764 }
2765 CATCH_GEOS( nullptr )
2766
2767 return coordSeq;
2768}
2769
2770geos::unique_ptr QgsGeos::createGeosPoint( const QgsAbstractGeometry *point, int coordDims, double precision, Qgis::GeosCreationFlags )
2771{
2772 const QgsPoint *pt = qgsgeometry_cast<const QgsPoint *>( point );
2773 if ( !pt )
2774 return nullptr;
2775
2776 return createGeosPointXY( pt->x(), pt->y(), pt->is3D(), pt->z(), pt->isMeasure(), pt->m(), coordDims, precision );
2777}
2778
2779geos::unique_ptr QgsGeos::createGeosPointXY( double x, double y, bool hasZ, double z, bool hasM, double m, int coordDims, double precision, Qgis::GeosCreationFlags )
2780{
2781 Q_UNUSED( hasM )
2782 Q_UNUSED( m )
2783
2784 geos::unique_ptr geosPoint;
2785 GEOSContextHandle_t context = QgsGeosContext::get();
2786 try
2787 {
2788 if ( coordDims == 2 )
2789 {
2790 // optimised constructor
2791 if ( precision > 0. )
2792 geosPoint.reset( GEOSGeom_createPointFromXY_r( context, std::round( x / precision ) * precision, std::round( y / precision ) * precision ) );
2793 else
2794 geosPoint.reset( GEOSGeom_createPointFromXY_r( context, x, y ) );
2795 return geosPoint;
2796 }
2797
2798 GEOSCoordSequence *coordSeq = GEOSCoordSeq_create_r( context, 1, coordDims );
2799 if ( !coordSeq )
2800 {
2801 QgsDebugError( u"GEOS Exception: Could not create coordinate sequence for point with %1 dimensions"_s.arg( coordDims ) );
2802 return nullptr;
2803 }
2804 if ( precision > 0. )
2805 {
2806 GEOSCoordSeq_setX_r( context, coordSeq, 0, std::round( x / precision ) * precision );
2807 GEOSCoordSeq_setY_r( context, coordSeq, 0, std::round( y / precision ) * precision );
2808 if ( hasZ )
2809 {
2810 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 2, std::round( z / precision ) * precision );
2811 }
2812 }
2813 else
2814 {
2815 GEOSCoordSeq_setX_r( context, coordSeq, 0, x );
2816 GEOSCoordSeq_setY_r( context, coordSeq, 0, y );
2817 if ( hasZ )
2818 {
2819 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 2, z );
2820 }
2821 }
2822#if 0 //disabled until geos supports m-coordinates
2823 if ( hasM )
2824 {
2825 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 3, m );
2826 }
2827#endif
2828 geosPoint.reset( GEOSGeom_createPoint_r( context, coordSeq ) );
2829 }
2830 CATCH_GEOS( nullptr )
2831 return geosPoint;
2832}
2833
2834geos::unique_ptr QgsGeos::createGeosLinestring( const QgsAbstractGeometry *curve, double precision, Qgis::GeosCreationFlags )
2835{
2836 const QgsCurve *c = qgsgeometry_cast<const QgsCurve *>( curve );
2837 if ( !c )
2838 return nullptr;
2839
2840 GEOSCoordSequence *coordSeq = createCoordinateSequence( c, precision );
2841 if ( !coordSeq )
2842 return nullptr;
2843
2844 geos::unique_ptr geosGeom;
2845 try
2846 {
2847 geosGeom.reset( GEOSGeom_createLineString_r( QgsGeosContext::get(), coordSeq ) );
2848 }
2849 CATCH_GEOS( nullptr )
2850 return geosGeom;
2851}
2852
2853geos::unique_ptr QgsGeos::createGeosPolygon( const QgsAbstractGeometry *poly, double precision, Qgis::GeosCreationFlags flags )
2854{
2855 const QgsCurvePolygon *polygon = qgsgeometry_cast<const QgsCurvePolygon *>( poly );
2856 if ( !polygon )
2857 return nullptr;
2858
2859 const QgsCurve *exteriorRing = polygon->exteriorRing();
2860 if ( !exteriorRing )
2861 {
2862 return nullptr;
2863 }
2864
2865 GEOSContextHandle_t context = QgsGeosContext::get();
2866 geos::unique_ptr geosPolygon;
2867 try
2868 {
2869 geos::unique_ptr exteriorRingGeos( GEOSGeom_createLinearRing_r( context, createCoordinateSequence( exteriorRing, precision, true ) ) );
2870
2871 const int nInteriorRings = polygon->numInteriorRings();
2872 QList< const QgsCurve * > holesToExport;
2873 holesToExport.reserve( nInteriorRings );
2874 for ( int i = 0; i < nInteriorRings; ++i )
2875 {
2876 const QgsCurve *interiorRing = polygon->interiorRing( i );
2877 if ( !( flags & Qgis::GeosCreationFlag::SkipEmptyInteriorRings ) || !interiorRing->isEmpty() )
2878 {
2879 holesToExport << interiorRing;
2880 }
2881 }
2882
2883 GEOSGeometry **holes = nullptr;
2884 if ( !holesToExport.empty() )
2885 {
2886 holes = new GEOSGeometry *[holesToExport.size()];
2887 for ( int i = 0; i < holesToExport.size(); ++i )
2888 {
2889 holes[i] = GEOSGeom_createLinearRing_r( context, createCoordinateSequence( holesToExport[i], precision, true ) );
2890 }
2891 }
2892
2893 geosPolygon.reset( GEOSGeom_createPolygon_r( context, exteriorRingGeos.release(), holes, holesToExport.size() ) );
2894 delete[] holes;
2895 }
2896 CATCH_GEOS( nullptr )
2897
2898 return geosPolygon;
2899}
2900
2901geos::unique_ptr QgsGeos::offsetCurve( const GEOSGeometry *geometry, double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg, QgsFeedback *feedback )
2902{
2903 if ( !geometry )
2904 return nullptr;
2905
2906 geos::unique_ptr offset;
2907 try
2908 {
2909 // Force quadrant segments to be at least 8, see
2910 // https://github.com/qgis/QGIS/issues/53165#issuecomment-1563470832
2911 if ( segments < 8 )
2912 segments = 8;
2913 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2914 offset.reset( GEOSOffsetCurve_r( QgsGeosContext::get(), geometry, distance, segments, static_cast< int >( joinStyle ), miterLimit ) );
2915 }
2916 CATCH_GEOS_WITH_ERRMSG( nullptr )
2917 return offset;
2918}
2919
2920QgsAbstractGeometry *QgsGeos::offsetCurve( double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg, QgsFeedback *feedback ) const
2921{
2922 geos::unique_ptr res = offsetCurve( mGeos.get(), distance, segments, joinStyle, miterLimit, errorMsg, feedback );
2923 if ( !res )
2924 return nullptr;
2925
2926 return fromGeos( res.get() ).release();
2927}
2928
2929std::unique_ptr<QgsAbstractGeometry> QgsGeos::singleSidedBuffer(
2930 double distance, int segments, Qgis::BufferSide side, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg, QgsFeedback *feedback
2931) const
2932{
2933 if ( !mGeos )
2934 {
2935 return nullptr;
2936 }
2937
2939 GEOSContextHandle_t context = QgsGeosContext::get();
2940 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2941 try
2942 {
2943 geos::buffer_params_unique_ptr bp( GEOSBufferParams_create_r( context ) );
2944 GEOSBufferParams_setSingleSided_r( context, bp.get(), 1 );
2945 GEOSBufferParams_setQuadrantSegments_r( context, bp.get(), segments );
2946 GEOSBufferParams_setJoinStyle_r( context, bp.get(), static_cast< int >( joinStyle ) );
2947 GEOSBufferParams_setMitreLimit_r( context, bp.get(), miterLimit ); //#spellok
2948
2949 if ( side == Qgis::BufferSide::Right )
2950 {
2951 distance = -distance;
2952 }
2953 geos.reset( GEOSBufferWithParams_r( context, mGeos.get(), bp.get(), distance ) );
2954 }
2955 CATCH_GEOS_WITH_ERRMSG( nullptr )
2956 return fromGeos( geos.get() );
2957}
2958
2959std::unique_ptr<QgsAbstractGeometry> QgsGeos::maximumInscribedCircle( double tolerance, QString *errorMsg, QgsFeedback *feedback ) const
2960{
2961 if ( !mGeos )
2962 {
2963 return nullptr;
2964 }
2965
2967 try
2968 {
2969 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2970 geos.reset( GEOSMaximumInscribedCircle_r( QgsGeosContext::get(), mGeos.get(), tolerance ) );
2971 }
2972 CATCH_GEOS_WITH_ERRMSG( nullptr )
2973 return fromGeos( geos.get() );
2974}
2975
2976std::unique_ptr<QgsAbstractGeometry> QgsGeos::largestEmptyCircle( double tolerance, const QgsAbstractGeometry *boundary, QString *errorMsg, QgsFeedback *feedback ) const
2977{
2978 if ( !mGeos )
2979 {
2980 return nullptr;
2981 }
2982
2984 try
2985 {
2986 geos::unique_ptr boundaryGeos;
2987 if ( boundary )
2988 boundaryGeos = asGeos( boundary );
2989
2990 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2991 geos.reset( GEOSLargestEmptyCircle_r( QgsGeosContext::get(), mGeos.get(), boundaryGeos.get(), tolerance ) );
2992 }
2993 CATCH_GEOS_WITH_ERRMSG( nullptr )
2994 return fromGeos( geos.get() );
2995}
2996
2997std::unique_ptr<QgsAbstractGeometry> QgsGeos::minimumWidth( QString *errorMsg, QgsFeedback *feedback ) const
2998{
2999 if ( !mGeos )
3000 {
3001 return nullptr;
3002 }
3003
3005 try
3006 {
3007 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3008 geos.reset( GEOSMinimumWidth_r( QgsGeosContext::get(), mGeos.get() ) );
3009 }
3010 CATCH_GEOS_WITH_ERRMSG( nullptr )
3011 return fromGeos( geos.get() );
3012}
3013
3014double QgsGeos::minimumClearance( QString *errorMsg, QgsFeedback *feedback ) const
3015{
3016 if ( !mGeos )
3017 {
3018 return std::numeric_limits< double >::quiet_NaN();
3019 }
3020
3022 double res = 0;
3023 try
3024 {
3025 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3026 if ( GEOSMinimumClearance_r( QgsGeosContext::get(), mGeos.get(), &res ) != 0 )
3027 return std::numeric_limits< double >::quiet_NaN();
3028 }
3029 CATCH_GEOS_WITH_ERRMSG( std::numeric_limits< double >::quiet_NaN() )
3030 return res;
3031}
3032
3033std::unique_ptr<QgsAbstractGeometry> QgsGeos::minimumClearanceLine( QString *errorMsg, QgsFeedback *feedback ) const
3034{
3035 if ( !mGeos )
3036 {
3037 return nullptr;
3038 }
3039
3041 try
3042 {
3043 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3044 geos.reset( GEOSMinimumClearanceLine_r( QgsGeosContext::get(), mGeos.get() ) );
3045 }
3046 CATCH_GEOS_WITH_ERRMSG( nullptr )
3047 return fromGeos( geos.get() );
3048}
3049
3050std::unique_ptr<QgsAbstractGeometry> QgsGeos::node( QString *errorMsg, QgsFeedback *feedback ) const
3051{
3052 if ( !mGeos )
3053 {
3054 return nullptr;
3055 }
3056
3058 try
3059 {
3060 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3061 geos.reset( GEOSNode_r( QgsGeosContext::get(), mGeos.get() ) );
3062 }
3063 CATCH_GEOS_WITH_ERRMSG( nullptr )
3064 return fromGeos( geos.get() );
3065}
3066
3067std::unique_ptr<QgsAbstractGeometry> QgsGeos::sharedPaths( const QgsAbstractGeometry *other, QString *errorMsg, QgsFeedback *feedback ) const
3068{
3069 if ( !mGeos || !other )
3070 {
3071 return nullptr;
3072 }
3073
3075 try
3076 {
3077 geos::unique_ptr otherGeos = asGeos( other );
3078 if ( !otherGeos )
3079 return nullptr;
3080
3081 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3082 geos.reset( GEOSSharedPaths_r( QgsGeosContext::get(), mGeos.get(), otherGeos.get() ) );
3083 }
3084 CATCH_GEOS_WITH_ERRMSG( nullptr )
3085 return fromGeos( geos.get() );
3086}
3087
3088std::unique_ptr<QgsAbstractGeometry> QgsGeos::reshapeGeometry( const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg ) const
3089{
3090 if ( !mGeos || mGeometry->dimension() == 0 )
3091 {
3092 if ( errorCode )
3093 {
3094 *errorCode = InvalidBaseGeometry;
3095 }
3096 return nullptr;
3097 }
3098
3099 if ( reshapeWithLine.numPoints() < 2 )
3100 {
3101 if ( errorCode )
3102 {
3103 *errorCode = InvalidInput;
3104 }
3105 return nullptr;
3106 }
3107
3108 geos::unique_ptr reshapeLineGeos = createGeosLinestring( &reshapeWithLine, mPrecision );
3109
3110 GEOSContextHandle_t context = QgsGeosContext::get();
3111 //single or multi?
3112 int numGeoms = GEOSGetNumGeometries_r( context, mGeos.get() );
3113 if ( numGeoms == -1 )
3114 {
3115 if ( errorCode )
3116 {
3117 *errorCode = InvalidBaseGeometry;
3118 }
3119 return nullptr;
3120 }
3121
3122 bool isMultiGeom = false;
3123 int geosTypeId = GEOSGeomTypeId_r( context, mGeos.get() );
3124 if ( geosTypeId == GEOS_MULTILINESTRING || geosTypeId == GEOS_MULTIPOLYGON )
3125 isMultiGeom = true;
3126
3127 bool isLine = ( mGeometry->dimension() == 1 );
3128
3129 if ( !isMultiGeom )
3130 {
3131 geos::unique_ptr reshapedGeometry;
3132 if ( isLine )
3133 {
3134 reshapedGeometry = reshapeLine( mGeos.get(), reshapeLineGeos.get(), mPrecision );
3135 }
3136 else
3137 {
3138 reshapedGeometry = reshapePolygon( mGeos.get(), reshapeLineGeos.get(), mPrecision );
3139 }
3140
3141 if ( errorCode )
3142 {
3143 if ( reshapedGeometry )
3144 *errorCode = Success;
3145 else
3146 *errorCode = NothingHappened;
3147 }
3148
3149 std::unique_ptr< QgsAbstractGeometry > reshapeResult = fromGeos( reshapedGeometry.get() );
3150 return reshapeResult;
3151 }
3152 else
3153 {
3154 try
3155 {
3156 //call reshape for each geometry part and replace mGeos with new geometry if reshape took place
3157 bool reshapeTookPlace = false;
3158
3159 geos::unique_ptr currentReshapeGeometry;
3160 GEOSGeometry **newGeoms = new GEOSGeometry *[numGeoms];
3161
3162 for ( int i = 0; i < numGeoms; ++i )
3163 {
3164 if ( isLine )
3165 currentReshapeGeometry = reshapeLine( GEOSGetGeometryN_r( context, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
3166 else
3167 currentReshapeGeometry = reshapePolygon( GEOSGetGeometryN_r( context, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
3168
3169 if ( currentReshapeGeometry )
3170 {
3171 newGeoms[i] = currentReshapeGeometry.release();
3172 reshapeTookPlace = true;
3173 }
3174 else
3175 {
3176 newGeoms[i] = GEOSGeom_clone_r( context, GEOSGetGeometryN_r( context, mGeos.get(), i ) );
3177 }
3178 }
3179
3180 geos::unique_ptr newMultiGeom;
3181 if ( isLine )
3182 {
3183 newMultiGeom.reset( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, newGeoms, numGeoms ) );
3184 }
3185 else //multipolygon
3186 {
3187 newMultiGeom.reset( GEOSGeom_createCollection_r( context, GEOS_MULTIPOLYGON, newGeoms, numGeoms ) );
3188 }
3189
3190 delete[] newGeoms;
3191 if ( !newMultiGeom )
3192 {
3193 if ( errorCode )
3194 {
3195 *errorCode = EngineError;
3196 }
3197 return nullptr;
3198 }
3199
3200 if ( reshapeTookPlace )
3201 {
3202 if ( errorCode )
3203 *errorCode = Success;
3204 std::unique_ptr< QgsAbstractGeometry > reshapedMultiGeom = fromGeos( newMultiGeom.get() );
3205 return reshapedMultiGeom;
3206 }
3207 else
3208 {
3209 if ( errorCode )
3210 {
3211 *errorCode = NothingHappened;
3212 }
3213 return nullptr;
3214 }
3215 }
3216 CATCH_GEOS_WITH_ERRMSG( nullptr )
3217 }
3218}
3219
3220std::unique_ptr< QgsAbstractGeometry > QgsGeos::mergeLines( QString *errorMsg, const QgsGeometryParameters &parameters, QgsFeedback *feedback ) const
3221{
3222 if ( !mGeos )
3223 {
3224 return nullptr;
3225 }
3226
3227 GEOSContextHandle_t context = QgsGeosContext::get();
3228 if ( GEOSGeomTypeId_r( context, mGeos.get() ) != GEOS_MULTILINESTRING )
3229 return nullptr;
3230
3231 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3233 try
3234 {
3235 double gridSize = parameters.gridSize();
3236 if ( gridSize > 0 )
3237 {
3238 geos::unique_ptr geosFixedSize( GEOSGeom_setPrecision_r( context, mGeos.get(), gridSize, 0 ) );
3239 geos.reset( GEOSLineMerge_r( context, geosFixedSize.get() ) );
3240 }
3241 else
3242 geos.reset( GEOSLineMerge_r( context, mGeos.get() ) );
3243 }
3244 CATCH_GEOS_WITH_ERRMSG( nullptr )
3245 return fromGeos( geos.get() );
3246}
3247
3248std::unique_ptr<QgsAbstractGeometry> QgsGeos::closestPoint( const QgsGeometry &other, QString *errorMsg, QgsFeedback *feedback ) const
3249{
3250 if ( !mGeos || isEmpty() || other.isEmpty() )
3251 {
3252 return nullptr;
3253 }
3254
3255 geos::unique_ptr otherGeom( asGeos( other.constGet(), mPrecision ) );
3256 if ( !otherGeom )
3257 {
3258 return nullptr;
3259 }
3260
3261 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3262 GEOSContextHandle_t context = QgsGeosContext::get();
3263 double nx = 0.0;
3264 double ny = 0.0;
3265 try
3266 {
3268 if ( mGeosPrepared ) // use faster version with prepared geometry
3269 {
3270 nearestCoord.reset( GEOSPreparedNearestPoints_r( context, mGeosPrepared.get(), otherGeom.get() ) );
3271 }
3272 else
3273 {
3274 nearestCoord.reset( GEOSNearestPoints_r( context, mGeos.get(), otherGeom.get() ) );
3275 }
3276
3277 ( void ) GEOSCoordSeq_getX_r( context, nearestCoord.get(), 0, &nx );
3278 ( void ) GEOSCoordSeq_getY_r( context, nearestCoord.get(), 0, &ny );
3279 }
3280 catch ( QgsGeosException &e )
3281 {
3282 logError( u"GEOS"_s, e.what() );
3283 if ( errorMsg )
3284 {
3285 *errorMsg = e.what();
3286 }
3287 return nullptr;
3288 }
3289
3290 return std::make_unique< QgsPoint >( nx, ny );
3291}
3292
3293std::unique_ptr<QgsAbstractGeometry> QgsGeos::shortestLine( const QgsGeometry &other, QString *errorMsg, QgsFeedback *feedback ) const
3294{
3295 if ( !mGeos || other.isEmpty() )
3296 {
3297 return nullptr;
3298 }
3299
3300 return shortestLine( other.constGet(), errorMsg, feedback );
3301}
3302
3303std::unique_ptr< QgsAbstractGeometry > QgsGeos::shortestLine( const QgsAbstractGeometry *other, QString *errorMsg, QgsFeedback *feedback ) const
3304{
3305 if ( !other || other->isEmpty() )
3306 return nullptr;
3307
3308 geos::unique_ptr otherGeom( asGeos( other, mPrecision ) );
3309 if ( !otherGeom )
3310 {
3311 return nullptr;
3312 }
3313
3314 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3315 GEOSContextHandle_t context = QgsGeosContext::get();
3316 double nx1 = 0.0;
3317 double ny1 = 0.0;
3318 double nx2 = 0.0;
3319 double ny2 = 0.0;
3320 try
3321 {
3322 geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( context, mGeos.get(), otherGeom.get() ) );
3323
3324 if ( !nearestCoord )
3325 {
3326 if ( errorMsg )
3327 *errorMsg = u"GEOS returned no nearest points"_s;
3328 return nullptr;
3329 }
3330
3331 ( void ) GEOSCoordSeq_getX_r( context, nearestCoord.get(), 0, &nx1 );
3332 ( void ) GEOSCoordSeq_getY_r( context, nearestCoord.get(), 0, &ny1 );
3333 ( void ) GEOSCoordSeq_getX_r( context, nearestCoord.get(), 1, &nx2 );
3334 ( void ) GEOSCoordSeq_getY_r( context, nearestCoord.get(), 1, &ny2 );
3335 }
3336 catch ( QgsGeosException &e )
3337 {
3338 logError( u"GEOS"_s, e.what() );
3339 if ( errorMsg )
3340 {
3341 *errorMsg = e.what();
3342 }
3343 return nullptr;
3344 }
3345
3346 auto line = std::make_unique< QgsLineString >();
3347 line->addVertex( QgsPoint( nx1, ny1 ) );
3348 line->addVertex( QgsPoint( nx2, ny2 ) );
3349 return line;
3350}
3351
3352double QgsGeos::lineLocatePoint( const QgsPoint &point, QString *errorMsg, QgsFeedback *feedback ) const
3353{
3354 if ( !mGeos )
3355 {
3356 return -1;
3357 }
3358
3359 geos::unique_ptr otherGeom( asGeos( &point, mPrecision ) );
3360 if ( !otherGeom )
3361 {
3362 return -1;
3363 }
3364
3365 double distance = -1;
3366 try
3367 {
3368 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3369 distance = GEOSProject_r( QgsGeosContext::get(), mGeos.get(), otherGeom.get() );
3370 }
3371 catch ( QgsGeosException &e )
3372 {
3373 logError( u"GEOS"_s, e.what() );
3374 if ( errorMsg )
3375 {
3376 *errorMsg = e.what();
3377 }
3378 return -1;
3379 }
3380
3381 return distance;
3382}
3383
3384double QgsGeos::lineLocatePoint( double x, double y, QString *errorMsg, QgsFeedback *feedback ) const
3385{
3386 if ( !mGeos )
3387 {
3388 return -1;
3389 }
3390
3391 geos::unique_ptr point = createGeosPointXY( x, y, false, 0, false, 0, 2, 0 );
3392 if ( !point )
3393 return false;
3394
3395 double distance = -1;
3396 try
3397 {
3398 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3399 distance = GEOSProject_r( QgsGeosContext::get(), mGeos.get(), point.get() );
3400 }
3401 catch ( QgsGeosException &e )
3402 {
3403 logError( u"GEOS"_s, e.what() );
3404 if ( errorMsg )
3405 {
3406 *errorMsg = e.what();
3407 }
3408 return -1;
3409 }
3410
3411 return distance;
3412}
3413
3414QgsGeometry QgsGeos::polygonize( const QVector<const QgsAbstractGeometry *> &geometries, QString *errorMsg, QgsFeedback *feedback )
3415{
3416 GEOSGeometry **const lineGeosGeometries = new GEOSGeometry *[geometries.size()];
3417 int validLines = 0;
3418 for ( const QgsAbstractGeometry *g : geometries )
3419 {
3420 geos::unique_ptr l = asGeos( g );
3421 if ( l )
3422 {
3423 lineGeosGeometries[validLines] = l.release();
3424 validLines++;
3425 }
3426 }
3427
3428 GEOSContextHandle_t context = QgsGeosContext::get();
3429 try
3430 {
3431 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3432 geos::unique_ptr result( GEOSPolygonize_r( context, lineGeosGeometries, validLines ) );
3433 for ( int i = 0; i < validLines; ++i )
3434 {
3435 GEOSGeom_destroy_r( context, lineGeosGeometries[i] );
3436 }
3437 delete[] lineGeosGeometries;
3438 return QgsGeometry( fromGeos( result.get() ) );
3439 }
3440 catch ( QgsGeosException &e )
3441 {
3442 if ( errorMsg )
3443 {
3444 *errorMsg = e.what();
3445 }
3446 for ( int i = 0; i < validLines; ++i )
3447 {
3448 GEOSGeom_destroy_r( context, lineGeosGeometries[i] );
3449 }
3450 delete[] lineGeosGeometries;
3451 return QgsGeometry();
3452 }
3453}
3454
3455std::unique_ptr<QgsAbstractGeometry> QgsGeos::voronoiDiagram( const QgsAbstractGeometry *extent, double tolerance, bool edgesOnly, QString *errorMsg, QgsFeedback *feedback ) const
3456{
3457 if ( !mGeos )
3458 {
3459 return nullptr;
3460 }
3461
3462 geos::unique_ptr extentGeosGeom;
3463 if ( extent )
3464 {
3465 extentGeosGeom = asGeos( extent, mPrecision );
3466 if ( !extentGeosGeom )
3467 {
3468 return nullptr;
3469 }
3470 }
3471
3473 GEOSContextHandle_t context = QgsGeosContext::get();
3474 try
3475 {
3476 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3477 geos.reset( GEOSVoronoiDiagram_r( context, mGeos.get(), extentGeosGeom.get(), tolerance, edgesOnly ) );
3478
3479 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3480 {
3481 return nullptr;
3482 }
3483
3484 return fromGeos( geos.get() );
3485 }
3486 CATCH_GEOS_WITH_ERRMSG( nullptr )
3487}
3488
3489std::unique_ptr<QgsAbstractGeometry> QgsGeos::delaunayTriangulation( double tolerance, bool edgesOnly, QString *errorMsg, QgsFeedback *feedback ) const
3490{
3491 if ( !mGeos )
3492 {
3493 return nullptr;
3494 }
3495
3496 GEOSContextHandle_t context = QgsGeosContext::get();
3498 try
3499 {
3500 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3501 geos.reset( GEOSDelaunayTriangulation_r( context, mGeos.get(), tolerance, edgesOnly ) );
3502
3503 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3504 {
3505 return nullptr;
3506 }
3507
3508 return fromGeos( geos.get() );
3509 }
3510 CATCH_GEOS_WITH_ERRMSG( nullptr )
3511}
3512
3513std::unique_ptr<QgsAbstractGeometry> QgsGeos::constrainedDelaunayTriangulation( QString *errorMsg, QgsFeedback *feedback ) const
3514{
3515#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 11
3516 ( void ) errorMsg;
3517 throw QgsNotSupportedException( QObject::tr( "Calculating constrainedDelaunayTriangulation requires a QGIS build based on GEOS 3.11 or later" ) );
3518#else
3519 if ( !mGeos )
3520 {
3521 return nullptr;
3522 }
3523
3525 GEOSContextHandle_t context = QgsGeosContext::get();
3526 try
3527 {
3528 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3529 geos.reset( GEOSConstrainedDelaunayTriangulation_r( context, mGeos.get() ) );
3530
3531 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3532 {
3533 return nullptr;
3534 }
3535
3536 std::unique_ptr< QgsAbstractGeometry > res = fromGeos( geos.get() );
3537 if ( const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( res.get() ) )
3538 {
3539 return std::unique_ptr< QgsAbstractGeometry >( collection->extractPartsByType( Qgis::WkbType::Polygon, true ) );
3540 }
3541 else
3542 {
3543 return res;
3544 }
3545 }
3546 CATCH_GEOS_WITH_ERRMSG( nullptr )
3547#endif
3548}
3549
3551static bool _linestringEndpoints( const GEOSGeometry *linestring, double &x1, double &y1, double &x2, double &y2 )
3552{
3553 GEOSContextHandle_t context = QgsGeosContext::get();
3554 const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( context, linestring );
3555 if ( !coordSeq )
3556 return false;
3557
3558 unsigned int coordSeqSize;
3559 if ( GEOSCoordSeq_getSize_r( context, coordSeq, &coordSeqSize ) == 0 )
3560 return false;
3561
3562 if ( coordSeqSize < 2 )
3563 return false;
3564
3565 GEOSCoordSeq_getX_r( context, coordSeq, 0, &x1 );
3566 GEOSCoordSeq_getY_r( context, coordSeq, 0, &y1 );
3567 GEOSCoordSeq_getX_r( context, coordSeq, coordSeqSize - 1, &x2 );
3568 GEOSCoordSeq_getY_r( context, coordSeq, coordSeqSize - 1, &y2 );
3569 return true;
3570}
3571
3572
3574static geos::unique_ptr _mergeLinestrings( const GEOSGeometry *line1, const GEOSGeometry *line2, const QgsPointXY &intersectionPoint )
3575{
3576 double x1, y1, x2, y2;
3577 if ( !_linestringEndpoints( line1, x1, y1, x2, y2 ) )
3578 return nullptr;
3579
3580 double rx1, ry1, rx2, ry2;
3581 if ( !_linestringEndpoints( line2, rx1, ry1, rx2, ry2 ) )
3582 return nullptr;
3583
3584 bool intersectionAtOrigLineEndpoint = ( intersectionPoint.x() == x1 && intersectionPoint.y() == y1 ) != ( intersectionPoint.x() == x2 && intersectionPoint.y() == y2 );
3585 bool intersectionAtReshapeLineEndpoint = ( intersectionPoint.x() == rx1 && intersectionPoint.y() == ry1 ) || ( intersectionPoint.x() == rx2 && intersectionPoint.y() == ry2 );
3586
3587 GEOSContextHandle_t context = QgsGeosContext::get();
3588 // the intersection must be at the begin/end of both lines
3589 if ( intersectionAtOrigLineEndpoint && intersectionAtReshapeLineEndpoint )
3590 {
3591 geos::unique_ptr g1( GEOSGeom_clone_r( context, line1 ) );
3592 geos::unique_ptr g2( GEOSGeom_clone_r( context, line2 ) );
3593 GEOSGeometry *geoms[2] = { g1.release(), g2.release() };
3594 geos::unique_ptr multiGeom( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, geoms, 2 ) );
3595 geos::unique_ptr res( GEOSLineMerge_r( context, multiGeom.get() ) );
3596
3597 //keep the original orientation if the result has a start or end point in common with the original line
3598 //and this point is not the start or the end point for both lines
3599 double x1res, y1res, x2res, y2res;
3600 if ( !_linestringEndpoints( res.get(), x1res, y1res, x2res, y2res ) )
3601 return nullptr;
3602 if ( ( x1res == x2 && y1res == y2 ) || ( x2res == x1 && y2res == y1 ) )
3603 res.reset( GEOSReverse_r( context, res.get() ) );
3604
3605 return res;
3606 }
3607 else
3608 return nullptr;
3609}
3610
3611
3612geos::unique_ptr QgsGeos::reshapeLine( const GEOSGeometry *line, const GEOSGeometry *reshapeLineGeos, double precision )
3613{
3614 if ( !line || !reshapeLineGeos )
3615 return nullptr;
3616
3617 bool atLeastTwoIntersections = false;
3618 bool oneIntersection = false;
3619 QgsPointXY oneIntersectionPoint;
3620
3621 GEOSContextHandle_t context = QgsGeosContext::get();
3622 try
3623 {
3624 //make sure there are at least two intersection between line and reshape geometry
3625 geos::unique_ptr intersectGeom( GEOSIntersection_r( context, line, reshapeLineGeos ) );
3626 if ( intersectGeom )
3627 {
3628 const int geomType = GEOSGeomTypeId_r( context, intersectGeom.get() );
3629 atLeastTwoIntersections = ( geomType == GEOS_MULTIPOINT && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 1 )
3630 || ( geomType == GEOS_GEOMETRYCOLLECTION && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 0 ) // a collection implies at least two points!
3631 || ( geomType == GEOS_MULTILINESTRING && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 0 );
3632 // one point is enough when extending line at its endpoint
3633 if ( GEOSGeomTypeId_r( context, intersectGeom.get() ) == GEOS_POINT )
3634 {
3635 const GEOSCoordSequence *intersectionCoordSeq = GEOSGeom_getCoordSeq_r( context, intersectGeom.get() );
3636 double xi, yi;
3637 GEOSCoordSeq_getX_r( context, intersectionCoordSeq, 0, &xi );
3638 GEOSCoordSeq_getY_r( context, intersectionCoordSeq, 0, &yi );
3639 oneIntersection = true;
3640 oneIntersectionPoint = QgsPointXY( xi, yi );
3641 }
3642 }
3643 }
3644 catch ( QgsGeosException & )
3645 {
3646 atLeastTwoIntersections = false;
3647 }
3648
3649 // special case when extending line at its endpoint
3650 if ( oneIntersection )
3651 return _mergeLinestrings( line, reshapeLineGeos, oneIntersectionPoint );
3652
3653 if ( !atLeastTwoIntersections )
3654 return nullptr;
3655
3656 //begin and end point of original line
3657 double x1, y1, x2, y2;
3658 if ( !_linestringEndpoints( line, x1, y1, x2, y2 ) )
3659 return nullptr;
3660
3661 geos::unique_ptr beginLineVertex = createGeosPointXY( x1, y1, false, 0, false, 0, 2, precision );
3662 geos::unique_ptr endLineVertex = createGeosPointXY( x2, y2, false, 0, false, 0, 2, precision );
3663
3664 bool isRing = false;
3665 if ( GEOSGeomTypeId_r( context, line ) == GEOS_LINEARRING || GEOSEquals_r( context, beginLineVertex.get(), endLineVertex.get() ) == 1 )
3666 isRing = true;
3667
3668 //node line and reshape line
3669 geos::unique_ptr nodedGeometry = nodeGeometries( reshapeLineGeos, line );
3670 if ( !nodedGeometry )
3671 {
3672 return nullptr;
3673 }
3674
3675 //and merge them together
3676 geos::unique_ptr mergedLines( GEOSLineMerge_r( context, nodedGeometry.get() ) );
3677 if ( !mergedLines )
3678 {
3679 return nullptr;
3680 }
3681
3682 int numMergedLines = GEOSGetNumGeometries_r( context, mergedLines.get() );
3683 if ( numMergedLines < 2 ) //some special cases. Normally it is >2
3684 {
3685 if ( numMergedLines == 1 ) //reshape line is from begin to endpoint. So we keep the reshapeline
3686 {
3687 geos::unique_ptr result( GEOSGeom_clone_r( context, reshapeLineGeos ) );
3688 return result;
3689 }
3690 else
3691 return nullptr;
3692 }
3693
3694 QVector<GEOSGeometry *> resultLineParts; //collection with the line segments that will be contained in result
3695 QVector<GEOSGeometry *> probableParts; //parts where we can decide on inclusion only after going through all the candidates
3696
3697 for ( int i = 0; i < numMergedLines; ++i )
3698 {
3699 const GEOSGeometry *currentGeom = GEOSGetGeometryN_r( context, mergedLines.get(), i );
3700
3701 // have we already added this part?
3702 bool alreadyAdded = false;
3703 double distance = 0;
3704 double bufferDistance = std::pow( 10.0L, geomDigits( currentGeom ) - 11 );
3705 for ( const GEOSGeometry *other : std::as_const( resultLineParts ) )
3706 {
3707 GEOSHausdorffDistance_r( context, currentGeom, other, &distance );
3708 if ( distance < bufferDistance )
3709 {
3710 alreadyAdded = true;
3711 break;
3712 }
3713 }
3714 if ( alreadyAdded )
3715 continue;
3716
3717 const GEOSCoordSequence *currentCoordSeq = GEOSGeom_getCoordSeq_r( context, currentGeom );
3718 unsigned int currentCoordSeqSize;
3719 GEOSCoordSeq_getSize_r( context, currentCoordSeq, &currentCoordSeqSize );
3720 if ( currentCoordSeqSize < 2 )
3721 continue;
3722
3723 //get the two endpoints of the current line merge result
3724 double xBegin, xEnd, yBegin, yEnd;
3725 GEOSCoordSeq_getX_r( context, currentCoordSeq, 0, &xBegin );
3726 GEOSCoordSeq_getY_r( context, currentCoordSeq, 0, &yBegin );
3727 GEOSCoordSeq_getX_r( context, currentCoordSeq, currentCoordSeqSize - 1, &xEnd );
3728 GEOSCoordSeq_getY_r( context, currentCoordSeq, currentCoordSeqSize - 1, &yEnd );
3729 geos::unique_ptr beginCurrentGeomVertex = createGeosPointXY( xBegin, yBegin, false, 0, false, 0, 2, precision );
3730 geos::unique_ptr endCurrentGeomVertex = createGeosPointXY( xEnd, yEnd, false, 0, false, 0, 2, precision );
3731
3732 //check how many endpoints of the line merge result are on the (original) line
3733 int nEndpointsOnOriginalLine = 0;
3734 if ( pointContainedInLine( beginCurrentGeomVertex.get(), line ) == 1 )
3735 nEndpointsOnOriginalLine += 1;
3736
3737 if ( pointContainedInLine( endCurrentGeomVertex.get(), line ) == 1 )
3738 nEndpointsOnOriginalLine += 1;
3739
3740 //check how many endpoints equal the endpoints of the original line
3741 int nEndpointsSameAsOriginalLine = 0;
3742 if ( GEOSEquals_r( context, beginCurrentGeomVertex.get(), beginLineVertex.get() ) == 1 || GEOSEquals_r( context, beginCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
3743 nEndpointsSameAsOriginalLine += 1;
3744
3745 if ( GEOSEquals_r( context, endCurrentGeomVertex.get(), beginLineVertex.get() ) == 1 || GEOSEquals_r( context, endCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
3746 nEndpointsSameAsOriginalLine += 1;
3747
3748 //check if the current geometry overlaps the original geometry (GEOSOverlap does not seem to work with linestrings)
3749 bool currentGeomOverlapsOriginalGeom = false;
3750 bool currentGeomOverlapsReshapeLine = false;
3751 if ( lineContainedInLine( currentGeom, line ) == 1 )
3752 currentGeomOverlapsOriginalGeom = true;
3753
3754 if ( lineContainedInLine( currentGeom, reshapeLineGeos ) == 1 )
3755 currentGeomOverlapsReshapeLine = true;
3756
3757 //logic to decide if this part belongs to the result
3758 if ( !isRing && nEndpointsSameAsOriginalLine == 1 && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
3759 {
3760 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3761 }
3762 //for closed rings, we take one segment from the candidate list
3763 else if ( isRing && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
3764 {
3765 probableParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3766 }
3767 else if ( nEndpointsOnOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
3768 {
3769 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3770 }
3771 else if ( nEndpointsSameAsOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
3772 {
3773 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3774 }
3775 else if ( currentGeomOverlapsOriginalGeom && currentGeomOverlapsReshapeLine )
3776 {
3777 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3778 }
3779 }
3780
3781 //add the longest segment from the probable list for rings (only used for polygon rings)
3782 if ( isRing && !probableParts.isEmpty() )
3783 {
3784 geos::unique_ptr maxGeom; //the longest geometry in the probabla list
3785 GEOSGeometry *currentGeom = nullptr;
3786 double maxLength = -std::numeric_limits<double>::max();
3787 double currentLength = 0;
3788 for ( int i = 0; i < probableParts.size(); ++i )
3789 {
3790 currentGeom = probableParts.at( i );
3791 GEOSLength_r( context, currentGeom, &currentLength );
3792 if ( currentLength > maxLength )
3793 {
3794 maxLength = currentLength;
3795 maxGeom.reset( currentGeom );
3796 }
3797 else
3798 {
3799 GEOSGeom_destroy_r( context, currentGeom );
3800 }
3801 }
3802 resultLineParts.push_back( maxGeom.release() );
3803 }
3804
3805 geos::unique_ptr result;
3806 if ( resultLineParts.empty() )
3807 return nullptr;
3808
3809 if ( resultLineParts.size() == 1 ) //the whole result was reshaped
3810 {
3811 result.reset( resultLineParts[0] );
3812 }
3813 else //>1
3814 {
3815 GEOSGeometry **lineArray = new GEOSGeometry *[resultLineParts.size()];
3816 for ( int i = 0; i < resultLineParts.size(); ++i )
3817 {
3818 lineArray[i] = resultLineParts[i];
3819 }
3820
3821 //create multiline from resultLineParts
3822 geos::unique_ptr multiLineGeom( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, lineArray, resultLineParts.size() ) );
3823 delete[] lineArray;
3824
3825 //then do a linemerge with the newly combined partstrings
3826 result.reset( GEOSLineMerge_r( context, multiLineGeom.get() ) );
3827 }
3828
3829 //now test if the result is a linestring. Otherwise something went wrong
3830 if ( GEOSGeomTypeId_r( context, result.get() ) != GEOS_LINESTRING )
3831 {
3832 return nullptr;
3833 }
3834
3835 //keep the original orientation
3836 bool reverseLine = false;
3837 if ( isRing )
3838 {
3839 //for closed linestring check clockwise/counter-clockwise
3840 char isResultCCW = 0, isOriginCCW = 0;
3841 if ( GEOSCoordSeq_isCCW_r( context, GEOSGeom_getCoordSeq_r( context, result.get() ), &isResultCCW ) && GEOSCoordSeq_isCCW_r( context, GEOSGeom_getCoordSeq_r( context, line ), &isOriginCCW ) )
3842 {
3843 //reverse line if orientations are different
3844 reverseLine = ( isOriginCCW == 1 && isResultCCW == 0 ) || ( isOriginCCW == 0 && isResultCCW == 1 );
3845 }
3846 }
3847 else
3848 {
3849 //for linestring, check if the result has a start or end point in common with the original line
3850 double x1res, y1res, x2res, y2res;
3851 if ( !_linestringEndpoints( result.get(), x1res, y1res, x2res, y2res ) )
3852 return nullptr;
3853 geos::unique_ptr beginResultLineVertex = createGeosPointXY( x1res, y1res, false, 0, false, 0, 2, precision );
3854 geos::unique_ptr endResultLineVertex = createGeosPointXY( x2res, y2res, false, 0, false, 0, 2, precision );
3855 reverseLine = GEOSEquals_r( context, beginLineVertex.get(), endResultLineVertex.get() ) == 1 || GEOSEquals_r( context, endLineVertex.get(), beginResultLineVertex.get() ) == 1;
3856 }
3857 if ( reverseLine )
3858 result.reset( GEOSReverse_r( context, result.get() ) );
3859
3860 return result;
3861}
3862
3863geos::unique_ptr QgsGeos::reshapePolygon( const GEOSGeometry *polygon, const GEOSGeometry *reshapeLineGeos, double precision )
3864{
3865 //go through outer shell and all inner rings and check if there is exactly one intersection of a ring and the reshape line
3866 int nIntersections = 0;
3867 int lastIntersectingRing = -2;
3868 const GEOSGeometry *lastIntersectingGeom = nullptr;
3869
3870 GEOSContextHandle_t context = QgsGeosContext::get();
3871 int nRings = GEOSGetNumInteriorRings_r( context, polygon );
3872 if ( nRings < 0 )
3873 return nullptr;
3874
3875 //does outer ring intersect?
3876 const GEOSGeometry *outerRing = GEOSGetExteriorRing_r( context, polygon );
3877 if ( GEOSIntersects_r( context, outerRing, reshapeLineGeos ) == 1 )
3878 {
3879 ++nIntersections;
3880 lastIntersectingRing = -1;
3881 lastIntersectingGeom = outerRing;
3882 }
3883
3884 //do inner rings intersect?
3885 const GEOSGeometry **innerRings = new const GEOSGeometry *[nRings];
3886
3887 try
3888 {
3889 for ( int i = 0; i < nRings; ++i )
3890 {
3891 innerRings[i] = GEOSGetInteriorRingN_r( context, polygon, i );
3892 if ( GEOSIntersects_r( context, innerRings[i], reshapeLineGeos ) == 1 )
3893 {
3894 ++nIntersections;
3895 lastIntersectingRing = i;
3896 lastIntersectingGeom = innerRings[i];
3897 }
3898 }
3899 }
3900 catch ( QgsGeosException & )
3901 {
3902 nIntersections = 0;
3903 }
3904
3905 if ( nIntersections != 1 ) //reshape line is only allowed to intersect one ring
3906 {
3907 delete[] innerRings;
3908 return nullptr;
3909 }
3910
3911 //we have one intersecting ring, let's try to reshape it
3912 geos::unique_ptr reshapeResult = reshapeLine( lastIntersectingGeom, reshapeLineGeos, precision );
3913 if ( !reshapeResult )
3914 {
3915 delete[] innerRings;
3916 return nullptr;
3917 }
3918
3919 //if reshaping took place, we need to reassemble the polygon and its rings
3920 GEOSGeometry *newRing = nullptr;
3921 const GEOSCoordSequence *reshapeSequence = GEOSGeom_getCoordSeq_r( context, reshapeResult.get() );
3922 GEOSCoordSequence *newCoordSequence = GEOSCoordSeq_clone_r( context, reshapeSequence );
3923
3924 reshapeResult.reset();
3925
3926 try
3927 {
3928 newRing = GEOSGeom_createLinearRing_r( context, newCoordSequence );
3929 }
3930 catch ( QgsGeosException & )
3931 {
3932 // nothing to do: on exception newRing will be null
3933 }
3934
3935 if ( !newRing )
3936 {
3937 delete[] innerRings;
3938 return nullptr;
3939 }
3940
3941 GEOSGeometry *newOuterRing = nullptr;
3942 if ( lastIntersectingRing == -1 )
3943 newOuterRing = newRing;
3944 else
3945 newOuterRing = GEOSGeom_clone_r( context, outerRing );
3946
3947 //check if all the rings are still inside the outer boundary
3948 QVector<GEOSGeometry *> ringList;
3949 if ( nRings > 0 )
3950 {
3951 GEOSGeometry *outerRingPoly = GEOSGeom_createPolygon_r( context, GEOSGeom_clone_r( context, newOuterRing ), nullptr, 0 );
3952 if ( outerRingPoly )
3953 {
3954 ringList.reserve( nRings );
3955 GEOSGeometry *currentRing = nullptr;
3956 for ( int i = 0; i < nRings; ++i )
3957 {
3958 if ( lastIntersectingRing == i )
3959 currentRing = newRing;
3960 else
3961 currentRing = GEOSGeom_clone_r( context, innerRings[i] );
3962
3963 //possibly a ring is no longer contained in the result polygon after reshape
3964 if ( GEOSContains_r( context, outerRingPoly, currentRing ) == 1 )
3965 ringList.push_back( currentRing );
3966 else
3967 GEOSGeom_destroy_r( context, currentRing );
3968 }
3969 }
3970 GEOSGeom_destroy_r( context, outerRingPoly );
3971 }
3972
3973 GEOSGeometry **newInnerRings = new GEOSGeometry *[ringList.size()];
3974 for ( int i = 0; i < ringList.size(); ++i )
3975 newInnerRings[i] = ringList.at( i );
3976
3977 delete[] innerRings;
3978
3979 geos::unique_ptr reshapedPolygon( GEOSGeom_createPolygon_r( context, newOuterRing, newInnerRings, ringList.size() ) );
3980 delete[] newInnerRings;
3981
3982 return reshapedPolygon;
3983}
3984
3985int QgsGeos::lineContainedInLine( const GEOSGeometry *line1, const GEOSGeometry *line2 )
3986{
3987 if ( !line1 || !line2 )
3988 {
3989 return -1;
3990 }
3991
3992 double bufferDistance = std::pow( 10.0L, geomDigits( line2 ) - 11 );
3993
3994 GEOSContextHandle_t context = QgsGeosContext::get();
3995 geos::unique_ptr bufferGeom( GEOSBuffer_r( context, line2, bufferDistance, DEFAULT_QUADRANT_SEGMENTS ) );
3996 if ( !bufferGeom )
3997 return -2;
3998
3999 geos::unique_ptr intersectionGeom( GEOSIntersection_r( context, bufferGeom.get(), line1 ) );
4000
4001 //compare ratio between line1Length and intersectGeomLength (usually close to 1 if line1 is contained in line2)
4002 double intersectGeomLength;
4003 double line1Length;
4004
4005 GEOSLength_r( context, intersectionGeom.get(), &intersectGeomLength );
4006 GEOSLength_r( context, line1, &line1Length );
4007
4008 double intersectRatio = line1Length / intersectGeomLength;
4009 if ( intersectRatio > 0.9 && intersectRatio < 1.1 )
4010 return 1;
4011
4012 return 0;
4013}
4014
4015int QgsGeos::pointContainedInLine( const GEOSGeometry *point, const GEOSGeometry *line )
4016{
4017 if ( !point || !line )
4018 return -1;
4019
4020 double bufferDistance = std::pow( 10.0L, geomDigits( line ) - 11 );
4021
4022 GEOSContextHandle_t context = QgsGeosContext::get();
4023 geos::unique_ptr lineBuffer( GEOSBuffer_r( context, line, bufferDistance, 8 ) );
4024 if ( !lineBuffer )
4025 return -2;
4026
4027 bool contained = false;
4028 if ( GEOSContains_r( context, lineBuffer.get(), point ) == 1 )
4029 contained = true;
4030
4031 return contained;
4032}
4033
4034int QgsGeos::geomDigits( const GEOSGeometry *geom )
4035{
4036 GEOSContextHandle_t context = QgsGeosContext::get();
4037 geos::unique_ptr bbox( GEOSEnvelope_r( context, geom ) );
4038 if ( !bbox )
4039 return -1;
4040
4041 const GEOSGeometry *bBoxRing = GEOSGetExteriorRing_r( context, bbox.get() );
4042 if ( !bBoxRing )
4043 return -1;
4044
4045 const GEOSCoordSequence *bBoxCoordSeq = GEOSGeom_getCoordSeq_r( context, bBoxRing );
4046
4047 if ( !bBoxCoordSeq )
4048 return -1;
4049
4050 unsigned int nCoords = 0;
4051 if ( !GEOSCoordSeq_getSize_r( context, bBoxCoordSeq, &nCoords ) )
4052 return -1;
4053
4054 int maxDigits = -1;
4055 for ( unsigned int i = 0; i < nCoords - 1; ++i )
4056 {
4057 double t;
4058 GEOSCoordSeq_getX_r( context, bBoxCoordSeq, i, &t );
4059
4060 int digits;
4061 digits = std::ceil( std::log10( std::fabs( t ) ) );
4062 if ( digits > maxDigits )
4063 maxDigits = digits;
4064
4065 GEOSCoordSeq_getY_r( context, bBoxCoordSeq, i, &t );
4066 digits = std::ceil( std::log10( std::fabs( t ) ) );
4067 if ( digits > maxDigits )
4068 maxDigits = digits;
4069 }
4070
4071 return maxDigits;
4072}
4073
4075#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 14 )
4076 : mFeedback( feedback )
4077{
4078 GEOSContext_setInterruptCallback_r( QgsGeosContext::get(), &callback, reinterpret_cast< void * >( mFeedback ) );
4079}
4080#else
4081{
4082 ( void ) feedback;
4083}
4084#endif
4085
4086
4088{
4089#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 14 )
4090 GEOSContext_setInterruptCallback_r( QgsGeosContext::get(), nullptr, nullptr );
4091#endif
4092}
4093
4094#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 14 )
4095int QgsScopedGeosContextRegisterFeedback::callback( void *userData )
4096{
4097 if ( !userData )
4098 return 0;
4099
4100 QgsFeedback *feedback = reinterpret_cast< QgsFeedback * >( userData );
4101 return feedback && feedback->isCanceled() ? 1 : 0;
4102}
4103#endif
Provides global constants and enumerations for use throughout the application.
Definition qgis.h:62
BufferSide
Side of line to buffer.
Definition qgis.h:2247
@ Right
Buffer to right of line.
Definition qgis.h:2249
@ LongestBorder
Polygon with longest common border is selected to merge overlapping polygons into.
Definition qgis.h:6937
@ MaximumArea
Polygon with largest area is selected to merge overlapping polygons into.
Definition qgis.h:6938
@ MinimumArea
Polygon with minimum area is selected to merge overlapping polygons into.
Definition qgis.h:6939
@ MinimumIndex
Polygon with smallest input index is selected to merge overlapping polygons into.
Definition qgis.h:6940
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:2192
@ AddPartNotMultiGeometry
The source geometry is not multi.
Definition qgis.h:2203
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
Definition qgis.h:2195
@ RejectOnInvalidSubGeometry
Don't allow geometries with invalid sub-geometries to be created.
Definition qgis.h:2311
@ SkipEmptyInteriorRings
Skip any empty polygon interior ring.
Definition qgis.h:2312
QFlags< GeosCreationFlag > GeosCreationFlags
Geos geometry creation behavior flags.
Definition qgis.h:2321
@ 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:2272
EndCapStyle
End cap styles for buffers.
Definition qgis.h:2259
CoverageValidityResult
Coverage validity results.
Definition qgis.h:2330
@ Valid
Coverage is valid.
Definition qgis.h:2332
@ Invalid
Coverage is invalid. Invalidity includes polygons that overlap, that have gaps smaller than the gap w...
Definition qgis.h:2331
@ Error
An exception occurred while determining validity.
Definition qgis.h:2333
MakeValidMethod
Algorithms to use when repairing invalid geometries.
Definition qgis.h:2343
@ Linework
Combines all rings into a set of noded lines and then extracts valid polygons from that linework.
Definition qgis.h:2344
@ Structure
Structured method, first makes all rings valid and then merges shells and subtracts holes from shells...
Definition qgis.h:2345
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:2929
double minimumClearance(QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const
Computes the minimum clearance of a geometry.
Definition qgsgeos.cpp:3014
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:3088
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:3067
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:3033
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:3248
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:2564
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:2901
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:3352
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:2976
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:3513
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:2600
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:2479
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:2997
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:3293
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:3050
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:2543
std::unique_ptr< QgsAbstractGeometry > maximumInscribedCircle(double tolerance, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const
Returns the maximum inscribed circle.
Definition qgsgeos.cpp:2959
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:3455
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:3489
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:2586
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:3220
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:3414
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:4074
~QgsScopedGeosContextRegisterFeedback()
Resets the GEOS interruption checker for the current thread.
Definition qgsgeos.cpp:4087
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:7417
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