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