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