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