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