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