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