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