QGIS API Documentation 4.3.0-Master (0cfde48c85b)
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 int nCoordDims = GEOSGeom_getCoordinateDimension_r( context, geos );
1731 int nDims = GEOSGeom_getDimensions_r( context, geos );
1732 bool hasZ = ( nCoordDims == 3 );
1733 bool hasM = ( ( nDims - nCoordDims ) == 1 );
1734
1735 switch ( GEOSGeomTypeId_r( context, geos ) )
1736 {
1737 case GEOS_POINT: // a point
1738 {
1739 if ( GEOSisEmpty_r( context, geos ) )
1740 return nullptr;
1741
1742 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( context, geos );
1743 unsigned int nPoints = 0;
1744 GEOSCoordSeq_getSize_r( context, cs, &nPoints );
1745 if ( nPoints == 0 )
1746 {
1747 return nullptr;
1748 }
1749 // Since GEOS 3.13, Points with NAN coordinates are not considered empty anymore
1750 // See: https://github.com/libgeos/geos/pull/927
1751 // Handle this change by checking if QgsPoint is empty
1752 const QgsPoint point = coordSeqPoint( cs, 0, hasZ, hasM );
1753 return !point.isEmpty() ? std::unique_ptr<QgsAbstractGeometry>( point.clone() ) : nullptr;
1754 }
1755 case GEOS_LINESTRING:
1756 {
1757 return sequenceToLinestring( geos, hasZ, hasM );
1758 }
1759#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
1760 case GEOS_CIRCULARSTRING:
1761 {
1762 return sequenceToCircularString( geos, hasZ, hasM );
1763 }
1764 case GEOS_COMPOUNDCURVE:
1765 {
1766 auto compoundCurve = std::make_unique< QgsCompoundCurve >();
1767 const int nCurves = GEOSGetNumCurves_r( context, geos );
1768 for ( int i = 0; i < nCurves; i++ )
1769 {
1770 std::unique_ptr< QgsSimpleCurve > curve = sequenceToSimpleCurve( GEOSGetCurveN_r( context, geos, i ), hasZ, hasM );
1771 compoundCurve->addCurve( curve.release(), true );
1772 }
1773 return std::move( compoundCurve );
1774 }
1775#endif
1776 case GEOS_POLYGON:
1777 {
1778 return fromGeosPolygon( geos );
1779 }
1780#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
1781 case GEOS_CURVEPOLYGON:
1782 {
1783 return fromGeosCurvePolygon( geos );
1784 }
1785#endif
1786 case GEOS_MULTIPOINT:
1787 {
1788 auto multiPoint = std::make_unique<QgsMultiPoint>();
1789 int nParts = GEOSGetNumGeometries_r( context, geos );
1790 multiPoint->reserve( nParts );
1791 for ( int i = 0; i < nParts; ++i )
1792 {
1793 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( context, GEOSGetGeometryN_r( context, geos, i ) );
1794 if ( cs )
1795 {
1796 unsigned int nPoints = 0;
1797 GEOSCoordSeq_getSize_r( context, cs, &nPoints );
1798 if ( nPoints > 0 )
1799 multiPoint->addGeometry( coordSeqPoint( cs, 0, hasZ, hasM ).clone() );
1800 }
1801 }
1802 return std::move( multiPoint );
1803 }
1804 case GEOS_MULTILINESTRING:
1805 {
1806 auto multiLineString = std::make_unique<QgsMultiLineString>();
1807 int nParts = GEOSGetNumGeometries_r( context, geos );
1808 multiLineString->reserve( nParts );
1809 for ( int i = 0; i < nParts; ++i )
1810 {
1811 std::unique_ptr< QgsLineString > line( sequenceToLinestring( GEOSGetGeometryN_r( context, geos, i ), hasZ, hasM ) );
1812 if ( line )
1813 {
1814 multiLineString->addGeometry( line.release() );
1815 }
1816 }
1817 return std::move( multiLineString );
1818 }
1819#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
1820 case GEOS_MULTICURVE:
1821 {
1822 auto multiCurve = std::make_unique<QgsMultiCurve>();
1823 int nParts = GEOSGetNumGeometries_r( context, geos );
1824 multiCurve->reserve( nParts );
1825 for ( int i = 0; i < nParts; ++i )
1826 {
1827 std::unique_ptr< QgsAbstractGeometry > curve( fromGeos( GEOSGetGeometryN_r( context, geos, i ) ) );
1828 multiCurve->addGeometry( curve.release() );
1829 }
1830 return std::move( multiCurve );
1831 }
1832#endif
1833 case GEOS_MULTIPOLYGON:
1834 {
1835 auto multiPolygon = std::make_unique<QgsMultiPolygon>();
1836
1837 int nParts = GEOSGetNumGeometries_r( context, geos );
1838 multiPolygon->reserve( nParts );
1839 for ( int i = 0; i < nParts; ++i )
1840 {
1841 std::unique_ptr< QgsPolygon > poly = fromGeosPolygon( GEOSGetGeometryN_r( context, geos, i ) );
1842 if ( poly )
1843 {
1844 multiPolygon->addGeometry( poly.release() );
1845 }
1846 }
1847 return std::move( multiPolygon );
1848 }
1849#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
1850 case GEOS_MULTISURFACE:
1851 {
1852 auto multiSurface = std::make_unique<QgsMultiSurface>();
1853 int nParts = GEOSGetNumGeometries_r( context, geos );
1854 multiSurface->reserve( nParts );
1855 for ( int i = 0; i < nParts; ++i )
1856 {
1857 std::unique_ptr< QgsAbstractGeometry > polygon( fromGeos( GEOSGetGeometryN_r( context, geos, i ) ) );
1858 multiSurface->addGeometry( polygon.release() );
1859 }
1860 return std::move( multiSurface );
1861 }
1862#endif
1863 case GEOS_GEOMETRYCOLLECTION:
1864 {
1865 auto geomCollection = std::make_unique<QgsGeometryCollection>();
1866 int nParts = GEOSGetNumGeometries_r( context, geos );
1867 geomCollection->reserve( nParts );
1868 for ( int i = 0; i < nParts; ++i )
1869 {
1870 std::unique_ptr< QgsAbstractGeometry > geom( fromGeos( GEOSGetGeometryN_r( context, geos, i ) ) );
1871 if ( geom )
1872 {
1873 geomCollection->addGeometry( geom.release() );
1874 }
1875 }
1876 return std::move( geomCollection );
1877 }
1878 }
1879 return nullptr;
1880}
1881
1882#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
1883std::unique_ptr<QgsCurvePolygon> QgsGeos::fromGeosCurvePolygon( const GEOSGeometry *geos )
1884{
1885 GEOSContextHandle_t context = QgsGeosContext::get();
1886 if ( GEOSGeomTypeId_r( context, geos ) != GEOS_CURVEPOLYGON )
1887 {
1888 return nullptr;
1889 }
1890
1891 int nCoordDims = GEOSGeom_getCoordinateDimension_r( context, geos );
1892 int nDims = GEOSGeom_getDimensions_r( context, geos );
1893 bool hasZ = ( nCoordDims == 3 );
1894 bool hasM = ( ( nDims - nCoordDims ) == 1 );
1895
1896 auto curvePolygon = std::make_unique<QgsCurvePolygon>();
1897
1898 const GEOSGeometry *ring = GEOSGetExteriorRing_r( context, geos );
1899 if ( ring )
1900 {
1901 if ( GEOSGeomTypeId_r( context, ring ) == GEOS_COMPOUNDCURVE )
1902 {
1903 curvePolygon->setExteriorRing( qgis::down_cast< QgsCompoundCurve *>( fromGeos( ring ).release() ) );
1904 }
1905 else
1906 {
1907 curvePolygon->setExteriorRing( sequenceToSimpleCurve( ring, hasZ, hasM ).release() );
1908 }
1909 }
1910
1911 QVector<QgsCurve *> interiorRings;
1912 const int ringCount = GEOSGetNumInteriorRings_r( context, geos );
1913 interiorRings.reserve( ringCount );
1914 for ( int i = 0; i < ringCount; ++i )
1915 {
1916 ring = GEOSGetInteriorRingN_r( context, geos, i );
1917 if ( ring )
1918 {
1919 if ( GEOSGeomTypeId_r( context, ring ) == GEOS_COMPOUNDCURVE )
1920 {
1921 interiorRings.push_back( qgis::down_cast< QgsCompoundCurve *>( fromGeos( ring ).release() ) );
1922 }
1923 else
1924 {
1925 interiorRings.push_back( sequenceToSimpleCurve( ring, hasZ, hasM ).release() );
1926 }
1927 }
1928 }
1929 curvePolygon->setInteriorRings( interiorRings );
1930
1931 return curvePolygon;
1932}
1933#endif
1934
1935std::unique_ptr<QgsPolygon> QgsGeos::fromGeosPolygon( const GEOSGeometry *geos )
1936{
1937 GEOSContextHandle_t context = QgsGeosContext::get();
1938 if ( GEOSGeomTypeId_r( context, geos ) != GEOS_POLYGON )
1939 {
1940 return nullptr;
1941 }
1942
1943 int nCoordDims = GEOSGeom_getCoordinateDimension_r( context, geos );
1944 int nDims = GEOSGeom_getDimensions_r( context, geos );
1945 bool hasZ = ( nCoordDims == 3 );
1946 bool hasM = ( ( nDims - nCoordDims ) == 1 );
1947
1948 auto polygon = std::make_unique<QgsPolygon>();
1949
1950 const GEOSGeometry *ring = GEOSGetExteriorRing_r( context, geos );
1951 if ( ring )
1952 {
1953 polygon->setExteriorRing( sequenceToLinestring( ring, hasZ, hasM ).release() );
1954 }
1955
1956 QVector<QgsCurve *> interiorRings;
1957 const int ringCount = GEOSGetNumInteriorRings_r( context, geos );
1958 interiorRings.reserve( ringCount );
1959 for ( int i = 0; i < ringCount; ++i )
1960 {
1961 ring = GEOSGetInteriorRingN_r( context, geos, i );
1962 if ( ring )
1963 {
1964 interiorRings.push_back( sequenceToLinestring( ring, hasZ, hasM ).release() );
1965 }
1966 }
1967 polygon->setInteriorRings( interiorRings );
1968
1969 return polygon;
1970}
1971
1972std::unique_ptr<QgsSimpleCurve> QgsGeos::sequenceToSimpleCurve( const GEOSGeometry *geos, bool hasZ, bool hasM )
1973{
1974 GEOSContextHandle_t context = QgsGeosContext::get();
1975
1976 const int geometryType = GEOSGeomTypeId_r( context, geos );
1977 if ( !( geometryType == GEOS_LINESTRING || geometryType == GEOS_LINEARRING || geometryType == GEOS_CIRCULARSTRING ) )
1978 return nullptr;
1979
1980 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( context, geos );
1981
1982 unsigned int nPoints;
1983 GEOSCoordSeq_getSize_r( context, cs, &nPoints );
1984
1985 QVector< double > xOut( nPoints );
1986 QVector< double > yOut( nPoints );
1987 QVector< double > zOut;
1988 if ( hasZ )
1989 zOut.resize( nPoints );
1990 QVector< double > mOut;
1991 if ( hasM )
1992 mOut.resize( nPoints );
1993
1994 double *x = xOut.data();
1995 double *y = yOut.data();
1996 double *z = zOut.data();
1997 double *m = mOut.data();
1998
1999#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
2000 GEOSCoordSeq_copyToArrays_r( context, cs, x, y, hasZ ? z : nullptr, hasM ? m : nullptr );
2001#else
2002 for ( unsigned int i = 0; i < nPoints; ++i )
2003 {
2004 if ( hasZ )
2005 GEOSCoordSeq_getXYZ_r( context, cs, i, x++, y++, z++ );
2006 else
2007 GEOSCoordSeq_getXY_r( context, cs, i, x++, y++ );
2008 if ( hasM )
2009 {
2010 GEOSCoordSeq_getOrdinate_r( context, cs, i, 3, m++ );
2011 }
2012 }
2013#endif
2014
2015 std::unique_ptr< QgsSimpleCurve > simpleCurve;
2016 if ( geometryType == GEOS_LINESTRING || geometryType == GEOS_LINEARRING )
2017 {
2018 simpleCurve = std::make_unique<QgsLineString>( xOut, yOut, zOut, mOut );
2019 }
2020 else if ( geometryType == GEOS_CIRCULARSTRING )
2021 {
2022 simpleCurve = std::make_unique<QgsCircularString>( xOut, yOut, zOut, mOut );
2023 }
2024 return simpleCurve;
2025}
2026
2027std::unique_ptr<QgsLineString> QgsGeos::sequenceToLinestring( const GEOSGeometry *geos, bool hasZ, bool hasM )
2028{
2029 return std::unique_ptr<QgsLineString>( qgis::down_cast<QgsLineString *>( sequenceToSimpleCurve( geos, hasZ, hasM ).release() ) );
2030}
2031
2032#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
2033std::unique_ptr<QgsCircularString> QgsGeos::sequenceToCircularString( const GEOSGeometry *geos, bool hasZ, bool hasM )
2034{
2035 return std::unique_ptr<QgsCircularString>( qgis::down_cast<QgsCircularString *>( sequenceToSimpleCurve( geos, hasZ, hasM ).release() ) );
2036}
2037#endif
2038
2039int QgsGeos::numberOfGeometries( GEOSGeometry *g )
2040{
2041 if ( !g )
2042 return 0;
2043
2044 GEOSContextHandle_t context = QgsGeosContext::get();
2045 int geometryType = GEOSGeomTypeId_r( context, g );
2046 if ( geometryType == GEOS_POINT || geometryType == GEOS_LINESTRING || geometryType == GEOS_LINEARRING || geometryType == GEOS_POLYGON )
2047 return 1;
2048
2049 //calling GEOSGetNumGeometries is save for multi types and collections also in geos2
2050 return GEOSGetNumGeometries_r( context, g );
2051}
2052
2053QgsPoint QgsGeos::coordSeqPoint( const GEOSCoordSequence *cs, int i, bool hasZ, bool hasM )
2054{
2055 if ( !cs )
2056 {
2057 return QgsPoint();
2058 }
2059
2060 GEOSContextHandle_t context = QgsGeosContext::get();
2061
2062 double x, y;
2063 double z = 0;
2064 double m = 0;
2065 if ( hasZ )
2066 GEOSCoordSeq_getXYZ_r( context, cs, i, &x, &y, &z );
2067 else
2068 GEOSCoordSeq_getXY_r( context, cs, i, &x, &y );
2069 if ( hasM )
2070 {
2071 GEOSCoordSeq_getOrdinate_r( context, cs, i, 3, &m );
2072 }
2073
2075 if ( hasZ && hasM )
2076 {
2078 }
2079 else if ( hasZ )
2080 {
2082 }
2083 else if ( hasM )
2084 {
2086 }
2087 return QgsPoint( t, x, y, z, m );
2088}
2089
2091{
2092 if ( !geom )
2093 return nullptr;
2094
2095 int coordDims = 2;
2096 if ( geom->is3D() )
2097 {
2098 ++coordDims;
2099 }
2100 if ( geom->isMeasure() )
2101 {
2102 ++coordDims;
2103 }
2104
2106 {
2107 int geosType;
2108 switch ( QgsWkbTypes::flatType( geom->wkbType() ) )
2109 {
2111 geosType = GEOS_MULTIPOINT;
2112 break;
2113
2115#if !( GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 ) )
2117#endif
2118 geosType = GEOS_MULTILINESTRING;
2119 break;
2120
2122#if !( GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 ) )
2124#endif
2125 geosType = GEOS_MULTIPOLYGON;
2126 break;
2127
2128#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
2130 geosType = GEOS_MULTICURVE;
2131 break;
2132
2134 geosType = GEOS_MULTISURFACE;
2135 break;
2136#endif
2137
2139 geosType = GEOS_GEOMETRYCOLLECTION;
2140 break;
2141
2144 default:
2145 return nullptr;
2146 }
2147
2149
2150 if ( !c )
2151 return nullptr;
2152
2153 std::vector<geos::unique_ptr> geomVector;
2154 geomVector.reserve( c->numGeometries() );
2155 for ( int i = 0; i < c->numGeometries(); ++i )
2156 {
2157 geos::unique_ptr geosGeom = asGeos( c->geometryN( i ), precision, flags );
2158 if ( flags & Qgis::GeosCreationFlag::RejectOnInvalidSubGeometry && !geosGeom )
2159 {
2160 return nullptr;
2161 }
2162 geomVector.emplace_back( std::move( geosGeom ) );
2163 }
2164 return createGeosCollection( geosType, geomVector );
2165 }
2166 else
2167 {
2168 switch ( QgsWkbTypes::flatType( geom->wkbType() ) )
2169 {
2171 return createGeosPoint( geom, coordDims, precision, flags );
2172
2176#if !( GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 ) )
2178 return createGeosLinestring( geom, precision, flags );
2179#else // GEOS >= 3.15
2180 return createGeosSimpleCurve( geom, precision, flags );
2181
2183 return createGeosCompoundCurve( geom, precision, flags );
2184#endif
2185
2188#if !( GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 ) )
2190#endif
2191 return createGeosPolygon( geom, precision, flags );
2192
2193#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
2195 return createGeosCurvePolygon( geom, precision, flags );
2196#endif
2197
2198 case Qgis::WkbType::TIN:
2200 {
2201 // PolyhedralSurface and TIN support
2202 // convert it to a geos MultiPolygon
2204 if ( !polyhedralSurface )
2205 return nullptr;
2206
2207 std::vector<geos::unique_ptr> geomVector;
2208 geomVector.reserve( polyhedralSurface->numPatches() );
2209 for ( int i = 0; i < polyhedralSurface->numPatches(); ++i )
2210 {
2211 geos::unique_ptr geosPolygon = createGeosPolygon( polyhedralSurface->patchN( i ), precision );
2212 if ( flags & Qgis::GeosCreationFlag::RejectOnInvalidSubGeometry && !geosPolygon )
2213 {
2214 return nullptr;
2215 }
2216 geomVector.emplace_back( std::move( geosPolygon ) );
2217 }
2218
2219 return createGeosCollection( GEOS_MULTIPOLYGON, geomVector );
2220 }
2221
2224 default:
2225 return nullptr;
2226 }
2227 }
2228 return nullptr;
2229}
2230
2231std::unique_ptr<QgsAbstractGeometry> QgsGeos::overlay( const QgsAbstractGeometry *geom, Overlay op, QString *errorMsg, const QgsGeometryParameters &parameters, QgsFeedback *feedback ) const
2232{
2233 if ( !mGeos || !geom )
2234 {
2235 return nullptr;
2236 }
2237
2238 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2239 if ( !geosGeom )
2240 {
2241 return nullptr;
2242 }
2243
2244 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2245
2246 const double gridSize = parameters.gridSize();
2247
2248 GEOSContextHandle_t context = QgsGeosContext::get();
2249 try
2250 {
2251 geos::unique_ptr opGeom;
2252 switch ( op )
2253 {
2254 case OverlayIntersection:
2255 if ( gridSize > 0 )
2256 {
2257 opGeom.reset( GEOSIntersectionPrec_r( context, mGeos.get(), geosGeom.get(), gridSize ) );
2258 }
2259 else
2260 {
2261 opGeom.reset( GEOSIntersection_r( context, mGeos.get(), geosGeom.get() ) );
2262 }
2263 break;
2264
2265 case OverlayDifference:
2266 if ( gridSize > 0 )
2267 {
2268 opGeom.reset( GEOSDifferencePrec_r( context, mGeos.get(), geosGeom.get(), gridSize ) );
2269 }
2270 else
2271 {
2272 opGeom.reset( GEOSDifference_r( context, mGeos.get(), geosGeom.get() ) );
2273 }
2274 break;
2275
2276 case OverlayUnion:
2277 {
2278 geos::unique_ptr unionGeometry;
2279 if ( gridSize > 0 )
2280 {
2281 unionGeometry.reset( GEOSUnionPrec_r( context, mGeos.get(), geosGeom.get(), gridSize ) );
2282 }
2283 else
2284 {
2285 unionGeometry.reset( GEOSUnion_r( context, mGeos.get(), geosGeom.get() ) );
2286 }
2287
2288#if !( GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 ) )
2289 if ( unionGeometry && GEOSGeomTypeId_r( context, unionGeometry.get() ) == GEOS_MULTILINESTRING )
2290 {
2291 geos::unique_ptr mergedLines( GEOSLineMerge_r( context, unionGeometry.get() ) );
2292 if ( mergedLines )
2293 {
2294 unionGeometry = std::move( mergedLines );
2295 }
2296 }
2297#endif
2298
2299 opGeom = std::move( unionGeometry );
2300 }
2301 break;
2302
2303 case OverlaySymDifference:
2304 if ( gridSize > 0 )
2305 {
2306 opGeom.reset( GEOSSymDifferencePrec_r( context, mGeos.get(), geosGeom.get(), gridSize ) );
2307 }
2308 else
2309 {
2310 opGeom.reset( GEOSSymDifference_r( context, mGeos.get(), geosGeom.get() ) );
2311 }
2312 break;
2313 }
2314 return fromGeos( opGeom.get() );
2315 }
2316 catch ( QgsGeosException &e )
2317 {
2318 logError( u"GEOS"_s, e.what() );
2319 if ( errorMsg )
2320 {
2321 *errorMsg = e.what();
2322 }
2323 return nullptr;
2324 }
2325}
2326
2327bool QgsGeos::relation( const QgsAbstractGeometry *geom, Relation r, QString *errorMsg, QgsFeedback *feedback ) const
2328{
2329 if ( !mGeos || !geom )
2330 {
2331 return false;
2332 }
2333
2334 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2335 if ( !geosGeom )
2336 {
2337 return false;
2338 }
2339
2340 GEOSContextHandle_t context = QgsGeosContext::get();
2341 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2342
2343 bool result = false;
2344 try
2345 {
2346 if ( mGeosPrepared ) //use faster version with prepared geometry
2347 {
2348 switch ( r )
2349 {
2350 case RelationIntersects:
2351 result = ( GEOSPreparedIntersects_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2352 break;
2353 case RelationTouches:
2354 result = ( GEOSPreparedTouches_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2355 break;
2356 case RelationCrosses:
2357 result = ( GEOSPreparedCrosses_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2358 break;
2359 case RelationWithin:
2360 result = ( GEOSPreparedWithin_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2361 break;
2362 case RelationContains:
2363 result = ( GEOSPreparedContains_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2364 break;
2365 case RelationDisjoint:
2366 result = ( GEOSPreparedDisjoint_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2367 break;
2368 case RelationOverlaps:
2369 result = ( GEOSPreparedOverlaps_r( context, mGeosPrepared.get(), geosGeom.get() ) == 1 );
2370 break;
2371 }
2372 return result;
2373 }
2374
2375 switch ( r )
2376 {
2377 case RelationIntersects:
2378 result = ( GEOSIntersects_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2379 break;
2380 case RelationTouches:
2381 result = ( GEOSTouches_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2382 break;
2383 case RelationCrosses:
2384 result = ( GEOSCrosses_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2385 break;
2386 case RelationWithin:
2387 result = ( GEOSWithin_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2388 break;
2389 case RelationContains:
2390 result = ( GEOSContains_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2391 break;
2392 case RelationDisjoint:
2393 result = ( GEOSDisjoint_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2394 break;
2395 case RelationOverlaps:
2396 result = ( GEOSOverlaps_r( context, mGeos.get(), geosGeom.get() ) == 1 );
2397 break;
2398 }
2399 }
2400 catch ( QgsGeosException &e )
2401 {
2402 logError( u"GEOS"_s, e.what() );
2403 if ( errorMsg )
2404 {
2405 *errorMsg = e.what();
2406 }
2407 return false;
2408 }
2409
2410 return result;
2411}
2412
2413QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, QString *errorMsg, QgsFeedback *feedback ) const
2414{
2415 if ( !mGeos )
2416 {
2417 return nullptr;
2418 }
2419
2421 try
2422 {
2423 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2424
2425 geos.reset( GEOSBuffer_r( QgsGeosContext::get(), mGeos.get(), distance, segments ) );
2426 }
2427 CATCH_GEOS_WITH_ERRMSG( nullptr )
2428 return fromGeos( geos.get() ).release();
2429}
2430
2431QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, Qgis::EndCapStyle endCapStyle, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg, QgsFeedback *feedback ) const
2432{
2433 geos::unique_ptr geos = buffer( mGeos.get(), distance, segments, endCapStyle, joinStyle, miterLimit, errorMsg, feedback );
2434 return fromGeos( geos.get() ).release();
2435}
2436
2438 const GEOSGeometry *geometry, double distance, int segments, Qgis::EndCapStyle endCapStyle, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg, QgsFeedback *feedback
2439)
2440{
2441 if ( !geometry )
2442 {
2443 return nullptr;
2444 }
2445
2447 try
2448 {
2449 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2450 geos.reset( GEOSBufferWithStyle_r( QgsGeosContext::get(), geometry, distance, segments, static_cast< int >( endCapStyle ), static_cast< int >( joinStyle ), miterLimit ) );
2451 }
2452 CATCH_GEOS_WITH_ERRMSG( nullptr )
2453 return geos;
2454}
2455
2456QgsAbstractGeometry *QgsGeos::simplify( double tolerance, QString *errorMsg, QgsFeedback *feedback ) const
2457{
2458 if ( !mGeos )
2459 {
2460 return nullptr;
2461 }
2463 try
2464 {
2465 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2466 geos.reset( GEOSTopologyPreserveSimplify_r( QgsGeosContext::get(), mGeos.get(), tolerance ) );
2467 }
2468 CATCH_GEOS_WITH_ERRMSG( nullptr )
2469 return fromGeos( geos.get() ).release();
2470}
2471
2472QgsAbstractGeometry *QgsGeos::interpolate( double distance, QString *errorMsg, QgsFeedback *feedback ) const
2473{
2474 if ( !mGeos )
2475 {
2476 return nullptr;
2477 }
2479 try
2480 {
2481 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2482 geos.reset( GEOSInterpolate_r( QgsGeosContext::get(), mGeos.get(), distance ) );
2483 }
2484 CATCH_GEOS_WITH_ERRMSG( nullptr )
2485 return fromGeos( geos.get() ).release();
2486}
2487
2488QgsPoint *QgsGeos::centroid( QString *errorMsg, QgsFeedback *feedback ) const
2489{
2490 if ( !mGeos )
2491 {
2492 return nullptr;
2493 }
2494
2496 double x;
2497 double y;
2498
2499 GEOSContextHandle_t context = QgsGeosContext::get();
2500 try
2501 {
2502 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2503 geos.reset( GEOSGetCentroid_r( context, mGeos.get() ) );
2504
2505 if ( !geos )
2506 return nullptr;
2507
2508 GEOSGeomGetX_r( context, geos.get(), &x );
2509 GEOSGeomGetY_r( context, geos.get(), &y );
2510 }
2511 CATCH_GEOS_WITH_ERRMSG( nullptr )
2512
2513 return new QgsPoint( x, y );
2514}
2515
2516QgsAbstractGeometry *QgsGeos::envelope( QString *errorMsg ) const
2517{
2518 if ( !mGeos )
2519 {
2520 return nullptr;
2521 }
2523 try
2524 {
2525 geos.reset( GEOSEnvelope_r( QgsGeosContext::get(), mGeos.get() ) );
2526 }
2527 CATCH_GEOS_WITH_ERRMSG( nullptr )
2528 return fromGeos( geos.get() ).release();
2529}
2530
2531QgsPoint *QgsGeos::pointOnSurface( QString *errorMsg, QgsFeedback *feedback ) const
2532{
2533 if ( !mGeos )
2534 {
2535 return nullptr;
2536 }
2537
2538 double x;
2539 double y;
2540
2541 GEOSContextHandle_t context = QgsGeosContext::get();
2543 try
2544 {
2545 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2546 geos.reset( GEOSPointOnSurface_r( context, mGeos.get() ) );
2547
2548 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
2549 {
2550 return nullptr;
2551 }
2552
2553 GEOSGeomGetX_r( context, geos.get(), &x );
2554 GEOSGeomGetY_r( context, geos.get(), &y );
2555 }
2556 CATCH_GEOS_WITH_ERRMSG( nullptr )
2557
2558 return new QgsPoint( x, y );
2559}
2560
2561QgsAbstractGeometry *QgsGeos::convexHull( QString *errorMsg, QgsFeedback *feedback ) const
2562{
2563 if ( !mGeos )
2564 {
2565 return nullptr;
2566 }
2567
2568 try
2569 {
2570 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2571 geos::unique_ptr cHull( GEOSConvexHull_r( QgsGeosContext::get(), mGeos.get() ) );
2572 std::unique_ptr< QgsAbstractGeometry > cHullGeom = fromGeos( cHull.get() );
2573 return cHullGeom.release();
2574 }
2575 CATCH_GEOS_WITH_ERRMSG( nullptr )
2576}
2577
2578std::unique_ptr< QgsAbstractGeometry > QgsGeos::concaveHull( double targetPercent, bool allowHoles, QString *errorMsg, QgsFeedback *feedback ) const
2579{
2580#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 11
2581 ( void ) allowHoles;
2582 ( void ) targetPercent;
2583 ( void ) errorMsg;
2584 throw QgsNotSupportedException( QObject::tr( "Calculating concaveHull requires a QGIS build based on GEOS 3.11 or later" ) );
2585#else
2586 if ( !mGeos )
2587 {
2588 return nullptr;
2589 }
2590
2591 try
2592 {
2593 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2594 geos::unique_ptr concaveHull( GEOSConcaveHull_r( QgsGeosContext::get(), mGeos.get(), targetPercent, allowHoles ) );
2595 std::unique_ptr< QgsAbstractGeometry > concaveHullGeom = fromGeos( concaveHull.get() );
2596 return concaveHullGeom;
2597 }
2598 CATCH_GEOS_WITH_ERRMSG( nullptr )
2599#endif
2600}
2601
2602std::unique_ptr<QgsAbstractGeometry> QgsGeos::concaveHullOfPolygons( double lengthRatio, bool allowHoles, bool isTight, QString *errorMsg, QgsFeedback *feedback ) const
2603{
2604#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 11
2605 ( void ) allowHoles;
2606 ( void ) targetPercent;
2607 ( void ) errorMsg;
2608 throw QgsNotSupportedException( QObject::tr( "Calculating concaveHullOfPolygons requires a QGIS build based on GEOS 3.11 or later" ) );
2609#else
2610 if ( !mGeos )
2611 {
2612 return nullptr;
2613 }
2614
2615 try
2616 {
2617 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2618 geos::unique_ptr concaveHull( GEOSConcaveHullOfPolygons_r( QgsGeosContext::get(), mGeos.get(), lengthRatio, isTight ? 1 : 0, allowHoles ? 1 : 0 ) );
2619 std::unique_ptr< QgsAbstractGeometry > concaveHullGeom = fromGeos( concaveHull.get() );
2620 return concaveHullGeom;
2621 }
2622 CATCH_GEOS_WITH_ERRMSG( nullptr )
2623#endif
2624}
2625
2626Qgis::CoverageValidityResult QgsGeos::validateCoverage( double gapWidth, std::unique_ptr<QgsAbstractGeometry> *invalidEdges, QString *errorMsg, QgsFeedback *feedback ) const
2627{
2628#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 12
2629 ( void ) gapWidth;
2630 ( void ) invalidEdges;
2631 ( void ) errorMsg;
2632 throw QgsNotSupportedException( QObject::tr( "Validating coverages requires a QGIS build based on GEOS 3.12 or later" ) );
2633#else
2634 if ( !mGeos )
2635 {
2636 if ( errorMsg )
2637 *errorMsg = u"Input geometry was not set"_s;
2639 }
2640
2641 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2642 GEOSContextHandle_t context = QgsGeosContext::get();
2643 try
2644 {
2645 GEOSGeometry *invalidEdgesGeos = nullptr;
2646 const int result = GEOSCoverageIsValid_r( context, mGeos.get(), gapWidth, invalidEdges ? &invalidEdgesGeos : nullptr );
2647 if ( invalidEdges && invalidEdgesGeos )
2648 {
2649 *invalidEdges = fromGeos( invalidEdgesGeos );
2650 }
2651 if ( invalidEdgesGeos )
2652 {
2653 GEOSGeom_destroy_r( context, invalidEdgesGeos );
2654 invalidEdgesGeos = nullptr;
2655 }
2656
2657 switch ( result )
2658 {
2659 case 0:
2661 case 1:
2663 case 2:
2664 break;
2665 }
2667 }
2669#endif
2670}
2671
2672std::unique_ptr<QgsAbstractGeometry> QgsGeos::simplifyCoverageVW( double tolerance, bool preserveBoundary, QString *errorMsg, QgsFeedback *feedback ) const
2673{
2674#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 12
2675 ( void ) tolerance;
2676 ( void ) preserveBoundary;
2677 ( void ) errorMsg;
2678 throw QgsNotSupportedException( QObject::tr( "Simplifying coverages requires a QGIS build based on GEOS 3.12 or later" ) );
2679#else
2680 if ( !mGeos )
2681 {
2682 if ( errorMsg )
2683 *errorMsg = u"Input geometry was not set"_s;
2684 return nullptr;
2685 }
2686
2687 try
2688 {
2689 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2690 geos::unique_ptr simplified( GEOSCoverageSimplifyVW_r( QgsGeosContext::get(), mGeos.get(), tolerance, preserveBoundary ? 1 : 0 ) );
2691 std::unique_ptr< QgsAbstractGeometry > simplifiedGeom = fromGeos( simplified.get() );
2692 return simplifiedGeom;
2693 }
2694 CATCH_GEOS_WITH_ERRMSG( nullptr )
2695#endif
2696}
2697
2698std::unique_ptr<QgsAbstractGeometry> QgsGeos::unionCoverage( QString *errorMsg, QgsFeedback *feedback ) const
2699{
2700 if ( !mGeos )
2701 {
2702 if ( errorMsg )
2703 *errorMsg = u"Input geometry was not set"_s;
2704 return nullptr;
2705 }
2706
2707 try
2708 {
2709 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2710 geos::unique_ptr unioned( GEOSCoverageUnion_r( QgsGeosContext::get(), mGeos.get() ) );
2711 std::unique_ptr< QgsAbstractGeometry > result = fromGeos( unioned.get() );
2712 return result;
2713 }
2714 CATCH_GEOS_WITH_ERRMSG( nullptr )
2715}
2716
2717std::unique_ptr< QgsAbstractGeometry > QgsGeos::cleanCoverage( const QgsCoverageCleanParameters &parameters, QString *errorMsg, QgsFeedback *feedback ) const
2718{
2719#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 14
2720 ( void ) parameters;
2721 ( void ) errorMsg;
2722 ( void ) feedback;
2723 throw QgsNotSupportedException( QObject::tr( "Cleaning coverages requires a QGIS build based on GEOS 3.14 or later" ) );
2724#else
2725 if ( !mGeos )
2726 {
2727 if ( errorMsg )
2728 *errorMsg = u"Input geometry was not set"_s;
2729 return nullptr;
2730 }
2731
2732 GEOSCoverageCleanParams *params = nullptr;
2733 try
2734 {
2735 params = GEOSCoverageCleanParams_create_r( QgsGeosContext::get() );
2736 if ( parameters.snappingDistance() >= 0 )
2737 {
2738 GEOSCoverageCleanParams_setSnappingDistance_r( QgsGeosContext::get(), params, parameters.snappingDistance() );
2739 }
2740 GEOSCoverageCleanParams_setGapMaximumWidth_r( QgsGeosContext::get(), params, parameters.maximumGapWidth() );
2741 switch ( parameters.overlapMergeStrategy() )
2742 {
2744 GEOSCoverageCleanParams_setOverlapMergeStrategy_r( QgsGeosContext::get(), params, 0 );
2745 break;
2747 GEOSCoverageCleanParams_setOverlapMergeStrategy_r( QgsGeosContext::get(), params, 1 );
2748 break;
2750 GEOSCoverageCleanParams_setOverlapMergeStrategy_r( QgsGeosContext::get(), params, 2 );
2751 break;
2753 GEOSCoverageCleanParams_setOverlapMergeStrategy_r( QgsGeosContext::get(), params, 3 );
2754 break;
2755 }
2756
2757 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2758 geos::unique_ptr cleaned( GEOSCoverageCleanWithParams_r( QgsGeosContext::get(), mGeos.get(), params ) );
2759 GEOSCoverageCleanParams_destroy_r( QgsGeosContext::get(), params );
2760
2761 std::unique_ptr< QgsAbstractGeometry> cleanedGeom = fromGeos( cleaned.get() );
2762
2763 return cleanedGeom;
2764 }
2765 catch ( QgsGeosException &e )
2766 {
2767 if ( params )
2768 {
2769 GEOSCoverageCleanParams_destroy_r( QgsGeosContext::get(), params );
2770 params = nullptr;
2771 }
2772
2773 if ( errorMsg )
2774 {
2775 *errorMsg = e.what();
2776 if ( errorMsg->startsWith( "InterruptedException"_L1, Qt::CaseInsensitive ) )
2777 {
2778 errorMsg->clear();
2779 }
2780 }
2781 return nullptr;
2782 }
2783#endif
2784}
2785
2786bool QgsGeos::isValid( QString *errorMsg, const bool allowSelfTouchingHoles, QgsGeometry *errorLoc, QgsFeedback *feedback ) const
2787{
2788 if ( !mGeos )
2789 {
2790 if ( errorMsg )
2791 *errorMsg = QObject::tr( "QGIS geometry cannot be converted to a GEOS geometry", "GEOS Error" );
2792 return false;
2793 }
2794
2795 GEOSContextHandle_t context = QgsGeosContext::get();
2796 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2797 try
2798 {
2799 GEOSGeometry *g1 = nullptr;
2800 char *r = nullptr;
2801 char res = GEOSisValidDetail_r( context, mGeos.get(), allowSelfTouchingHoles ? GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE : 0, &r, &g1 );
2802 const bool invalid = res != 1;
2803
2804 QString error;
2805 if ( r )
2806 {
2807 error = QString( r );
2808 GEOSFree_r( context, r );
2809 }
2810
2811 if ( invalid && errorMsg )
2812 {
2813 // Copied from https://github.com/libgeos/geos/blob/main/src/operation/valid/TopologyValidationError.cpp
2814 static const std::map< QString, QString > sTranslatedErrors {
2815 { u"topology validation error"_s, QObject::tr( "Topology validation error", "GEOS Error" ) },
2816 { u"repeated point"_s, QObject::tr( "Repeated point", "GEOS Error" ) },
2817 { u"hole lies outside shell"_s, QObject::tr( "Hole lies outside shell", "GEOS Error" ) },
2818 { u"holes are nested"_s, QObject::tr( "Holes are nested", "GEOS Error" ) },
2819 { u"interior is disconnected"_s, QObject::tr( "Interior is disconnected", "GEOS Error" ) },
2820 { u"self-intersection"_s, QObject::tr( "Self-intersection", "GEOS Error" ) },
2821 { u"ring self-intersection"_s, QObject::tr( "Ring self-intersection", "GEOS Error" ) },
2822 { u"nested shells"_s, QObject::tr( "Nested shells", "GEOS Error" ) },
2823 { u"duplicate rings"_s, QObject::tr( "Duplicate rings", "GEOS Error" ) },
2824 { u"too few points in geometry component"_s, QObject::tr( "Too few points in geometry component", "GEOS Error" ) },
2825 { u"invalid coordinate"_s, QObject::tr( "Invalid coordinate", "GEOS Error" ) },
2826 { u"ring is not closed"_s, QObject::tr( "Ring is not closed", "GEOS Error" ) },
2827 };
2828
2829 const auto translatedError = sTranslatedErrors.find( error.toLower() );
2830 if ( translatedError != sTranslatedErrors.end() )
2831 *errorMsg = translatedError->second;
2832 else
2833 *errorMsg = error;
2834
2835 if ( g1 && errorLoc )
2836 {
2837 *errorLoc = geometryFromGeos( g1 );
2838 }
2839 else if ( g1 )
2840 {
2841 GEOSGeom_destroy_r( context, g1 );
2842 }
2843 }
2844 return !invalid;
2845 }
2846 CATCH_GEOS_WITH_ERRMSG( false )
2847}
2848
2849bool QgsGeos::isEqual( const QgsAbstractGeometry *geom, QString *errorMsg, QgsFeedback *feedback ) const
2850{
2851 if ( !mGeos || !geom )
2852 {
2853 return false;
2854 }
2855
2856 try
2857 {
2858 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2859 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2860 if ( !geosGeom )
2861 {
2862 return false;
2863 }
2864 bool equal = GEOSEquals_r( QgsGeosContext::get(), mGeos.get(), geosGeom.get() );
2865 return equal;
2866 }
2867 CATCH_GEOS_WITH_ERRMSG( false )
2868}
2869
2870bool QgsGeos::isFuzzyEqual( const QgsAbstractGeometry *geom, double epsilon, QString *errorMsg, QgsFeedback *feedback ) const
2871{
2872 if ( !mGeos || !geom )
2873 {
2874 return false;
2875 }
2876
2877 try
2878 {
2879 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
2880
2881 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2882 if ( !geosGeom )
2883 {
2884 return false;
2885 }
2886 bool equal = GEOSEqualsExact_r( QgsGeosContext::get(), mGeos.get(), geosGeom.get(), epsilon );
2887 return equal;
2888 }
2889 CATCH_GEOS_WITH_ERRMSG( false )
2890}
2891
2892bool QgsGeos::isEmpty( QString *errorMsg ) const
2893{
2894 if ( !mGeos )
2895 {
2896 return false;
2897 }
2898
2899 try
2900 {
2901 return GEOSisEmpty_r( QgsGeosContext::get(), mGeos.get() );
2902 }
2903 CATCH_GEOS_WITH_ERRMSG( false )
2904}
2905
2906bool QgsGeos::isSimple( QString *errorMsg ) const
2907{
2908 if ( !mGeos )
2909 {
2910 return false;
2911 }
2912
2913 try
2914 {
2915 return GEOSisSimple_r( QgsGeosContext::get(), mGeos.get() );
2916 }
2917 CATCH_GEOS_WITH_ERRMSG( false )
2918}
2919
2920GEOSCoordSequence *QgsGeos::createCoordinateSequence( const QgsCurve *curve, double precision, bool forceClose )
2921{
2922 GEOSContextHandle_t context = QgsGeosContext::get();
2923 const QgsSimpleCurve *simpleCurve;
2924
2925#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
2927 {
2928 simpleCurve = qgsgeometry_cast< const QgsCircularString * >( curve );
2929 }
2930 else
2931 {
2932#endif
2933 simpleCurve = qgsgeometry_cast< const QgsLineString *>( curve );
2934
2935 std::unique_ptr< QgsLineString > segmentized;
2936 if ( !simpleCurve )
2937 {
2938 segmentized.reset( curve->curveToLine() );
2939 simpleCurve = segmentized.get();
2940 }
2941#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
2942 }
2943#endif
2944
2945 if ( !simpleCurve )
2946 {
2947 return nullptr;
2948 }
2949 GEOSCoordSequence *coordSeq = nullptr;
2950
2951 const int numPoints = simpleCurve->numPoints();
2952
2953 const bool hasZ = simpleCurve->is3D();
2954
2955#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 10 )
2956 if ( qgsDoubleNear( precision, 0 ) )
2957 {
2958 if ( !forceClose || ( simpleCurve->pointN( 0 ) == simpleCurve->pointN( numPoints - 1 ) ) )
2959 {
2960 // use optimised method if we don't have to force close an open ring
2961 try
2962 {
2963 coordSeq = GEOSCoordSeq_copyFromArrays_r( context, simpleCurve->xData(), simpleCurve->yData(), simpleCurve->zData(), nullptr, numPoints );
2964 if ( !coordSeq )
2965 {
2966 QgsDebugError( u"GEOS Exception: Could not create coordinate sequence for %1 points"_s.arg( numPoints ) );
2967 return nullptr;
2968 }
2969 }
2970 CATCH_GEOS( nullptr )
2971 }
2972 else
2973 {
2974 QVector< double > x = simpleCurve->xVector();
2975 if ( numPoints > 0 )
2976 x.append( x.at( 0 ) );
2977 QVector< double > y = simpleCurve->yVector();
2978 if ( numPoints > 0 )
2979 y.append( y.at( 0 ) );
2980 QVector< double > z = simpleCurve->zVector();
2981 if ( hasZ && numPoints > 0 )
2982 z.append( z.at( 0 ) );
2983 try
2984 {
2985 coordSeq = GEOSCoordSeq_copyFromArrays_r( context, x.constData(), y.constData(), !hasZ ? nullptr : z.constData(), nullptr, numPoints + 1 );
2986 if ( !coordSeq )
2987 {
2988 QgsDebugError( u"GEOS Exception: Could not create closed coordinate sequence for %1 points"_s.arg( numPoints + 1 ) );
2989 return nullptr;
2990 }
2991 }
2992 CATCH_GEOS( nullptr )
2993 }
2994 return coordSeq;
2995 }
2996#endif
2997
2998 int coordDims = 2;
2999 const bool hasM = false; //line->isMeasure(); //disabled until geos supports m-coordinates
3000
3001 if ( hasZ )
3002 {
3003 ++coordDims;
3004 }
3005 if ( hasM )
3006 {
3007 ++coordDims;
3008 }
3009
3010 int numOutPoints = numPoints;
3011 if ( forceClose && ( simpleCurve->pointN( 0 ) != simpleCurve->pointN( numPoints - 1 ) ) )
3012 {
3013 ++numOutPoints;
3014 }
3015
3016 try
3017 {
3018 coordSeq = GEOSCoordSeq_create_r( context, numOutPoints, coordDims );
3019 if ( !coordSeq )
3020 {
3021 QgsDebugError( u"GEOS Exception: Could not create coordinate sequence for %1 points in %2 dimensions"_s.arg( numPoints ).arg( coordDims ) );
3022 return nullptr;
3023 }
3024
3025 const double *xData = simpleCurve->xData();
3026 const double *yData = simpleCurve->yData();
3027 const double *zData = hasZ ? simpleCurve->zData() : nullptr;
3028 const double *mData = hasM ? simpleCurve->mData() : nullptr;
3029
3030 if ( precision > 0. )
3031 {
3032 for ( int i = 0; i < numOutPoints; ++i )
3033 {
3034 if ( i >= numPoints )
3035 {
3036 // start reading back from start of line
3037 xData = simpleCurve->xData();
3038 yData = simpleCurve->yData();
3039 zData = hasZ ? simpleCurve->zData() : nullptr;
3040 mData = hasM ? simpleCurve->mData() : nullptr;
3041 }
3042 if ( hasZ )
3043 {
3044 GEOSCoordSeq_setXYZ_r( context, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision, std::round( *zData++ / precision ) * precision );
3045 }
3046 else
3047 {
3048 GEOSCoordSeq_setXY_r( context, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision );
3049 }
3050 if ( hasM )
3051 {
3052 GEOSCoordSeq_setOrdinate_r( context, coordSeq, i, 3, *mData++ );
3053 }
3054 }
3055 }
3056 else
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, *xData++, *yData++, *zData++ );
3071 }
3072 else
3073 {
3074 GEOSCoordSeq_setXY_r( context, coordSeq, i, *xData++, *yData++ );
3075 }
3076 if ( hasM )
3077 {
3078 GEOSCoordSeq_setOrdinate_r( context, coordSeq, i, 3, *mData++ );
3079 }
3080 }
3081 }
3082 }
3083 CATCH_GEOS( nullptr )
3084
3085 return coordSeq;
3086}
3087
3088geos::unique_ptr QgsGeos::createGeosPoint( const QgsAbstractGeometry *point, int coordDims, double precision, Qgis::GeosCreationFlags )
3089{
3090 const QgsPoint *pt = qgsgeometry_cast<const QgsPoint *>( point );
3091 if ( !pt )
3092 return nullptr;
3093
3094 return createGeosPointXY( pt->x(), pt->y(), pt->is3D(), pt->z(), pt->isMeasure(), pt->m(), coordDims, precision );
3095}
3096
3097geos::unique_ptr QgsGeos::createGeosPointXY( double x, double y, bool hasZ, double z, bool hasM, double m, int coordDims, double precision, Qgis::GeosCreationFlags )
3098{
3099 Q_UNUSED( hasM )
3100 Q_UNUSED( m )
3101
3102 geos::unique_ptr geosPoint;
3103 GEOSContextHandle_t context = QgsGeosContext::get();
3104 try
3105 {
3106 if ( coordDims == 2 )
3107 {
3108 // optimised constructor
3109 if ( precision > 0. )
3110 geosPoint.reset( GEOSGeom_createPointFromXY_r( context, std::round( x / precision ) * precision, std::round( y / precision ) * precision ) );
3111 else
3112 geosPoint.reset( GEOSGeom_createPointFromXY_r( context, x, y ) );
3113 return geosPoint;
3114 }
3115
3116 GEOSCoordSequence *coordSeq = GEOSCoordSeq_create_r( context, 1, coordDims );
3117 if ( !coordSeq )
3118 {
3119 QgsDebugError( u"GEOS Exception: Could not create coordinate sequence for point with %1 dimensions"_s.arg( coordDims ) );
3120 return nullptr;
3121 }
3122 if ( precision > 0. )
3123 {
3124 GEOSCoordSeq_setX_r( context, coordSeq, 0, std::round( x / precision ) * precision );
3125 GEOSCoordSeq_setY_r( context, coordSeq, 0, std::round( y / precision ) * precision );
3126 if ( hasZ )
3127 {
3128 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 2, std::round( z / precision ) * precision );
3129 }
3130 }
3131 else
3132 {
3133 GEOSCoordSeq_setX_r( context, coordSeq, 0, x );
3134 GEOSCoordSeq_setY_r( context, coordSeq, 0, y );
3135 if ( hasZ )
3136 {
3137 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 2, z );
3138 }
3139 }
3140#if 0 //disabled until geos supports m-coordinates
3141 if ( hasM )
3142 {
3143 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 3, m );
3144 }
3145#endif
3146 geosPoint.reset( GEOSGeom_createPoint_r( context, coordSeq ) );
3147 }
3148 CATCH_GEOS( nullptr )
3149 return geosPoint;
3150}
3151
3152geos::unique_ptr QgsGeos::createGeosLinestring( const QgsAbstractGeometry *curve, double precision, Qgis::GeosCreationFlags )
3153{
3154 const QgsCurve *c = qgsgeometry_cast<const QgsCurve *>( curve );
3155 if ( !c )
3156 return nullptr;
3157
3158 GEOSCoordSequence *coordSeq = createCoordinateSequence( c, precision );
3159 if ( !coordSeq )
3160 return nullptr;
3161
3162 geos::unique_ptr geosGeom;
3163 try
3164 {
3165 geosGeom.reset( GEOSGeom_createLineString_r( QgsGeosContext::get(), coordSeq ) );
3166 }
3167 CATCH_GEOS( nullptr )
3168 return geosGeom;
3169}
3170
3171#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 15 )
3172geos::unique_ptr QgsGeos::createGeosSimpleCurve( const QgsAbstractGeometry *curve, double precision, Qgis::GeosCreationFlags )
3173{
3174 const QgsCurve *c = qgsgeometry_cast<const QgsCurve *>( curve );
3175 if ( !c )
3176 return nullptr;
3177
3178 // TODO: implement QgsCurve->isSimpleCurve()?
3180 return nullptr;
3181
3182 GEOSCoordSequence *coordSeq = createCoordinateSequence( c, precision );
3183 if ( !coordSeq )
3184 return nullptr;
3185
3186 geos::unique_ptr geosGeom;
3187 try
3188 {
3189 if ( !c->hasCurvedSegments() )
3190 {
3191 geosGeom.reset( GEOSGeom_createLineString_r( QgsGeosContext::get(), coordSeq ) );
3192 }
3193 else
3194 {
3195 geosGeom.reset( GEOSGeom_createCircularString_r( QgsGeosContext::get(), coordSeq ) );
3196 }
3197 }
3198 CATCH_GEOS( nullptr )
3199 return geosGeom;
3200}
3201
3202geos::unique_ptr QgsGeos::createGeosCompoundCurve( const QgsAbstractGeometry *curve, double precision, Qgis::GeosCreationFlags flags )
3203{
3204 const QgsCompoundCurve *c = qgsgeometry_cast<const QgsCompoundCurve *>( curve );
3205 if ( !c )
3206 return nullptr;
3207
3208 GEOSContextHandle_t context = QgsGeosContext::get();
3209 geos::unique_ptr geosCurve;
3210
3211 try
3212 {
3213 const int nCurves = c->nCurves();
3214 GEOSGeometry **curves = new GEOSGeometry *[nCurves];
3215
3216 for ( int i = 0; i < nCurves; i++ )
3217 {
3218 // TODO: use QgsCurve->isSimpleCurve()
3220 {
3221 curves[i] = createGeosSimpleCurve( c->curveAt( i ), precision, flags ).release();
3222 }
3223 }
3224 geosCurve.reset( GEOSGeom_createCompoundCurve_r( context, curves, nCurves ) );
3225 delete[] curves;
3226 }
3227 CATCH_GEOS( nullptr )
3228 return geosCurve;
3229}
3230
3231geos::unique_ptr QgsGeos::createGeosCurvePolygon( const QgsAbstractGeometry *poly, double precision, Qgis::GeosCreationFlags flags )
3232{
3233 const QgsCurvePolygon *polygon = qgsgeometry_cast<const QgsCurvePolygon *>( poly );
3234 if ( !polygon )
3235 return nullptr;
3236
3237 const QgsCurve *exteriorRing = polygon->exteriorRing();
3238 if ( !exteriorRing )
3239 {
3240 return nullptr;
3241 }
3242
3243 GEOSContextHandle_t context = QgsGeosContext::get();
3244 geos::unique_ptr geosCurvePolygon;
3245 try
3246 {
3247 geos::unique_ptr exteriorRingGeos;
3248 // TODO: implement QgsCurve::isSimpleCurve() ?
3250 {
3251 exteriorRingGeos.reset( createGeosSimpleCurve( exteriorRing, precision, flags ).release() );
3252 }
3253 else
3254 {
3255 exteriorRingGeos.reset( createGeosCompoundCurve( exteriorRing, precision, flags ).release() );
3256 }
3257
3258 const int nInteriorRings = polygon->numInteriorRings();
3259 QList< const QgsCurve * > holesToExport;
3260 holesToExport.reserve( nInteriorRings );
3261 for ( int i = 0; i < nInteriorRings; ++i )
3262 {
3263 const QgsCurve *interiorRing = polygon->interiorRing( i );
3264 if ( !( flags & Qgis::GeosCreationFlag::SkipEmptyInteriorRings ) || !interiorRing->isEmpty() )
3265 {
3266 holesToExport << interiorRing;
3267 }
3268 }
3269
3270 GEOSGeometry **holes = nullptr;
3271 if ( !holesToExport.empty() )
3272 {
3273 holes = new GEOSGeometry *[holesToExport.size()];
3274 for ( int i = 0; i < holesToExport.size(); ++i )
3275 {
3276 // TODO: implement QgsCurve::isSimpleCurve() ?
3277 if ( QgsWkbTypes::flatType( holesToExport[i]->wkbType() ) == Qgis::WkbType::CircularString || QgsWkbTypes::flatType( holesToExport[i]->wkbType() ) == Qgis::WkbType::LineString )
3278 {
3279 holes[i] = createGeosSimpleCurve( holesToExport[i], precision, flags ).release();
3280 }
3281 else
3282 {
3283 holes[i] = createGeosCompoundCurve( holesToExport[i], precision, flags ).release();
3284 }
3285 }
3286 }
3287
3288 geosCurvePolygon.reset( GEOSGeom_createCurvePolygon_r( context, exteriorRingGeos.release(), holes, holesToExport.size() ) );
3289 delete[] holes;
3290 }
3291 CATCH_GEOS( nullptr )
3292
3293 return geosCurvePolygon;
3294}
3295#endif
3296
3297geos::unique_ptr QgsGeos::createGeosPolygon( const QgsAbstractGeometry *poly, double precision, Qgis::GeosCreationFlags flags )
3298{
3299 const QgsCurvePolygon *polygon = qgsgeometry_cast<const QgsCurvePolygon *>( poly );
3300 if ( !polygon )
3301 return nullptr;
3302
3303 const QgsCurve *exteriorRing = polygon->exteriorRing();
3304 if ( !exteriorRing )
3305 {
3306 return nullptr;
3307 }
3308
3309 GEOSContextHandle_t context = QgsGeosContext::get();
3310 geos::unique_ptr geosPolygon;
3311 try
3312 {
3313 geos::unique_ptr exteriorRingGeos( GEOSGeom_createLinearRing_r( context, createCoordinateSequence( exteriorRing, precision, true ) ) );
3314
3315 const int nInteriorRings = polygon->numInteriorRings();
3316 QList< const QgsCurve * > holesToExport;
3317 holesToExport.reserve( nInteriorRings );
3318 for ( int i = 0; i < nInteriorRings; ++i )
3319 {
3320 const QgsCurve *interiorRing = polygon->interiorRing( i );
3321 if ( !( flags & Qgis::GeosCreationFlag::SkipEmptyInteriorRings ) || !interiorRing->isEmpty() )
3322 {
3323 holesToExport << interiorRing;
3324 }
3325 }
3326
3327 GEOSGeometry **holes = nullptr;
3328 if ( !holesToExport.empty() )
3329 {
3330 holes = new GEOSGeometry *[holesToExport.size()];
3331 for ( int i = 0; i < holesToExport.size(); ++i )
3332 {
3333 holes[i] = GEOSGeom_createLinearRing_r( context, createCoordinateSequence( holesToExport[i], precision, true ) );
3334 }
3335 }
3336
3337 geosPolygon.reset( GEOSGeom_createPolygon_r( context, exteriorRingGeos.release(), holes, holesToExport.size() ) );
3338 delete[] holes;
3339 }
3340 CATCH_GEOS( nullptr )
3341
3342 return geosPolygon;
3343}
3344
3345geos::unique_ptr QgsGeos::offsetCurve( const GEOSGeometry *geometry, double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg, QgsFeedback *feedback )
3346{
3347 if ( !geometry )
3348 return nullptr;
3349
3350 geos::unique_ptr offset;
3351 try
3352 {
3353 // Force quadrant segments to be at least 8, see
3354 // https://github.com/qgis/QGIS/issues/53165#issuecomment-1563470832
3355 if ( segments < 8 )
3356 segments = 8;
3357 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3358 offset.reset( GEOSOffsetCurve_r( QgsGeosContext::get(), geometry, distance, segments, static_cast< int >( joinStyle ), miterLimit ) );
3359 }
3360 CATCH_GEOS_WITH_ERRMSG( nullptr )
3361 return offset;
3362}
3363
3364QgsAbstractGeometry *QgsGeos::offsetCurve( double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg, QgsFeedback *feedback ) const
3365{
3366 geos::unique_ptr res = offsetCurve( mGeos.get(), distance, segments, joinStyle, miterLimit, errorMsg, feedback );
3367 if ( !res )
3368 return nullptr;
3369
3370 return fromGeos( res.get() ).release();
3371}
3372
3373std::unique_ptr<QgsAbstractGeometry> QgsGeos::singleSidedBuffer(
3374 double distance, int segments, Qgis::BufferSide side, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg, QgsFeedback *feedback
3375) const
3376{
3377 if ( !mGeos )
3378 {
3379 return nullptr;
3380 }
3381
3383 GEOSContextHandle_t context = QgsGeosContext::get();
3384 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3385 try
3386 {
3387 geos::buffer_params_unique_ptr bp( GEOSBufferParams_create_r( context ) );
3388 GEOSBufferParams_setSingleSided_r( context, bp.get(), 1 );
3389 GEOSBufferParams_setQuadrantSegments_r( context, bp.get(), segments );
3390 GEOSBufferParams_setJoinStyle_r( context, bp.get(), static_cast< int >( joinStyle ) );
3391 GEOSBufferParams_setMitreLimit_r( context, bp.get(), miterLimit ); //#spellok
3392
3393 if ( side == Qgis::BufferSide::Right )
3394 {
3395 distance = -distance;
3396 }
3397 geos.reset( GEOSBufferWithParams_r( context, mGeos.get(), bp.get(), distance ) );
3398 }
3399 CATCH_GEOS_WITH_ERRMSG( nullptr )
3400 return fromGeos( geos.get() );
3401}
3402
3403std::unique_ptr<QgsAbstractGeometry> QgsGeos::maximumInscribedCircle( double tolerance, QString *errorMsg, QgsFeedback *feedback ) const
3404{
3405 if ( !mGeos )
3406 {
3407 return nullptr;
3408 }
3409
3411 try
3412 {
3413 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3414 geos.reset( GEOSMaximumInscribedCircle_r( QgsGeosContext::get(), mGeos.get(), tolerance ) );
3415 }
3416 CATCH_GEOS_WITH_ERRMSG( nullptr )
3417 return fromGeos( geos.get() );
3418}
3419
3420std::unique_ptr<QgsAbstractGeometry> QgsGeos::largestEmptyCircle( double tolerance, const QgsAbstractGeometry *boundary, QString *errorMsg, QgsFeedback *feedback ) const
3421{
3422 if ( !mGeos )
3423 {
3424 return nullptr;
3425 }
3426
3428 try
3429 {
3430 geos::unique_ptr boundaryGeos;
3431 if ( boundary )
3432 boundaryGeos = asGeos( boundary );
3433
3434 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3435 geos.reset( GEOSLargestEmptyCircle_r( QgsGeosContext::get(), mGeos.get(), boundaryGeos.get(), tolerance ) );
3436 }
3437 CATCH_GEOS_WITH_ERRMSG( nullptr )
3438 return fromGeos( geos.get() );
3439}
3440
3441std::unique_ptr<QgsAbstractGeometry> QgsGeos::minimumWidth( QString *errorMsg, QgsFeedback *feedback ) const
3442{
3443 if ( !mGeos )
3444 {
3445 return nullptr;
3446 }
3447
3449 try
3450 {
3451 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3452 geos.reset( GEOSMinimumWidth_r( QgsGeosContext::get(), mGeos.get() ) );
3453 }
3454 CATCH_GEOS_WITH_ERRMSG( nullptr )
3455 return fromGeos( geos.get() );
3456}
3457
3458double QgsGeos::minimumClearance( QString *errorMsg, QgsFeedback *feedback ) const
3459{
3460 if ( !mGeos )
3461 {
3462 return std::numeric_limits< double >::quiet_NaN();
3463 }
3464
3466 double res = 0;
3467 try
3468 {
3469 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3470 if ( GEOSMinimumClearance_r( QgsGeosContext::get(), mGeos.get(), &res ) != 0 )
3471 return std::numeric_limits< double >::quiet_NaN();
3472 }
3473 CATCH_GEOS_WITH_ERRMSG( std::numeric_limits< double >::quiet_NaN() )
3474 return res;
3475}
3476
3477std::unique_ptr<QgsAbstractGeometry> QgsGeos::minimumClearanceLine( 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( GEOSMinimumClearanceLine_r( QgsGeosContext::get(), mGeos.get() ) );
3489 }
3490 CATCH_GEOS_WITH_ERRMSG( nullptr )
3491 return fromGeos( geos.get() );
3492}
3493
3494std::unique_ptr<QgsAbstractGeometry> QgsGeos::node( QString *errorMsg, QgsFeedback *feedback ) const
3495{
3496 if ( !mGeos )
3497 {
3498 return nullptr;
3499 }
3500
3502 try
3503 {
3504 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3505 geos.reset( GEOSNode_r( QgsGeosContext::get(), mGeos.get() ) );
3506 }
3507 CATCH_GEOS_WITH_ERRMSG( nullptr )
3508 return fromGeos( geos.get() );
3509}
3510
3511std::unique_ptr<QgsAbstractGeometry> QgsGeos::sharedPaths( const QgsAbstractGeometry *other, QString *errorMsg, QgsFeedback *feedback ) const
3512{
3513 if ( !mGeos || !other )
3514 {
3515 return nullptr;
3516 }
3517
3519 try
3520 {
3521 geos::unique_ptr otherGeos = asGeos( other );
3522 if ( !otherGeos )
3523 return nullptr;
3524
3525 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3526 geos.reset( GEOSSharedPaths_r( QgsGeosContext::get(), mGeos.get(), otherGeos.get() ) );
3527 }
3528 CATCH_GEOS_WITH_ERRMSG( nullptr )
3529 return fromGeos( geos.get() );
3530}
3531
3532std::unique_ptr<QgsAbstractGeometry> QgsGeos::reshapeGeometry( const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg ) const
3533{
3534 if ( !mGeos || mGeometry->dimension() == 0 )
3535 {
3536 if ( errorCode )
3537 {
3538 *errorCode = InvalidBaseGeometry;
3539 }
3540 return nullptr;
3541 }
3542
3543 if ( reshapeWithLine.numPoints() < 2 )
3544 {
3545 if ( errorCode )
3546 {
3547 *errorCode = InvalidInput;
3548 }
3549 return nullptr;
3550 }
3551
3552 geos::unique_ptr reshapeLineGeos = createGeosLinestring( &reshapeWithLine, mPrecision );
3553
3554 GEOSContextHandle_t context = QgsGeosContext::get();
3555 //single or multi?
3556 int numGeoms = GEOSGetNumGeometries_r( context, mGeos.get() );
3557 if ( numGeoms == -1 )
3558 {
3559 if ( errorCode )
3560 {
3561 *errorCode = InvalidBaseGeometry;
3562 }
3563 return nullptr;
3564 }
3565
3566 bool isMultiGeom = false;
3567 int geosTypeId = GEOSGeomTypeId_r( context, mGeos.get() );
3568 if ( geosTypeId == GEOS_MULTILINESTRING || geosTypeId == GEOS_MULTIPOLYGON )
3569 isMultiGeom = true;
3570
3571 bool isLine = ( mGeometry->dimension() == 1 );
3572
3573 if ( !isMultiGeom )
3574 {
3575 geos::unique_ptr reshapedGeometry;
3576 if ( isLine )
3577 {
3578 reshapedGeometry = reshapeLine( mGeos.get(), reshapeLineGeos.get(), mPrecision );
3579 }
3580 else
3581 {
3582 reshapedGeometry = reshapePolygon( mGeos.get(), reshapeLineGeos.get(), mPrecision );
3583 }
3584
3585 if ( errorCode )
3586 {
3587 if ( reshapedGeometry )
3588 *errorCode = Success;
3589 else
3590 *errorCode = NothingHappened;
3591 }
3592
3593 std::unique_ptr< QgsAbstractGeometry > reshapeResult = fromGeos( reshapedGeometry.get() );
3594 return reshapeResult;
3595 }
3596 else
3597 {
3598 try
3599 {
3600 //call reshape for each geometry part and replace mGeos with new geometry if reshape took place
3601 bool reshapeTookPlace = false;
3602
3603 geos::unique_ptr currentReshapeGeometry;
3604 GEOSGeometry **newGeoms = new GEOSGeometry *[numGeoms];
3605
3606 for ( int i = 0; i < numGeoms; ++i )
3607 {
3608 if ( isLine )
3609 currentReshapeGeometry = reshapeLine( GEOSGetGeometryN_r( context, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
3610 else
3611 currentReshapeGeometry = reshapePolygon( GEOSGetGeometryN_r( context, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
3612
3613 if ( currentReshapeGeometry )
3614 {
3615 newGeoms[i] = currentReshapeGeometry.release();
3616 reshapeTookPlace = true;
3617 }
3618 else
3619 {
3620 newGeoms[i] = GEOSGeom_clone_r( context, GEOSGetGeometryN_r( context, mGeos.get(), i ) );
3621 }
3622 }
3623
3624 geos::unique_ptr newMultiGeom;
3625 if ( isLine )
3626 {
3627 newMultiGeom.reset( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, newGeoms, numGeoms ) );
3628 }
3629 else //multipolygon
3630 {
3631 newMultiGeom.reset( GEOSGeom_createCollection_r( context, GEOS_MULTIPOLYGON, newGeoms, numGeoms ) );
3632 }
3633
3634 delete[] newGeoms;
3635 if ( !newMultiGeom )
3636 {
3637 if ( errorCode )
3638 {
3639 *errorCode = EngineError;
3640 }
3641 return nullptr;
3642 }
3643
3644 if ( reshapeTookPlace )
3645 {
3646 if ( errorCode )
3647 *errorCode = Success;
3648 std::unique_ptr< QgsAbstractGeometry > reshapedMultiGeom = fromGeos( newMultiGeom.get() );
3649 return reshapedMultiGeom;
3650 }
3651 else
3652 {
3653 if ( errorCode )
3654 {
3655 *errorCode = NothingHappened;
3656 }
3657 return nullptr;
3658 }
3659 }
3660 CATCH_GEOS_WITH_ERRMSG( nullptr )
3661 }
3662}
3663
3664std::unique_ptr< QgsAbstractGeometry > QgsGeos::mergeLines( QString *errorMsg, const QgsGeometryParameters &parameters, QgsFeedback *feedback ) const
3665{
3666 if ( !mGeos )
3667 {
3668 return nullptr;
3669 }
3670
3671 GEOSContextHandle_t context = QgsGeosContext::get();
3672 if ( GEOSGeomTypeId_r( context, mGeos.get() ) != GEOS_MULTILINESTRING )
3673 return nullptr;
3674
3675 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3677 try
3678 {
3679 double gridSize = parameters.gridSize();
3680 if ( gridSize > 0 )
3681 {
3682 geos::unique_ptr geosFixedSize( GEOSGeom_setPrecision_r( context, mGeos.get(), gridSize, 0 ) );
3683 geos.reset( GEOSLineMerge_r( context, geosFixedSize.get() ) );
3684 }
3685 else
3686 geos.reset( GEOSLineMerge_r( context, mGeos.get() ) );
3687 }
3688 CATCH_GEOS_WITH_ERRMSG( nullptr )
3689 return fromGeos( geos.get() );
3690}
3691
3692std::unique_ptr<QgsAbstractGeometry> QgsGeos::closestPoint( const QgsGeometry &other, QString *errorMsg, QgsFeedback *feedback ) const
3693{
3694 if ( !mGeos || isEmpty() || other.isEmpty() )
3695 {
3696 return nullptr;
3697 }
3698
3699 geos::unique_ptr otherGeom( asGeos( other.constGet(), mPrecision ) );
3700 if ( !otherGeom )
3701 {
3702 return nullptr;
3703 }
3704
3705 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3706 GEOSContextHandle_t context = QgsGeosContext::get();
3707 double nx = 0.0;
3708 double ny = 0.0;
3709 try
3710 {
3712 if ( mGeosPrepared ) // use faster version with prepared geometry
3713 {
3714 nearestCoord.reset( GEOSPreparedNearestPoints_r( context, mGeosPrepared.get(), otherGeom.get() ) );
3715 }
3716 else
3717 {
3718 nearestCoord.reset( GEOSNearestPoints_r( context, mGeos.get(), otherGeom.get() ) );
3719 }
3720
3721 ( void ) GEOSCoordSeq_getX_r( context, nearestCoord.get(), 0, &nx );
3722 ( void ) GEOSCoordSeq_getY_r( context, nearestCoord.get(), 0, &ny );
3723 }
3724 catch ( QgsGeosException &e )
3725 {
3726 logError( u"GEOS"_s, e.what() );
3727 if ( errorMsg )
3728 {
3729 *errorMsg = e.what();
3730 }
3731 return nullptr;
3732 }
3733
3734 return std::make_unique< QgsPoint >( nx, ny );
3735}
3736
3737std::unique_ptr<QgsAbstractGeometry> QgsGeos::shortestLine( const QgsGeometry &other, QString *errorMsg, QgsFeedback *feedback ) const
3738{
3739 if ( !mGeos || other.isEmpty() )
3740 {
3741 return nullptr;
3742 }
3743
3744 return shortestLine( other.constGet(), errorMsg, feedback );
3745}
3746
3747std::unique_ptr< QgsAbstractGeometry > QgsGeos::shortestLine( const QgsAbstractGeometry *other, QString *errorMsg, QgsFeedback *feedback ) const
3748{
3749 if ( !other || other->isEmpty() )
3750 return nullptr;
3751
3752 geos::unique_ptr otherGeom( asGeos( other, mPrecision ) );
3753 if ( !otherGeom )
3754 {
3755 return nullptr;
3756 }
3757
3758 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3759 GEOSContextHandle_t context = QgsGeosContext::get();
3760 double nx1 = 0.0;
3761 double ny1 = 0.0;
3762 double nx2 = 0.0;
3763 double ny2 = 0.0;
3764 try
3765 {
3766 geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( context, mGeos.get(), otherGeom.get() ) );
3767
3768 if ( !nearestCoord )
3769 {
3770 if ( errorMsg )
3771 *errorMsg = u"GEOS returned no nearest points"_s;
3772 return nullptr;
3773 }
3774
3775 ( void ) GEOSCoordSeq_getX_r( context, nearestCoord.get(), 0, &nx1 );
3776 ( void ) GEOSCoordSeq_getY_r( context, nearestCoord.get(), 0, &ny1 );
3777 ( void ) GEOSCoordSeq_getX_r( context, nearestCoord.get(), 1, &nx2 );
3778 ( void ) GEOSCoordSeq_getY_r( context, nearestCoord.get(), 1, &ny2 );
3779 }
3780 catch ( QgsGeosException &e )
3781 {
3782 logError( u"GEOS"_s, e.what() );
3783 if ( errorMsg )
3784 {
3785 *errorMsg = e.what();
3786 }
3787 return nullptr;
3788 }
3789
3790 auto line = std::make_unique< QgsLineString >();
3791 line->addVertex( QgsPoint( nx1, ny1 ) );
3792 line->addVertex( QgsPoint( nx2, ny2 ) );
3793 return line;
3794}
3795
3796double QgsGeos::lineLocatePoint( const QgsPoint &point, QString *errorMsg, QgsFeedback *feedback ) const
3797{
3798 if ( !mGeos )
3799 {
3800 return -1;
3801 }
3802
3803 geos::unique_ptr otherGeom( asGeos( &point, mPrecision ) );
3804 if ( !otherGeom )
3805 {
3806 return -1;
3807 }
3808
3809 double distance = -1;
3810 try
3811 {
3812 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3813 distance = GEOSProject_r( QgsGeosContext::get(), mGeos.get(), otherGeom.get() );
3814 }
3815 catch ( QgsGeosException &e )
3816 {
3817 logError( u"GEOS"_s, e.what() );
3818 if ( errorMsg )
3819 {
3820 *errorMsg = e.what();
3821 }
3822 return -1;
3823 }
3824
3825 return distance;
3826}
3827
3828double QgsGeos::lineLocatePoint( double x, double y, QString *errorMsg, QgsFeedback *feedback ) const
3829{
3830 if ( !mGeos )
3831 {
3832 return -1;
3833 }
3834
3835 geos::unique_ptr point = createGeosPointXY( x, y, false, 0, false, 0, 2, 0 );
3836 if ( !point )
3837 return false;
3838
3839 double distance = -1;
3840 try
3841 {
3842 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3843 distance = GEOSProject_r( QgsGeosContext::get(), mGeos.get(), point.get() );
3844 }
3845 catch ( QgsGeosException &e )
3846 {
3847 logError( u"GEOS"_s, e.what() );
3848 if ( errorMsg )
3849 {
3850 *errorMsg = e.what();
3851 }
3852 return -1;
3853 }
3854
3855 return distance;
3856}
3857
3858QgsGeometry QgsGeos::polygonize( const QVector<const QgsAbstractGeometry *> &geometries, QString *errorMsg, QgsFeedback *feedback )
3859{
3860 GEOSGeometry **const lineGeosGeometries = new GEOSGeometry *[geometries.size()];
3861 int validLines = 0;
3862 for ( const QgsAbstractGeometry *g : geometries )
3863 {
3864 geos::unique_ptr l = asGeos( g );
3865 if ( l )
3866 {
3867 lineGeosGeometries[validLines] = l.release();
3868 validLines++;
3869 }
3870 }
3871
3872 GEOSContextHandle_t context = QgsGeosContext::get();
3873 try
3874 {
3875 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3876 geos::unique_ptr result( GEOSPolygonize_r( context, lineGeosGeometries, validLines ) );
3877 for ( int i = 0; i < validLines; ++i )
3878 {
3879 GEOSGeom_destroy_r( context, lineGeosGeometries[i] );
3880 }
3881 delete[] lineGeosGeometries;
3882 return QgsGeometry( fromGeos( result.get() ) );
3883 }
3884 catch ( QgsGeosException &e )
3885 {
3886 if ( errorMsg )
3887 {
3888 *errorMsg = e.what();
3889 }
3890 for ( int i = 0; i < validLines; ++i )
3891 {
3892 GEOSGeom_destroy_r( context, lineGeosGeometries[i] );
3893 }
3894 delete[] lineGeosGeometries;
3895 return QgsGeometry();
3896 }
3897}
3898
3899std::unique_ptr<QgsAbstractGeometry> QgsGeos::voronoiDiagram( const QgsAbstractGeometry *extent, double tolerance, bool edgesOnly, QString *errorMsg, QgsFeedback *feedback ) const
3900{
3901 if ( !mGeos )
3902 {
3903 return nullptr;
3904 }
3905
3906 geos::unique_ptr extentGeosGeom;
3907 if ( extent )
3908 {
3909 extentGeosGeom = asGeos( extent, mPrecision );
3910 if ( !extentGeosGeom )
3911 {
3912 return nullptr;
3913 }
3914 }
3915
3917 GEOSContextHandle_t context = QgsGeosContext::get();
3918 try
3919 {
3920 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3921 geos.reset( GEOSVoronoiDiagram_r( context, mGeos.get(), extentGeosGeom.get(), tolerance, edgesOnly ) );
3922
3923 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3924 {
3925 return nullptr;
3926 }
3927
3928 return fromGeos( geos.get() );
3929 }
3930 CATCH_GEOS_WITH_ERRMSG( nullptr )
3931}
3932
3933std::unique_ptr<QgsAbstractGeometry> QgsGeos::delaunayTriangulation( double tolerance, bool edgesOnly, QString *errorMsg, QgsFeedback *feedback ) const
3934{
3935 if ( !mGeos )
3936 {
3937 return nullptr;
3938 }
3939
3940 GEOSContextHandle_t context = QgsGeosContext::get();
3942 try
3943 {
3944 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3945 geos.reset( GEOSDelaunayTriangulation_r( context, mGeos.get(), tolerance, edgesOnly ) );
3946
3947 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3948 {
3949 return nullptr;
3950 }
3951
3952 return fromGeos( geos.get() );
3953 }
3954 CATCH_GEOS_WITH_ERRMSG( nullptr )
3955}
3956
3957std::unique_ptr<QgsAbstractGeometry> QgsGeos::constrainedDelaunayTriangulation( QString *errorMsg, QgsFeedback *feedback ) const
3958{
3959#if GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 11
3960 ( void ) errorMsg;
3961 throw QgsNotSupportedException( QObject::tr( "Calculating constrainedDelaunayTriangulation requires a QGIS build based on GEOS 3.11 or later" ) );
3962#else
3963 if ( !mGeos )
3964 {
3965 return nullptr;
3966 }
3967
3969 GEOSContextHandle_t context = QgsGeosContext::get();
3970 try
3971 {
3972 QgsScopedGeosContextRegisterFeedback interrupt( feedback );
3973 geos.reset( GEOSConstrainedDelaunayTriangulation_r( context, mGeos.get() ) );
3974
3975 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3976 {
3977 return nullptr;
3978 }
3979
3980 std::unique_ptr< QgsAbstractGeometry > res = fromGeos( geos.get() );
3981 if ( const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( res.get() ) )
3982 {
3983 return std::unique_ptr< QgsAbstractGeometry >( collection->extractPartsByType( Qgis::WkbType::Polygon, true ) );
3984 }
3985 else
3986 {
3987 return res;
3988 }
3989 }
3990 CATCH_GEOS_WITH_ERRMSG( nullptr )
3991#endif
3992}
3993
3995static bool _linestringEndpoints( const GEOSGeometry *linestring, double &x1, double &y1, double &x2, double &y2 )
3996{
3997 GEOSContextHandle_t context = QgsGeosContext::get();
3998 const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( context, linestring );
3999 if ( !coordSeq )
4000 return false;
4001
4002 unsigned int coordSeqSize;
4003 if ( GEOSCoordSeq_getSize_r( context, coordSeq, &coordSeqSize ) == 0 )
4004 return false;
4005
4006 if ( coordSeqSize < 2 )
4007 return false;
4008
4009 GEOSCoordSeq_getX_r( context, coordSeq, 0, &x1 );
4010 GEOSCoordSeq_getY_r( context, coordSeq, 0, &y1 );
4011 GEOSCoordSeq_getX_r( context, coordSeq, coordSeqSize - 1, &x2 );
4012 GEOSCoordSeq_getY_r( context, coordSeq, coordSeqSize - 1, &y2 );
4013 return true;
4014}
4015
4016
4018static geos::unique_ptr _mergeLinestrings( const GEOSGeometry *line1, const GEOSGeometry *line2, const QgsPointXY &intersectionPoint )
4019{
4020 double x1, y1, x2, y2;
4021 if ( !_linestringEndpoints( line1, x1, y1, x2, y2 ) )
4022 return nullptr;
4023
4024 double rx1, ry1, rx2, ry2;
4025 if ( !_linestringEndpoints( line2, rx1, ry1, rx2, ry2 ) )
4026 return nullptr;
4027
4028 bool intersectionAtOrigLineEndpoint = ( intersectionPoint.x() == x1 && intersectionPoint.y() == y1 ) != ( intersectionPoint.x() == x2 && intersectionPoint.y() == y2 );
4029 bool intersectionAtReshapeLineEndpoint = ( intersectionPoint.x() == rx1 && intersectionPoint.y() == ry1 ) || ( intersectionPoint.x() == rx2 && intersectionPoint.y() == ry2 );
4030
4031 GEOSContextHandle_t context = QgsGeosContext::get();
4032 // the intersection must be at the begin/end of both lines
4033 if ( intersectionAtOrigLineEndpoint && intersectionAtReshapeLineEndpoint )
4034 {
4035 geos::unique_ptr g1( GEOSGeom_clone_r( context, line1 ) );
4036 geos::unique_ptr g2( GEOSGeom_clone_r( context, line2 ) );
4037 GEOSGeometry *geoms[2] = { g1.release(), g2.release() };
4038 geos::unique_ptr multiGeom( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, geoms, 2 ) );
4039 geos::unique_ptr res( GEOSLineMerge_r( context, multiGeom.get() ) );
4040
4041 //keep the original orientation if the result has a start or end point in common with the original line
4042 //and this point is not the start or the end point for both lines
4043 double x1res, y1res, x2res, y2res;
4044 if ( !_linestringEndpoints( res.get(), x1res, y1res, x2res, y2res ) )
4045 return nullptr;
4046 if ( ( x1res == x2 && y1res == y2 ) || ( x2res == x1 && y2res == y1 ) )
4047 res.reset( GEOSReverse_r( context, res.get() ) );
4048
4049 return res;
4050 }
4051 else
4052 return nullptr;
4053}
4054
4055
4056geos::unique_ptr QgsGeos::reshapeLine( const GEOSGeometry *line, const GEOSGeometry *reshapeLineGeos, double precision )
4057{
4058 if ( !line || !reshapeLineGeos )
4059 return nullptr;
4060
4061 bool atLeastTwoIntersections = false;
4062 bool oneIntersection = false;
4063 QgsPointXY oneIntersectionPoint;
4064
4065 GEOSContextHandle_t context = QgsGeosContext::get();
4066 try
4067 {
4068 //make sure there are at least two intersection between line and reshape geometry
4069 geos::unique_ptr intersectGeom( GEOSIntersection_r( context, line, reshapeLineGeos ) );
4070 if ( intersectGeom )
4071 {
4072 const int geomType = GEOSGeomTypeId_r( context, intersectGeom.get() );
4073 atLeastTwoIntersections = ( geomType == GEOS_MULTIPOINT && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 1 )
4074 || ( geomType == GEOS_GEOMETRYCOLLECTION && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 0 ) // a collection implies at least two points!
4075 || ( geomType == GEOS_MULTILINESTRING && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 0 );
4076 // one point is enough when extending line at its endpoint
4077 if ( GEOSGeomTypeId_r( context, intersectGeom.get() ) == GEOS_POINT )
4078 {
4079 const GEOSCoordSequence *intersectionCoordSeq = GEOSGeom_getCoordSeq_r( context, intersectGeom.get() );
4080 double xi, yi;
4081 GEOSCoordSeq_getX_r( context, intersectionCoordSeq, 0, &xi );
4082 GEOSCoordSeq_getY_r( context, intersectionCoordSeq, 0, &yi );
4083 oneIntersection = true;
4084 oneIntersectionPoint = QgsPointXY( xi, yi );
4085 }
4086 }
4087 }
4088 catch ( QgsGeosException & )
4089 {
4090 atLeastTwoIntersections = false;
4091 }
4092
4093 // special case when extending line at its endpoint
4094 if ( oneIntersection )
4095 return _mergeLinestrings( line, reshapeLineGeos, oneIntersectionPoint );
4096
4097 if ( !atLeastTwoIntersections )
4098 return nullptr;
4099
4100 //begin and end point of original line
4101 double x1, y1, x2, y2;
4102 if ( !_linestringEndpoints( line, x1, y1, x2, y2 ) )
4103 return nullptr;
4104
4105 geos::unique_ptr beginLineVertex = createGeosPointXY( x1, y1, false, 0, false, 0, 2, precision );
4106 geos::unique_ptr endLineVertex = createGeosPointXY( x2, y2, false, 0, false, 0, 2, precision );
4107
4108 bool isRing = false;
4109 if ( GEOSGeomTypeId_r( context, line ) == GEOS_LINEARRING || GEOSEquals_r( context, beginLineVertex.get(), endLineVertex.get() ) == 1 )
4110 isRing = true;
4111
4112 //node line and reshape line
4113 geos::unique_ptr nodedGeometry = nodeGeometries( reshapeLineGeos, line );
4114 if ( !nodedGeometry )
4115 {
4116 return nullptr;
4117 }
4118
4119 //and merge them together
4120 geos::unique_ptr mergedLines( GEOSLineMerge_r( context, nodedGeometry.get() ) );
4121 if ( !mergedLines )
4122 {
4123 return nullptr;
4124 }
4125
4126 int numMergedLines = GEOSGetNumGeometries_r( context, mergedLines.get() );
4127 if ( numMergedLines < 2 ) //some special cases. Normally it is >2
4128 {
4129 if ( numMergedLines == 1 ) //reshape line is from begin to endpoint. So we keep the reshapeline
4130 {
4131 geos::unique_ptr result( GEOSGeom_clone_r( context, reshapeLineGeos ) );
4132 return result;
4133 }
4134 else
4135 return nullptr;
4136 }
4137
4138 QVector<GEOSGeometry *> resultLineParts; //collection with the line segments that will be contained in result
4139 QVector<GEOSGeometry *> probableParts; //parts where we can decide on inclusion only after going through all the candidates
4140
4141 for ( int i = 0; i < numMergedLines; ++i )
4142 {
4143 const GEOSGeometry *currentGeom = GEOSGetGeometryN_r( context, mergedLines.get(), i );
4144
4145 // have we already added this part?
4146 bool alreadyAdded = false;
4147 double distance = 0;
4148 double bufferDistance = std::pow( 10.0L, geomDigits( currentGeom ) - 11 );
4149 for ( const GEOSGeometry *other : std::as_const( resultLineParts ) )
4150 {
4151 GEOSHausdorffDistance_r( context, currentGeom, other, &distance );
4152 if ( distance < bufferDistance )
4153 {
4154 alreadyAdded = true;
4155 break;
4156 }
4157 }
4158 if ( alreadyAdded )
4159 continue;
4160
4161 const GEOSCoordSequence *currentCoordSeq = GEOSGeom_getCoordSeq_r( context, currentGeom );
4162 unsigned int currentCoordSeqSize;
4163 GEOSCoordSeq_getSize_r( context, currentCoordSeq, &currentCoordSeqSize );
4164 if ( currentCoordSeqSize < 2 )
4165 continue;
4166
4167 //get the two endpoints of the current line merge result
4168 double xBegin, xEnd, yBegin, yEnd;
4169 GEOSCoordSeq_getX_r( context, currentCoordSeq, 0, &xBegin );
4170 GEOSCoordSeq_getY_r( context, currentCoordSeq, 0, &yBegin );
4171 GEOSCoordSeq_getX_r( context, currentCoordSeq, currentCoordSeqSize - 1, &xEnd );
4172 GEOSCoordSeq_getY_r( context, currentCoordSeq, currentCoordSeqSize - 1, &yEnd );
4173 geos::unique_ptr beginCurrentGeomVertex = createGeosPointXY( xBegin, yBegin, false, 0, false, 0, 2, precision );
4174 geos::unique_ptr endCurrentGeomVertex = createGeosPointXY( xEnd, yEnd, false, 0, false, 0, 2, precision );
4175
4176 //check how many endpoints of the line merge result are on the (original) line
4177 int nEndpointsOnOriginalLine = 0;
4178 if ( pointContainedInLine( beginCurrentGeomVertex.get(), line ) == 1 )
4179 nEndpointsOnOriginalLine += 1;
4180
4181 if ( pointContainedInLine( endCurrentGeomVertex.get(), line ) == 1 )
4182 nEndpointsOnOriginalLine += 1;
4183
4184 //check how many endpoints equal the endpoints of the original line
4185 int nEndpointsSameAsOriginalLine = 0;
4186 if ( GEOSEquals_r( context, beginCurrentGeomVertex.get(), beginLineVertex.get() ) == 1 || GEOSEquals_r( context, beginCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
4187 nEndpointsSameAsOriginalLine += 1;
4188
4189 if ( GEOSEquals_r( context, endCurrentGeomVertex.get(), beginLineVertex.get() ) == 1 || GEOSEquals_r( context, endCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
4190 nEndpointsSameAsOriginalLine += 1;
4191
4192 //check if the current geometry overlaps the original geometry (GEOSOverlap does not seem to work with linestrings)
4193 bool currentGeomOverlapsOriginalGeom = false;
4194 bool currentGeomOverlapsReshapeLine = false;
4195 if ( lineContainedInLine( currentGeom, line ) == 1 )
4196 currentGeomOverlapsOriginalGeom = true;
4197
4198 if ( lineContainedInLine( currentGeom, reshapeLineGeos ) == 1 )
4199 currentGeomOverlapsReshapeLine = true;
4200
4201 //logic to decide if this part belongs to the result
4202 if ( !isRing && nEndpointsSameAsOriginalLine == 1 && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
4203 {
4204 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
4205 }
4206 //for closed rings, we take one segment from the candidate list
4207 else if ( isRing && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
4208 {
4209 probableParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
4210 }
4211 else if ( nEndpointsOnOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
4212 {
4213 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
4214 }
4215 else if ( nEndpointsSameAsOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
4216 {
4217 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
4218 }
4219 else if ( currentGeomOverlapsOriginalGeom && currentGeomOverlapsReshapeLine )
4220 {
4221 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
4222 }
4223 }
4224
4225 //add the longest segment from the probable list for rings (only used for polygon rings)
4226 if ( isRing && !probableParts.isEmpty() )
4227 {
4228 geos::unique_ptr maxGeom; //the longest geometry in the probabla list
4229 GEOSGeometry *currentGeom = nullptr;
4230 double maxLength = -std::numeric_limits<double>::max();
4231 double currentLength = 0;
4232 for ( int i = 0; i < probableParts.size(); ++i )
4233 {
4234 currentGeom = probableParts.at( i );
4235 GEOSLength_r( context, currentGeom, &currentLength );
4236 if ( currentLength > maxLength )
4237 {
4238 maxLength = currentLength;
4239 maxGeom.reset( currentGeom );
4240 }
4241 else
4242 {
4243 GEOSGeom_destroy_r( context, currentGeom );
4244 }
4245 }
4246 resultLineParts.push_back( maxGeom.release() );
4247 }
4248
4249 geos::unique_ptr result;
4250 if ( resultLineParts.empty() )
4251 return nullptr;
4252
4253 if ( resultLineParts.size() == 1 ) //the whole result was reshaped
4254 {
4255 result.reset( resultLineParts[0] );
4256 }
4257 else //>1
4258 {
4259 GEOSGeometry **lineArray = new GEOSGeometry *[resultLineParts.size()];
4260 for ( int i = 0; i < resultLineParts.size(); ++i )
4261 {
4262 lineArray[i] = resultLineParts[i];
4263 }
4264
4265 //create multiline from resultLineParts
4266 geos::unique_ptr multiLineGeom( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, lineArray, resultLineParts.size() ) );
4267 delete[] lineArray;
4268
4269 //then do a linemerge with the newly combined partstrings
4270 result.reset( GEOSLineMerge_r( context, multiLineGeom.get() ) );
4271 }
4272
4273 //now test if the result is a linestring. Otherwise something went wrong
4274 if ( GEOSGeomTypeId_r( context, result.get() ) != GEOS_LINESTRING )
4275 {
4276 return nullptr;
4277 }
4278
4279 //keep the original orientation
4280 bool reverseLine = false;
4281 if ( isRing )
4282 {
4283 //for closed linestring check clockwise/counter-clockwise
4284 char isResultCCW = 0, isOriginCCW = 0;
4285 if ( GEOSCoordSeq_isCCW_r( context, GEOSGeom_getCoordSeq_r( context, result.get() ), &isResultCCW ) && GEOSCoordSeq_isCCW_r( context, GEOSGeom_getCoordSeq_r( context, line ), &isOriginCCW ) )
4286 {
4287 //reverse line if orientations are different
4288 reverseLine = ( isOriginCCW == 1 && isResultCCW == 0 ) || ( isOriginCCW == 0 && isResultCCW == 1 );
4289 }
4290 }
4291 else
4292 {
4293 //for linestring, check if the result has a start or end point in common with the original line
4294 double x1res, y1res, x2res, y2res;
4295 if ( !_linestringEndpoints( result.get(), x1res, y1res, x2res, y2res ) )
4296 return nullptr;
4297 geos::unique_ptr beginResultLineVertex = createGeosPointXY( x1res, y1res, false, 0, false, 0, 2, precision );
4298 geos::unique_ptr endResultLineVertex = createGeosPointXY( x2res, y2res, false, 0, false, 0, 2, precision );
4299 reverseLine = GEOSEquals_r( context, beginLineVertex.get(), endResultLineVertex.get() ) == 1 || GEOSEquals_r( context, endLineVertex.get(), beginResultLineVertex.get() ) == 1;
4300 }
4301 if ( reverseLine )
4302 result.reset( GEOSReverse_r( context, result.get() ) );
4303
4304 return result;
4305}
4306
4307geos::unique_ptr QgsGeos::reshapePolygon( const GEOSGeometry *polygon, const GEOSGeometry *reshapeLineGeos, double precision )
4308{
4309 //go through outer shell and all inner rings and check if there is exactly one intersection of a ring and the reshape line
4310 int nIntersections = 0;
4311 int lastIntersectingRing = -2;
4312 const GEOSGeometry *lastIntersectingGeom = nullptr;
4313
4314 GEOSContextHandle_t context = QgsGeosContext::get();
4315 int nRings = GEOSGetNumInteriorRings_r( context, polygon );
4316 if ( nRings < 0 )
4317 return nullptr;
4318
4319 //does outer ring intersect?
4320 const GEOSGeometry *outerRing = GEOSGetExteriorRing_r( context, polygon );
4321 if ( GEOSIntersects_r( context, outerRing, reshapeLineGeos ) == 1 )
4322 {
4323 ++nIntersections;
4324 lastIntersectingRing = -1;
4325 lastIntersectingGeom = outerRing;
4326 }
4327
4328 //do inner rings intersect?
4329 const GEOSGeometry **innerRings = new const GEOSGeometry *[nRings];
4330
4331 try
4332 {
4333 for ( int i = 0; i < nRings; ++i )
4334 {
4335 innerRings[i] = GEOSGetInteriorRingN_r( context, polygon, i );
4336 if ( GEOSIntersects_r( context, innerRings[i], reshapeLineGeos ) == 1 )
4337 {
4338 ++nIntersections;
4339 lastIntersectingRing = i;
4340 lastIntersectingGeom = innerRings[i];
4341 }
4342 }
4343 }
4344 catch ( QgsGeosException & )
4345 {
4346 nIntersections = 0;
4347 }
4348
4349 if ( nIntersections != 1 ) //reshape line is only allowed to intersect one ring
4350 {
4351 delete[] innerRings;
4352 return nullptr;
4353 }
4354
4355 //we have one intersecting ring, let's try to reshape it
4356 geos::unique_ptr reshapeResult = reshapeLine( lastIntersectingGeom, reshapeLineGeos, precision );
4357 if ( !reshapeResult )
4358 {
4359 delete[] innerRings;
4360 return nullptr;
4361 }
4362
4363 //if reshaping took place, we need to reassemble the polygon and its rings
4364 GEOSGeometry *newRing = nullptr;
4365 const GEOSCoordSequence *reshapeSequence = GEOSGeom_getCoordSeq_r( context, reshapeResult.get() );
4366 GEOSCoordSequence *newCoordSequence = GEOSCoordSeq_clone_r( context, reshapeSequence );
4367
4368 reshapeResult.reset();
4369
4370 try
4371 {
4372 newRing = GEOSGeom_createLinearRing_r( context, newCoordSequence );
4373 }
4374 catch ( QgsGeosException & )
4375 {
4376 // nothing to do: on exception newRing will be null
4377 }
4378
4379 if ( !newRing )
4380 {
4381 delete[] innerRings;
4382 return nullptr;
4383 }
4384
4385 GEOSGeometry *newOuterRing = nullptr;
4386 if ( lastIntersectingRing == -1 )
4387 newOuterRing = newRing;
4388 else
4389 newOuterRing = GEOSGeom_clone_r( context, outerRing );
4390
4391 //check if all the rings are still inside the outer boundary
4392 QVector<GEOSGeometry *> ringList;
4393 if ( nRings > 0 )
4394 {
4395 GEOSGeometry *outerRingPoly = GEOSGeom_createPolygon_r( context, GEOSGeom_clone_r( context, newOuterRing ), nullptr, 0 );
4396 if ( outerRingPoly )
4397 {
4398 ringList.reserve( nRings );
4399 GEOSGeometry *currentRing = nullptr;
4400 for ( int i = 0; i < nRings; ++i )
4401 {
4402 if ( lastIntersectingRing == i )
4403 currentRing = newRing;
4404 else
4405 currentRing = GEOSGeom_clone_r( context, innerRings[i] );
4406
4407 //possibly a ring is no longer contained in the result polygon after reshape
4408 if ( GEOSContains_r( context, outerRingPoly, currentRing ) == 1 )
4409 ringList.push_back( currentRing );
4410 else
4411 GEOSGeom_destroy_r( context, currentRing );
4412 }
4413 }
4414 GEOSGeom_destroy_r( context, outerRingPoly );
4415 }
4416
4417 GEOSGeometry **newInnerRings = new GEOSGeometry *[ringList.size()];
4418 for ( int i = 0; i < ringList.size(); ++i )
4419 newInnerRings[i] = ringList.at( i );
4420
4421 delete[] innerRings;
4422
4423 geos::unique_ptr reshapedPolygon( GEOSGeom_createPolygon_r( context, newOuterRing, newInnerRings, ringList.size() ) );
4424 delete[] newInnerRings;
4425
4426 return reshapedPolygon;
4427}
4428
4429int QgsGeos::lineContainedInLine( const GEOSGeometry *line1, const GEOSGeometry *line2 )
4430{
4431 if ( !line1 || !line2 )
4432 {
4433 return -1;
4434 }
4435
4436 double bufferDistance = std::pow( 10.0L, geomDigits( line2 ) - 11 );
4437
4438 GEOSContextHandle_t context = QgsGeosContext::get();
4439 geos::unique_ptr bufferGeom( GEOSBuffer_r( context, line2, bufferDistance, DEFAULT_QUADRANT_SEGMENTS ) );
4440 if ( !bufferGeom )
4441 return -2;
4442
4443 geos::unique_ptr intersectionGeom( GEOSIntersection_r( context, bufferGeom.get(), line1 ) );
4444
4445 //compare ratio between line1Length and intersectGeomLength (usually close to 1 if line1 is contained in line2)
4446 double intersectGeomLength;
4447 double line1Length;
4448
4449 GEOSLength_r( context, intersectionGeom.get(), &intersectGeomLength );
4450 GEOSLength_r( context, line1, &line1Length );
4451
4452 double intersectRatio = line1Length / intersectGeomLength;
4453 if ( intersectRatio > 0.9 && intersectRatio < 1.1 )
4454 return 1;
4455
4456 return 0;
4457}
4458
4459int QgsGeos::pointContainedInLine( const GEOSGeometry *point, const GEOSGeometry *line )
4460{
4461 if ( !point || !line )
4462 return -1;
4463
4464 double bufferDistance = std::pow( 10.0L, geomDigits( line ) - 11 );
4465
4466 GEOSContextHandle_t context = QgsGeosContext::get();
4467 geos::unique_ptr lineBuffer( GEOSBuffer_r( context, line, bufferDistance, 8 ) );
4468 if ( !lineBuffer )
4469 return -2;
4470
4471 bool contained = false;
4472 if ( GEOSContains_r( context, lineBuffer.get(), point ) == 1 )
4473 contained = true;
4474
4475 return contained;
4476}
4477
4478int QgsGeos::geomDigits( const GEOSGeometry *geom )
4479{
4480 GEOSContextHandle_t context = QgsGeosContext::get();
4481 geos::unique_ptr bbox( GEOSEnvelope_r( context, geom ) );
4482 if ( !bbox )
4483 return -1;
4484
4485 const GEOSGeometry *bBoxRing = GEOSGetExteriorRing_r( context, bbox.get() );
4486 if ( !bBoxRing )
4487 return -1;
4488
4489 const GEOSCoordSequence *bBoxCoordSeq = GEOSGeom_getCoordSeq_r( context, bBoxRing );
4490
4491 if ( !bBoxCoordSeq )
4492 return -1;
4493
4494 unsigned int nCoords = 0;
4495 if ( !GEOSCoordSeq_getSize_r( context, bBoxCoordSeq, &nCoords ) )
4496 return -1;
4497
4498 int maxDigits = -1;
4499 for ( unsigned int i = 0; i < nCoords - 1; ++i )
4500 {
4501 double t;
4502 GEOSCoordSeq_getX_r( context, bBoxCoordSeq, i, &t );
4503
4504 int digits;
4505 digits = std::ceil( std::log10( std::fabs( t ) ) );
4506 if ( digits > maxDigits )
4507 maxDigits = digits;
4508
4509 GEOSCoordSeq_getY_r( context, bBoxCoordSeq, i, &t );
4510 digits = std::ceil( std::log10( std::fabs( t ) ) );
4511 if ( digits > maxDigits )
4512 maxDigits = digits;
4513 }
4514
4515 return maxDigits;
4516}
4517
4519#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 14 )
4520 : mFeedback( feedback )
4521{
4522 GEOSContext_setInterruptCallback_r( QgsGeosContext::get(), &callback, reinterpret_cast< void * >( mFeedback ) );
4523}
4524#else
4525{
4526 ( void ) feedback;
4527}
4528#endif
4529
4530
4532{
4533#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 14 )
4534 GEOSContext_setInterruptCallback_r( QgsGeosContext::get(), nullptr, nullptr );
4535#endif
4536}
4537
4538#if GEOS_VERSION_MAJOR > 3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 14 )
4539int QgsScopedGeosContextRegisterFeedback::callback( void *userData )
4540{
4541 if ( !userData )
4542 return 0;
4543
4544 QgsFeedback *feedback = reinterpret_cast< QgsFeedback * >( userData );
4545 return feedback && feedback->isCanceled() ? 1 : 0;
4546}
4547#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 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:49
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:3373
double minimumClearance(QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const
Computes the minimum clearance of a geometry.
Definition qgsgeos.cpp:3458
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:2578
std::unique_ptr< QgsAbstractGeometry > reshapeGeometry(const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg=nullptr) const
Reshapes the geometry using a line.
Definition qgsgeos.cpp:3532
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:3511
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:3477
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:3692
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:2698
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:2870
QgsAbstractGeometry * simplify(double tolerance, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const override
Simplifies the geometery.
Definition qgsgeos.cpp:2456
static const QgsSettingsEntryBool * settingLineToCurveParam
Settings entry - Whether to convert any linear output of a GEOS method to a curved type,...
Definition qgsgeos.h:185
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:3345
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:2717
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:3796
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:1935
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:3420
QgsAbstractGeometry * envelope(QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2516
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:2626
QgsAbstractGeometry * buffer(double distance, int segments, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const override
Buffers the geometry.
Definition qgsgeos.cpp:2413
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:3957
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:2906
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:2786
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:3441
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:2672
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:3737
QgsAbstractGeometry * convexHull(QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const override
Calculate the convex hull of this geometry.
Definition qgsgeos.cpp:2561
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:2531
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:3494
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:2849
std::unique_ptr< QgsAbstractGeometry > maximumInscribedCircle(double tolerance, QString *errorMsg=nullptr, QgsFeedback *feedback=nullptr) const
Returns the maximum inscribed circle.
Definition qgsgeos.cpp:3403
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:3899
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:3933
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:2602
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:2892
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:2488
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:3664
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:2472
static QgsPoint coordSeqPoint(const GEOSCoordSequence *cs, int i, bool hasZ, bool hasM)
Definition qgsgeos.cpp:2053
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:3858
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:781
double distance(double x, double y) const
Returns the Cartesian 2D distance between this point and a specified x, y coordinate.
Definition qgspoint.h:466
double m
Definition qgspoint.h:59
double y
Definition qgspoint.h:57
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:87
QgsScopedGeosContextRegisterFeedback(QgsFeedback *feedback)
Registers a feedback object for GEOS interruption checking.
Definition qgsgeos.cpp:4518
~QgsScopedGeosContextRegisterFeedback()
Resets the GEOS interruption checker for the current thread.
Definition qgsgeos.cpp:4531
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.
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:115
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:151
std::unique_ptr< GEOSCoordSequence, GeosDeleter > coord_sequence_unique_ptr
Scoped GEOS coordinate sequence pointer.
Definition qgsgeos.h:166
std::unique_ptr< GEOSBufferParams, GeosDeleter > buffer_params_unique_ptr
Scoped GEOS buffer params pointer.
Definition qgsgeos.h:161
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