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