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