QGIS API Documentation 3.39.0-Master (47f7b3a4989)
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 static QgsStringMap translatedErrors;
2274
2275 if ( translatedErrors.empty() )
2276 {
2277 // Copied from https://git.osgeo.org/gitea/geos/geos/src/branch/master/src/operation/valid/TopologyValidationError.cpp
2278 translatedErrors.insert( QStringLiteral( "topology validation error" ), QObject::tr( "Topology validation error", "GEOS Error" ) );
2279 translatedErrors.insert( QStringLiteral( "repeated point" ), QObject::tr( "Repeated point", "GEOS Error" ) );
2280 translatedErrors.insert( QStringLiteral( "hole lies outside shell" ), QObject::tr( "Hole lies outside shell", "GEOS Error" ) );
2281 translatedErrors.insert( QStringLiteral( "holes are nested" ), QObject::tr( "Holes are nested", "GEOS Error" ) );
2282 translatedErrors.insert( QStringLiteral( "interior is disconnected" ), QObject::tr( "Interior is disconnected", "GEOS Error" ) );
2283 translatedErrors.insert( QStringLiteral( "self-intersection" ), QObject::tr( "Self-intersection", "GEOS Error" ) );
2284 translatedErrors.insert( QStringLiteral( "ring self-intersection" ), QObject::tr( "Ring self-intersection", "GEOS Error" ) );
2285 translatedErrors.insert( QStringLiteral( "nested shells" ), QObject::tr( "Nested shells", "GEOS Error" ) );
2286 translatedErrors.insert( QStringLiteral( "duplicate rings" ), QObject::tr( "Duplicate rings", "GEOS Error" ) );
2287 translatedErrors.insert( QStringLiteral( "too few points in geometry component" ), QObject::tr( "Too few points in geometry component", "GEOS Error" ) );
2288 translatedErrors.insert( QStringLiteral( "invalid coordinate" ), QObject::tr( "Invalid coordinate", "GEOS Error" ) );
2289 translatedErrors.insert( QStringLiteral( "ring is not closed" ), QObject::tr( "Ring is not closed", "GEOS Error" ) );
2290 }
2291
2292 *errorMsg = translatedErrors.value( error.toLower(), error );
2293
2294 if ( g1 && errorLoc )
2295 {
2296 *errorLoc = geometryFromGeos( g1 );
2297 }
2298 else if ( g1 )
2299 {
2300 GEOSGeom_destroy_r( context, g1 );
2301 }
2302 }
2303 return !invalid;
2304 }
2305 CATCH_GEOS_WITH_ERRMSG( false )
2306}
2307
2308bool QgsGeos::isEqual( const QgsAbstractGeometry *geom, QString *errorMsg ) const
2309{
2310 if ( !mGeos || !geom )
2311 {
2312 return false;
2313 }
2314
2315 try
2316 {
2317 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2318 if ( !geosGeom )
2319 {
2320 return false;
2321 }
2322 bool equal = GEOSEquals_r( QgsGeosContext::get(), mGeos.get(), geosGeom.get() );
2323 return equal;
2324 }
2325 CATCH_GEOS_WITH_ERRMSG( false )
2326}
2327
2328bool QgsGeos::isEmpty( QString *errorMsg ) const
2329{
2330 if ( !mGeos )
2331 {
2332 return false;
2333 }
2334
2335 try
2336 {
2337 return GEOSisEmpty_r( QgsGeosContext::get(), mGeos.get() );
2338 }
2339 CATCH_GEOS_WITH_ERRMSG( false )
2340}
2341
2342bool QgsGeos::isSimple( QString *errorMsg ) const
2343{
2344 if ( !mGeos )
2345 {
2346 return false;
2347 }
2348
2349 try
2350 {
2351 return GEOSisSimple_r( QgsGeosContext::get(), mGeos.get() );
2352 }
2353 CATCH_GEOS_WITH_ERRMSG( false )
2354}
2355
2356GEOSCoordSequence *QgsGeos::createCoordinateSequence( const QgsCurve *curve, double precision, bool forceClose )
2357{
2358 GEOSContextHandle_t context = QgsGeosContext::get();
2359
2360 std::unique_ptr< QgsLineString > segmentized;
2361 const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( curve );
2362
2363 if ( !line )
2364 {
2365 segmentized.reset( curve->curveToLine() );
2366 line = segmentized.get();
2367 }
2368
2369 if ( !line )
2370 {
2371 return nullptr;
2372 }
2373 GEOSCoordSequence *coordSeq = nullptr;
2374
2375 const int numPoints = line->numPoints();
2376
2377 const bool hasZ = line->is3D();
2378
2379#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
2380 if ( qgsDoubleNear( precision, 0 ) )
2381 {
2382 if ( !forceClose || ( line->pointN( 0 ) == line->pointN( numPoints - 1 ) ) )
2383 {
2384 // use optimised method if we don't have to force close an open ring
2385 try
2386 {
2387 coordSeq = GEOSCoordSeq_copyFromArrays_r( context, line->xData(), line->yData(), line->zData(), nullptr, numPoints );
2388 if ( !coordSeq )
2389 {
2390 QgsDebugError( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for %1 points" ).arg( numPoints ) );
2391 return nullptr;
2392 }
2393 }
2394 CATCH_GEOS( nullptr )
2395 }
2396 else
2397 {
2398 QVector< double > x = line->xVector();
2399 if ( numPoints > 0 )
2400 x.append( x.at( 0 ) );
2401 QVector< double > y = line->yVector();
2402 if ( numPoints > 0 )
2403 y.append( y.at( 0 ) );
2404 QVector< double > z = line->zVector();
2405 if ( hasZ && numPoints > 0 )
2406 z.append( z.at( 0 ) );
2407 try
2408 {
2409 coordSeq = GEOSCoordSeq_copyFromArrays_r( context, x.constData(), y.constData(), !hasZ ? nullptr : z.constData(), nullptr, numPoints + 1 );
2410 if ( !coordSeq )
2411 {
2412 QgsDebugError( QStringLiteral( "GEOS Exception: Could not create closed coordinate sequence for %1 points" ).arg( numPoints + 1 ) );
2413 return nullptr;
2414 }
2415 }
2416 CATCH_GEOS( nullptr )
2417 }
2418 return coordSeq;
2419 }
2420#endif
2421
2422 int coordDims = 2;
2423 const bool hasM = false; //line->isMeasure(); //disabled until geos supports m-coordinates
2424
2425 if ( hasZ )
2426 {
2427 ++coordDims;
2428 }
2429 if ( hasM )
2430 {
2431 ++coordDims;
2432 }
2433
2434 int numOutPoints = numPoints;
2435 if ( forceClose && ( line->pointN( 0 ) != line->pointN( numPoints - 1 ) ) )
2436 {
2437 ++numOutPoints;
2438 }
2439
2440 try
2441 {
2442 coordSeq = GEOSCoordSeq_create_r( context, numOutPoints, coordDims );
2443 if ( !coordSeq )
2444 {
2445 QgsDebugError( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for %1 points in %2 dimensions" ).arg( numPoints ).arg( coordDims ) );
2446 return nullptr;
2447 }
2448
2449 const double *xData = line->xData();
2450 const double *yData = line->yData();
2451 const double *zData = hasZ ? line->zData() : nullptr;
2452 const double *mData = hasM ? line->mData() : nullptr;
2453
2454 if ( precision > 0. )
2455 {
2456 for ( int i = 0; i < numOutPoints; ++i )
2457 {
2458 if ( i >= numPoints )
2459 {
2460 // start reading back from start of line
2461 xData = line->xData();
2462 yData = line->yData();
2463 zData = hasZ ? line->zData() : nullptr;
2464 mData = hasM ? line->mData() : nullptr;
2465 }
2466 if ( hasZ )
2467 {
2468 GEOSCoordSeq_setXYZ_r( context, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision, std::round( *zData++ / precision ) * precision );
2469 }
2470 else
2471 {
2472 GEOSCoordSeq_setXY_r( context, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision );
2473 }
2474 if ( hasM )
2475 {
2476 GEOSCoordSeq_setOrdinate_r( context, coordSeq, i, 3, *mData++ );
2477 }
2478 }
2479 }
2480 else
2481 {
2482 for ( int i = 0; i < numOutPoints; ++i )
2483 {
2484 if ( i >= numPoints )
2485 {
2486 // start reading back from start of line
2487 xData = line->xData();
2488 yData = line->yData();
2489 zData = hasZ ? line->zData() : nullptr;
2490 mData = hasM ? line->mData() : nullptr;
2491 }
2492 if ( hasZ )
2493 {
2494 GEOSCoordSeq_setXYZ_r( context, coordSeq, i, *xData++, *yData++, *zData++ );
2495 }
2496 else
2497 {
2498 GEOSCoordSeq_setXY_r( context, coordSeq, i, *xData++, *yData++ );
2499 }
2500 if ( hasM )
2501 {
2502 GEOSCoordSeq_setOrdinate_r( context, coordSeq, i, 3, *mData++ );
2503 }
2504 }
2505 }
2506 }
2507 CATCH_GEOS( nullptr )
2508
2509 return coordSeq;
2510}
2511
2512geos::unique_ptr QgsGeos::createGeosPoint( const QgsAbstractGeometry *point, int coordDims, double precision )
2513{
2514 const QgsPoint *pt = qgsgeometry_cast<const QgsPoint *>( point );
2515 if ( !pt )
2516 return nullptr;
2517
2518 return createGeosPointXY( pt->x(), pt->y(), pt->is3D(), pt->z(), pt->isMeasure(), pt->m(), coordDims, precision );
2519}
2520
2521geos::unique_ptr QgsGeos::createGeosPointXY( double x, double y, bool hasZ, double z, bool hasM, double m, int coordDims, double precision )
2522{
2523 Q_UNUSED( hasM )
2524 Q_UNUSED( m )
2525
2526 geos::unique_ptr geosPoint;
2527 GEOSContextHandle_t context = QgsGeosContext::get();
2528 try
2529 {
2530 if ( coordDims == 2 )
2531 {
2532 // optimised constructor
2533 if ( precision > 0. )
2534 geosPoint.reset( GEOSGeom_createPointFromXY_r( context, std::round( x / precision ) * precision, std::round( y / precision ) * precision ) );
2535 else
2536 geosPoint.reset( GEOSGeom_createPointFromXY_r( context, x, y ) );
2537 return geosPoint;
2538 }
2539
2540 GEOSCoordSequence *coordSeq = GEOSCoordSeq_create_r( context, 1, coordDims );
2541 if ( !coordSeq )
2542 {
2543 QgsDebugError( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for point with %1 dimensions" ).arg( coordDims ) );
2544 return nullptr;
2545 }
2546 if ( precision > 0. )
2547 {
2548 GEOSCoordSeq_setX_r( context, coordSeq, 0, std::round( x / precision ) * precision );
2549 GEOSCoordSeq_setY_r( context, coordSeq, 0, std::round( y / precision ) * precision );
2550 if ( hasZ )
2551 {
2552 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 2, std::round( z / precision ) * precision );
2553 }
2554 }
2555 else
2556 {
2557 GEOSCoordSeq_setX_r( context, coordSeq, 0, x );
2558 GEOSCoordSeq_setY_r( context, coordSeq, 0, y );
2559 if ( hasZ )
2560 {
2561 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 2, z );
2562 }
2563 }
2564#if 0 //disabled until geos supports m-coordinates
2565 if ( hasM )
2566 {
2567 GEOSCoordSeq_setOrdinate_r( context, coordSeq, 0, 3, m );
2568 }
2569#endif
2570 geosPoint.reset( GEOSGeom_createPoint_r( context, coordSeq ) );
2571 }
2572 CATCH_GEOS( nullptr )
2573 return geosPoint;
2574}
2575
2576geos::unique_ptr QgsGeos::createGeosLinestring( const QgsAbstractGeometry *curve, double precision )
2577{
2578 const QgsCurve *c = qgsgeometry_cast<const QgsCurve *>( curve );
2579 if ( !c )
2580 return nullptr;
2581
2582 GEOSCoordSequence *coordSeq = createCoordinateSequence( c, precision );
2583 if ( !coordSeq )
2584 return nullptr;
2585
2586 geos::unique_ptr geosGeom;
2587 try
2588 {
2589 geosGeom.reset( GEOSGeom_createLineString_r( QgsGeosContext::get(), coordSeq ) );
2590 }
2591 CATCH_GEOS( nullptr )
2592 return geosGeom;
2593}
2594
2595geos::unique_ptr QgsGeos::createGeosPolygon( const QgsAbstractGeometry *poly, double precision )
2596{
2597 const QgsCurvePolygon *polygon = qgsgeometry_cast<const QgsCurvePolygon *>( poly );
2598 if ( !polygon )
2599 return nullptr;
2600
2601 const QgsCurve *exteriorRing = polygon->exteriorRing();
2602 if ( !exteriorRing )
2603 {
2604 return nullptr;
2605 }
2606
2607 GEOSContextHandle_t context = QgsGeosContext::get();
2608 geos::unique_ptr geosPolygon;
2609 try
2610 {
2611 geos::unique_ptr exteriorRingGeos( GEOSGeom_createLinearRing_r( context, createCoordinateSequence( exteriorRing, precision, true ) ) );
2612
2613 int nHoles = polygon->numInteriorRings();
2614 GEOSGeometry **holes = nullptr;
2615 if ( nHoles > 0 )
2616 {
2617 holes = new GEOSGeometry*[ nHoles ];
2618 }
2619
2620 for ( int i = 0; i < nHoles; ++i )
2621 {
2622 const QgsCurve *interiorRing = polygon->interiorRing( i );
2623 holes[i] = GEOSGeom_createLinearRing_r( context, createCoordinateSequence( interiorRing, precision, true ) );
2624 }
2625 geosPolygon.reset( GEOSGeom_createPolygon_r( context, exteriorRingGeos.release(), holes, nHoles ) );
2626 delete[] holes;
2627 }
2628 CATCH_GEOS( nullptr )
2629
2630 return geosPolygon;
2631}
2632
2633QgsAbstractGeometry *QgsGeos::offsetCurve( double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg ) const
2634{
2635 if ( !mGeos )
2636 return nullptr;
2637
2638 geos::unique_ptr offset;
2639 try
2640 {
2641 // Force quadrant segments to be at least 8, see
2642 // https://github.com/qgis/QGIS/issues/53165#issuecomment-1563470832
2643 if ( segments < 8 )
2644 segments = 8;
2645 offset.reset( GEOSOffsetCurve_r( QgsGeosContext::get(), mGeos.get(), distance, segments, static_cast< int >( joinStyle ), miterLimit ) );
2646 }
2647 CATCH_GEOS_WITH_ERRMSG( nullptr )
2648 std::unique_ptr< QgsAbstractGeometry > offsetGeom = fromGeos( offset.get() );
2649 return offsetGeom.release();
2650}
2651
2652std::unique_ptr<QgsAbstractGeometry> QgsGeos::singleSidedBuffer( double distance, int segments, Qgis::BufferSide side, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg ) const
2653{
2654 if ( !mGeos )
2655 {
2656 return nullptr;
2657 }
2658
2659 geos::unique_ptr geos;
2660 GEOSContextHandle_t context = QgsGeosContext::get();
2661 try
2662 {
2663 geos::buffer_params_unique_ptr bp( GEOSBufferParams_create_r( context ) );
2664 GEOSBufferParams_setSingleSided_r( context, bp.get(), 1 );
2665 GEOSBufferParams_setQuadrantSegments_r( context, bp.get(), segments );
2666 GEOSBufferParams_setJoinStyle_r( context, bp.get(), static_cast< int >( joinStyle ) );
2667 GEOSBufferParams_setMitreLimit_r( context, bp.get(), miterLimit ); //#spellok
2668
2669 if ( side == Qgis::BufferSide::Right )
2670 {
2671 distance = -distance;
2672 }
2673 geos.reset( GEOSBufferWithParams_r( context, mGeos.get(), bp.get(), distance ) );
2674 }
2675 CATCH_GEOS_WITH_ERRMSG( nullptr )
2676 return fromGeos( geos.get() );
2677}
2678
2679std::unique_ptr<QgsAbstractGeometry> QgsGeos::maximumInscribedCircle( double tolerance, QString *errorMsg ) const
2680{
2681 if ( !mGeos )
2682 {
2683 return nullptr;
2684 }
2685
2686 geos::unique_ptr geos;
2687 try
2688 {
2689 geos.reset( GEOSMaximumInscribedCircle_r( QgsGeosContext::get(), mGeos.get(), tolerance ) );
2690 }
2691 CATCH_GEOS_WITH_ERRMSG( nullptr )
2692 return fromGeos( geos.get() );
2693}
2694
2695std::unique_ptr<QgsAbstractGeometry> QgsGeos::largestEmptyCircle( double tolerance, const QgsAbstractGeometry *boundary, QString *errorMsg ) const
2696{
2697 if ( !mGeos )
2698 {
2699 return nullptr;
2700 }
2701
2702 geos::unique_ptr geos;
2703 try
2704 {
2705 geos::unique_ptr boundaryGeos;
2706 if ( boundary )
2707 boundaryGeos = asGeos( boundary );
2708
2709 geos.reset( GEOSLargestEmptyCircle_r( QgsGeosContext::get(), mGeos.get(), boundaryGeos.get(), tolerance ) );
2710 }
2711 CATCH_GEOS_WITH_ERRMSG( nullptr )
2712 return fromGeos( geos.get() );
2713}
2714
2715std::unique_ptr<QgsAbstractGeometry> QgsGeos::minimumWidth( QString *errorMsg ) const
2716{
2717 if ( !mGeos )
2718 {
2719 return nullptr;
2720 }
2721
2722 geos::unique_ptr geos;
2723 try
2724 {
2725 geos.reset( GEOSMinimumWidth_r( QgsGeosContext::get(), mGeos.get() ) );
2726 }
2727 CATCH_GEOS_WITH_ERRMSG( nullptr )
2728 return fromGeos( geos.get() );
2729}
2730
2731double QgsGeos::minimumClearance( QString *errorMsg ) const
2732{
2733 if ( !mGeos )
2734 {
2735 return std::numeric_limits< double >::quiet_NaN();
2736 }
2737
2738 geos::unique_ptr geos;
2739 double res = 0;
2740 try
2741 {
2742 if ( GEOSMinimumClearance_r( QgsGeosContext::get(), mGeos.get(), &res ) != 0 )
2743 return std::numeric_limits< double >::quiet_NaN();
2744 }
2745 CATCH_GEOS_WITH_ERRMSG( std::numeric_limits< double >::quiet_NaN() )
2746 return res;
2747}
2748
2749std::unique_ptr<QgsAbstractGeometry> QgsGeos::minimumClearanceLine( QString *errorMsg ) const
2750{
2751 if ( !mGeos )
2752 {
2753 return nullptr;
2754 }
2755
2756 geos::unique_ptr geos;
2757 try
2758 {
2759 geos.reset( GEOSMinimumClearanceLine_r( QgsGeosContext::get(), mGeos.get() ) );
2760 }
2761 CATCH_GEOS_WITH_ERRMSG( nullptr )
2762 return fromGeos( geos.get() );
2763}
2764
2765std::unique_ptr<QgsAbstractGeometry> QgsGeos::node( QString *errorMsg ) const
2766{
2767 if ( !mGeos )
2768 {
2769 return nullptr;
2770 }
2771
2772 geos::unique_ptr geos;
2773 try
2774 {
2775 geos.reset( GEOSNode_r( QgsGeosContext::get(), mGeos.get() ) );
2776 }
2777 CATCH_GEOS_WITH_ERRMSG( nullptr )
2778 return fromGeos( geos.get() );
2779}
2780
2781std::unique_ptr<QgsAbstractGeometry> QgsGeos::sharedPaths( const QgsAbstractGeometry *other, QString *errorMsg ) const
2782{
2783 if ( !mGeos || !other )
2784 {
2785 return nullptr;
2786 }
2787
2788 geos::unique_ptr geos;
2789 try
2790 {
2791 geos::unique_ptr otherGeos = asGeos( other );
2792 if ( !otherGeos )
2793 return nullptr;
2794
2795 geos.reset( GEOSSharedPaths_r( QgsGeosContext::get(), mGeos.get(), otherGeos.get() ) );
2796 }
2797 CATCH_GEOS_WITH_ERRMSG( nullptr )
2798 return fromGeos( geos.get() );
2799}
2800
2801std::unique_ptr<QgsAbstractGeometry> QgsGeos::reshapeGeometry( const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg ) const
2802{
2803 if ( !mGeos || mGeometry->dimension() == 0 )
2804 {
2805 if ( errorCode ) { *errorCode = InvalidBaseGeometry; }
2806 return nullptr;
2807 }
2808
2809 if ( reshapeWithLine.numPoints() < 2 )
2810 {
2811 if ( errorCode ) { *errorCode = InvalidInput; }
2812 return nullptr;
2813 }
2814
2815 geos::unique_ptr reshapeLineGeos = createGeosLinestring( &reshapeWithLine, mPrecision );
2816
2817 GEOSContextHandle_t context = QgsGeosContext::get();
2818 //single or multi?
2819 int numGeoms = GEOSGetNumGeometries_r( context, mGeos.get() );
2820 if ( numGeoms == -1 )
2821 {
2822 if ( errorCode )
2823 {
2824 *errorCode = InvalidBaseGeometry;
2825 }
2826 return nullptr;
2827 }
2828
2829 bool isMultiGeom = false;
2830 int geosTypeId = GEOSGeomTypeId_r( context, mGeos.get() );
2831 if ( geosTypeId == GEOS_MULTILINESTRING || geosTypeId == GEOS_MULTIPOLYGON )
2832 isMultiGeom = true;
2833
2834 bool isLine = ( mGeometry->dimension() == 1 );
2835
2836 if ( !isMultiGeom )
2837 {
2838 geos::unique_ptr reshapedGeometry;
2839 if ( isLine )
2840 {
2841 reshapedGeometry = reshapeLine( mGeos.get(), reshapeLineGeos.get(), mPrecision );
2842 }
2843 else
2844 {
2845 reshapedGeometry = reshapePolygon( mGeos.get(), reshapeLineGeos.get(), mPrecision );
2846 }
2847
2848 if ( errorCode )
2849 *errorCode = Success;
2850 std::unique_ptr< QgsAbstractGeometry > reshapeResult = fromGeos( reshapedGeometry.get() );
2851 return reshapeResult;
2852 }
2853 else
2854 {
2855 try
2856 {
2857 //call reshape for each geometry part and replace mGeos with new geometry if reshape took place
2858 bool reshapeTookPlace = false;
2859
2860 geos::unique_ptr currentReshapeGeometry;
2861 GEOSGeometry **newGeoms = new GEOSGeometry*[numGeoms];
2862
2863 for ( int i = 0; i < numGeoms; ++i )
2864 {
2865 if ( isLine )
2866 currentReshapeGeometry = reshapeLine( GEOSGetGeometryN_r( context, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
2867 else
2868 currentReshapeGeometry = reshapePolygon( GEOSGetGeometryN_r( context, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
2869
2870 if ( currentReshapeGeometry )
2871 {
2872 newGeoms[i] = currentReshapeGeometry.release();
2873 reshapeTookPlace = true;
2874 }
2875 else
2876 {
2877 newGeoms[i] = GEOSGeom_clone_r( context, GEOSGetGeometryN_r( context, mGeos.get(), i ) );
2878 }
2879 }
2880
2881 geos::unique_ptr newMultiGeom;
2882 if ( isLine )
2883 {
2884 newMultiGeom.reset( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, newGeoms, numGeoms ) );
2885 }
2886 else //multipolygon
2887 {
2888 newMultiGeom.reset( GEOSGeom_createCollection_r( context, GEOS_MULTIPOLYGON, newGeoms, numGeoms ) );
2889 }
2890
2891 delete[] newGeoms;
2892 if ( !newMultiGeom )
2893 {
2894 if ( errorCode ) { *errorCode = EngineError; }
2895 return nullptr;
2896 }
2897
2898 if ( reshapeTookPlace )
2899 {
2900 if ( errorCode )
2901 *errorCode = Success;
2902 std::unique_ptr< QgsAbstractGeometry > reshapedMultiGeom = fromGeos( newMultiGeom.get() );
2903 return reshapedMultiGeom;
2904 }
2905 else
2906 {
2907 if ( errorCode )
2908 {
2909 *errorCode = NothingHappened;
2910 }
2911 return nullptr;
2912 }
2913 }
2914 CATCH_GEOS_WITH_ERRMSG( nullptr )
2915 }
2916}
2917
2918QgsGeometry QgsGeos::mergeLines( QString *errorMsg ) const
2919{
2920 if ( !mGeos )
2921 {
2922 return QgsGeometry();
2923 }
2924
2925 GEOSContextHandle_t context = QgsGeosContext::get();
2926 if ( GEOSGeomTypeId_r( context, mGeos.get() ) != GEOS_MULTILINESTRING )
2927 return QgsGeometry();
2928
2929 geos::unique_ptr geos;
2930 try
2931 {
2932 geos.reset( GEOSLineMerge_r( context, mGeos.get() ) );
2933 }
2935 return QgsGeometry( fromGeos( geos.get() ) );
2936}
2937
2938QgsGeometry QgsGeos::closestPoint( const QgsGeometry &other, QString *errorMsg ) const
2939{
2940 if ( !mGeos || isEmpty() || other.isEmpty() )
2941 {
2942 return QgsGeometry();
2943 }
2944
2945 geos::unique_ptr otherGeom( asGeos( other.constGet(), mPrecision ) );
2946 if ( !otherGeom )
2947 {
2948 return QgsGeometry();
2949 }
2950
2951 GEOSContextHandle_t context = QgsGeosContext::get();
2952 double nx = 0.0;
2953 double ny = 0.0;
2954 try
2955 {
2956 geos::coord_sequence_unique_ptr nearestCoord;
2957 if ( mGeosPrepared ) // use faster version with prepared geometry
2958 {
2959 nearestCoord.reset( GEOSPreparedNearestPoints_r( context, mGeosPrepared.get(), otherGeom.get() ) );
2960 }
2961 else
2962 {
2963 nearestCoord.reset( GEOSNearestPoints_r( context, mGeos.get(), otherGeom.get() ) );
2964 }
2965
2966 ( void )GEOSCoordSeq_getX_r( context, nearestCoord.get(), 0, &nx );
2967 ( void )GEOSCoordSeq_getY_r( context, nearestCoord.get(), 0, &ny );
2968 }
2969 catch ( GEOSException &e )
2970 {
2971 logError( QStringLiteral( "GEOS" ), e.what() );
2972 if ( errorMsg )
2973 {
2974 *errorMsg = e.what();
2975 }
2976 return QgsGeometry();
2977 }
2978
2979 return QgsGeometry( new QgsPoint( nx, ny ) );
2980}
2981
2982QgsGeometry QgsGeos::shortestLine( const QgsGeometry &other, QString *errorMsg ) const
2983{
2984 if ( !mGeos || other.isEmpty() )
2985 {
2986 return QgsGeometry();
2987 }
2988
2989 return shortestLine( other.constGet(), errorMsg );
2990}
2991
2992QgsGeometry QgsGeos::shortestLine( const QgsAbstractGeometry *other, QString *errorMsg ) const
2993{
2994 if ( !other || other->isEmpty() )
2995 return QgsGeometry();
2996
2997 geos::unique_ptr otherGeom( asGeos( other, mPrecision ) );
2998 if ( !otherGeom )
2999 {
3000 return QgsGeometry();
3001 }
3002
3003 GEOSContextHandle_t context = QgsGeosContext::get();
3004 double nx1 = 0.0;
3005 double ny1 = 0.0;
3006 double nx2 = 0.0;
3007 double ny2 = 0.0;
3008 try
3009 {
3010 geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( context, mGeos.get(), otherGeom.get() ) );
3011
3012 if ( !nearestCoord )
3013 {
3014 if ( errorMsg )
3015 *errorMsg = QStringLiteral( "GEOS returned no nearest points" );
3016 return QgsGeometry();
3017 }
3018
3019 ( void )GEOSCoordSeq_getX_r( context, nearestCoord.get(), 0, &nx1 );
3020 ( void )GEOSCoordSeq_getY_r( context, nearestCoord.get(), 0, &ny1 );
3021 ( void )GEOSCoordSeq_getX_r( context, nearestCoord.get(), 1, &nx2 );
3022 ( void )GEOSCoordSeq_getY_r( context, nearestCoord.get(), 1, &ny2 );
3023 }
3024 catch ( GEOSException &e )
3025 {
3026 logError( QStringLiteral( "GEOS" ), e.what() );
3027 if ( errorMsg )
3028 {
3029 *errorMsg = e.what();
3030 }
3031 return QgsGeometry();
3032 }
3033
3034 QgsLineString *line = new QgsLineString();
3035 line->addVertex( QgsPoint( nx1, ny1 ) );
3036 line->addVertex( QgsPoint( nx2, ny2 ) );
3037 return QgsGeometry( line );
3038}
3039
3040double QgsGeos::lineLocatePoint( const QgsPoint &point, QString *errorMsg ) const
3041{
3042 if ( !mGeos )
3043 {
3044 return -1;
3045 }
3046
3047 geos::unique_ptr otherGeom( asGeos( &point, mPrecision ) );
3048 if ( !otherGeom )
3049 {
3050 return -1;
3051 }
3052
3053 double distance = -1;
3054 try
3055 {
3056 distance = GEOSProject_r( QgsGeosContext::get(), mGeos.get(), otherGeom.get() );
3057 }
3058 catch ( GEOSException &e )
3059 {
3060 logError( QStringLiteral( "GEOS" ), e.what() );
3061 if ( errorMsg )
3062 {
3063 *errorMsg = e.what();
3064 }
3065 return -1;
3066 }
3067
3068 return distance;
3069}
3070
3071double QgsGeos::lineLocatePoint( double x, double y, QString *errorMsg ) const
3072{
3073 if ( !mGeos )
3074 {
3075 return -1;
3076 }
3077
3078 geos::unique_ptr point = createGeosPointXY( x, y, false, 0, false, 0, 2, 0 );
3079 if ( !point )
3080 return false;
3081
3082 double distance = -1;
3083 try
3084 {
3085 distance = GEOSProject_r( QgsGeosContext::get(), mGeos.get(), point.get() );
3086 }
3087 catch ( GEOSException &e )
3088 {
3089 logError( QStringLiteral( "GEOS" ), e.what() );
3090 if ( errorMsg )
3091 {
3092 *errorMsg = e.what();
3093 }
3094 return -1;
3095 }
3096
3097 return distance;
3098}
3099
3100QgsGeometry QgsGeos::polygonize( const QVector<const QgsAbstractGeometry *> &geometries, QString *errorMsg )
3101{
3102 GEOSGeometry **const lineGeosGeometries = new GEOSGeometry*[ geometries.size()];
3103 int validLines = 0;
3104 for ( const QgsAbstractGeometry *g : geometries )
3105 {
3106 geos::unique_ptr l = asGeos( g );
3107 if ( l )
3108 {
3109 lineGeosGeometries[validLines] = l.release();
3110 validLines++;
3111 }
3112 }
3113
3114 GEOSContextHandle_t context = QgsGeosContext::get();
3115 try
3116 {
3117 geos::unique_ptr result( GEOSPolygonize_r( context, lineGeosGeometries, validLines ) );
3118 for ( int i = 0; i < validLines; ++i )
3119 {
3120 GEOSGeom_destroy_r( context, lineGeosGeometries[i] );
3121 }
3122 delete[] lineGeosGeometries;
3123 return QgsGeometry( fromGeos( result.get() ) );
3124 }
3125 catch ( GEOSException &e )
3126 {
3127 if ( errorMsg )
3128 {
3129 *errorMsg = e.what();
3130 }
3131 for ( int i = 0; i < validLines; ++i )
3132 {
3133 GEOSGeom_destroy_r( context, lineGeosGeometries[i] );
3134 }
3135 delete[] lineGeosGeometries;
3136 return QgsGeometry();
3137 }
3138}
3139
3140QgsGeometry QgsGeos::voronoiDiagram( const QgsAbstractGeometry *extent, double tolerance, bool edgesOnly, QString *errorMsg ) const
3141{
3142 if ( !mGeos )
3143 {
3144 return QgsGeometry();
3145 }
3146
3147 geos::unique_ptr extentGeosGeom;
3148 if ( extent )
3149 {
3150 extentGeosGeom = asGeos( extent, mPrecision );
3151 if ( !extentGeosGeom )
3152 {
3153 return QgsGeometry();
3154 }
3155 }
3156
3157 geos::unique_ptr geos;
3158 GEOSContextHandle_t context = QgsGeosContext::get();
3159 try
3160 {
3161 geos.reset( GEOSVoronoiDiagram_r( context, mGeos.get(), extentGeosGeom.get(), tolerance, edgesOnly ) );
3162
3163 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3164 {
3165 return QgsGeometry();
3166 }
3167
3168 return QgsGeometry( fromGeos( geos.get() ) );
3169 }
3171}
3172
3173QgsGeometry QgsGeos::delaunayTriangulation( double tolerance, bool edgesOnly, QString *errorMsg ) const
3174{
3175 if ( !mGeos )
3176 {
3177 return QgsGeometry();
3178 }
3179
3180 GEOSContextHandle_t context = QgsGeosContext::get();
3181 geos::unique_ptr geos;
3182 try
3183 {
3184 geos.reset( GEOSDelaunayTriangulation_r( context, mGeos.get(), tolerance, edgesOnly ) );
3185
3186 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3187 {
3188 return QgsGeometry();
3189 }
3190
3191 return QgsGeometry( fromGeos( geos.get() ) );
3192 }
3194}
3195
3196std::unique_ptr<QgsAbstractGeometry> QgsGeos::constrainedDelaunayTriangulation( QString *errorMsg ) const
3197{
3198#if GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR<11
3199 ( void )errorMsg;
3200 throw QgsNotSupportedException( QObject::tr( "Calculating constrainedDelaunayTriangulation requires a QGIS build based on GEOS 3.11 or later" ) );
3201#else
3202 if ( !mGeos )
3203 {
3204 return nullptr;
3205 }
3206
3207 geos::unique_ptr geos;
3208 GEOSContextHandle_t context = QgsGeosContext::get();
3209 try
3210 {
3211 geos.reset( GEOSConstrainedDelaunayTriangulation_r( context, mGeos.get() ) );
3212
3213 if ( !geos || GEOSisEmpty_r( context, geos.get() ) != 0 )
3214 {
3215 return nullptr;
3216 }
3217
3218 std::unique_ptr< QgsAbstractGeometry > res = fromGeos( geos.get() );
3219 if ( const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( res.get() ) )
3220 {
3221 return std::unique_ptr< QgsAbstractGeometry >( collection->extractPartsByType( Qgis::WkbType::Polygon, true ) );
3222 }
3223 else
3224 {
3225 return res;
3226 }
3227 }
3228 CATCH_GEOS_WITH_ERRMSG( nullptr )
3229#endif
3230}
3231
3233static bool _linestringEndpoints( const GEOSGeometry *linestring, double &x1, double &y1, double &x2, double &y2 )
3234{
3235 GEOSContextHandle_t context = QgsGeosContext::get();
3236 const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( context, linestring );
3237 if ( !coordSeq )
3238 return false;
3239
3240 unsigned int coordSeqSize;
3241 if ( GEOSCoordSeq_getSize_r( context, coordSeq, &coordSeqSize ) == 0 )
3242 return false;
3243
3244 if ( coordSeqSize < 2 )
3245 return false;
3246
3247 GEOSCoordSeq_getX_r( context, coordSeq, 0, &x1 );
3248 GEOSCoordSeq_getY_r( context, coordSeq, 0, &y1 );
3249 GEOSCoordSeq_getX_r( context, coordSeq, coordSeqSize - 1, &x2 );
3250 GEOSCoordSeq_getY_r( context, coordSeq, coordSeqSize - 1, &y2 );
3251 return true;
3252}
3253
3254
3256static geos::unique_ptr _mergeLinestrings( const GEOSGeometry *line1, const GEOSGeometry *line2, const QgsPointXY &intersectionPoint )
3257{
3258 double x1, y1, x2, y2;
3259 if ( !_linestringEndpoints( line1, x1, y1, x2, y2 ) )
3260 return nullptr;
3261
3262 double rx1, ry1, rx2, ry2;
3263 if ( !_linestringEndpoints( line2, rx1, ry1, rx2, ry2 ) )
3264 return nullptr;
3265
3266 bool intersectionAtOrigLineEndpoint =
3267 ( intersectionPoint.x() == x1 && intersectionPoint.y() == y1 ) !=
3268 ( intersectionPoint.x() == x2 && intersectionPoint.y() == y2 );
3269 bool intersectionAtReshapeLineEndpoint =
3270 ( intersectionPoint.x() == rx1 && intersectionPoint.y() == ry1 ) ||
3271 ( intersectionPoint.x() == rx2 && intersectionPoint.y() == ry2 );
3272
3273 GEOSContextHandle_t context = QgsGeosContext::get();
3274 // the intersection must be at the begin/end of both lines
3275 if ( intersectionAtOrigLineEndpoint && intersectionAtReshapeLineEndpoint )
3276 {
3277 geos::unique_ptr g1( GEOSGeom_clone_r( context, line1 ) );
3278 geos::unique_ptr g2( GEOSGeom_clone_r( context, line2 ) );
3279 GEOSGeometry *geoms[2] = { g1.release(), g2.release() };
3280 geos::unique_ptr multiGeom( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, geoms, 2 ) );
3281 geos::unique_ptr res( GEOSLineMerge_r( context, multiGeom.get() ) );
3282 return res;
3283 }
3284 else
3285 return nullptr;
3286}
3287
3288
3289geos::unique_ptr QgsGeos::reshapeLine( const GEOSGeometry *line, const GEOSGeometry *reshapeLineGeos, double precision )
3290{
3291 if ( !line || !reshapeLineGeos )
3292 return nullptr;
3293
3294 bool atLeastTwoIntersections = false;
3295 bool oneIntersection = false;
3296 QgsPointXY oneIntersectionPoint;
3297
3298 GEOSContextHandle_t context = QgsGeosContext::get();
3299 try
3300 {
3301 //make sure there are at least two intersection between line and reshape geometry
3302 geos::unique_ptr intersectGeom( GEOSIntersection_r( context, line, reshapeLineGeos ) );
3303 if ( intersectGeom )
3304 {
3305 const int geomType = GEOSGeomTypeId_r( context, intersectGeom.get() );
3306 atLeastTwoIntersections = ( geomType == GEOS_MULTIPOINT && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 1 )
3307 || ( geomType == GEOS_GEOMETRYCOLLECTION && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 0 ) // a collection implies at least two points!
3308 || ( geomType == GEOS_MULTILINESTRING && GEOSGetNumGeometries_r( context, intersectGeom.get() ) > 0 );
3309 // one point is enough when extending line at its endpoint
3310 if ( GEOSGeomTypeId_r( context, intersectGeom.get() ) == GEOS_POINT )
3311 {
3312 const GEOSCoordSequence *intersectionCoordSeq = GEOSGeom_getCoordSeq_r( context, intersectGeom.get() );
3313 double xi, yi;
3314 GEOSCoordSeq_getX_r( context, intersectionCoordSeq, 0, &xi );
3315 GEOSCoordSeq_getY_r( context, intersectionCoordSeq, 0, &yi );
3316 oneIntersection = true;
3317 oneIntersectionPoint = QgsPointXY( xi, yi );
3318 }
3319 }
3320 }
3321 catch ( GEOSException & )
3322 {
3323 atLeastTwoIntersections = false;
3324 }
3325
3326 // special case when extending line at its endpoint
3327 if ( oneIntersection )
3328 return _mergeLinestrings( line, reshapeLineGeos, oneIntersectionPoint );
3329
3330 if ( !atLeastTwoIntersections )
3331 return nullptr;
3332
3333 //begin and end point of original line
3334 double x1, y1, x2, y2;
3335 if ( !_linestringEndpoints( line, x1, y1, x2, y2 ) )
3336 return nullptr;
3337
3338 geos::unique_ptr beginLineVertex = createGeosPointXY( x1, y1, false, 0, false, 0, 2, precision );
3339 geos::unique_ptr endLineVertex = createGeosPointXY( x2, y2, false, 0, false, 0, 2, precision );
3340
3341 bool isRing = false;
3342 if ( GEOSGeomTypeId_r( context, line ) == GEOS_LINEARRING
3343 || GEOSEquals_r( context, beginLineVertex.get(), endLineVertex.get() ) == 1 )
3344 isRing = true;
3345
3346 //node line and reshape line
3347 geos::unique_ptr nodedGeometry = nodeGeometries( reshapeLineGeos, line );
3348 if ( !nodedGeometry )
3349 {
3350 return nullptr;
3351 }
3352
3353 //and merge them together
3354 geos::unique_ptr mergedLines( GEOSLineMerge_r( context, nodedGeometry.get() ) );
3355 if ( !mergedLines )
3356 {
3357 return nullptr;
3358 }
3359
3360 int numMergedLines = GEOSGetNumGeometries_r( context, mergedLines.get() );
3361 if ( numMergedLines < 2 ) //some special cases. Normally it is >2
3362 {
3363 if ( numMergedLines == 1 ) //reshape line is from begin to endpoint. So we keep the reshapeline
3364 {
3365 geos::unique_ptr result( GEOSGeom_clone_r( context, reshapeLineGeos ) );
3366 return result;
3367 }
3368 else
3369 return nullptr;
3370 }
3371
3372 QVector<GEOSGeometry *> resultLineParts; //collection with the line segments that will be contained in result
3373 QVector<GEOSGeometry *> probableParts; //parts where we can decide on inclusion only after going through all the candidates
3374
3375 for ( int i = 0; i < numMergedLines; ++i )
3376 {
3377 const GEOSGeometry *currentGeom = GEOSGetGeometryN_r( context, mergedLines.get(), i );
3378
3379 // have we already added this part?
3380 bool alreadyAdded = false;
3381 double distance = 0;
3382 double bufferDistance = std::pow( 10.0L, geomDigits( currentGeom ) - 11 );
3383 for ( const GEOSGeometry *other : std::as_const( resultLineParts ) )
3384 {
3385 GEOSHausdorffDistance_r( context, currentGeom, other, &distance );
3386 if ( distance < bufferDistance )
3387 {
3388 alreadyAdded = true;
3389 break;
3390 }
3391 }
3392 if ( alreadyAdded )
3393 continue;
3394
3395 const GEOSCoordSequence *currentCoordSeq = GEOSGeom_getCoordSeq_r( context, currentGeom );
3396 unsigned int currentCoordSeqSize;
3397 GEOSCoordSeq_getSize_r( context, currentCoordSeq, &currentCoordSeqSize );
3398 if ( currentCoordSeqSize < 2 )
3399 continue;
3400
3401 //get the two endpoints of the current line merge result
3402 double xBegin, xEnd, yBegin, yEnd;
3403 GEOSCoordSeq_getX_r( context, currentCoordSeq, 0, &xBegin );
3404 GEOSCoordSeq_getY_r( context, currentCoordSeq, 0, &yBegin );
3405 GEOSCoordSeq_getX_r( context, currentCoordSeq, currentCoordSeqSize - 1, &xEnd );
3406 GEOSCoordSeq_getY_r( context, currentCoordSeq, currentCoordSeqSize - 1, &yEnd );
3407 geos::unique_ptr beginCurrentGeomVertex = createGeosPointXY( xBegin, yBegin, false, 0, false, 0, 2, precision );
3408 geos::unique_ptr endCurrentGeomVertex = createGeosPointXY( xEnd, yEnd, false, 0, false, 0, 2, precision );
3409
3410 //check how many endpoints of the line merge result are on the (original) line
3411 int nEndpointsOnOriginalLine = 0;
3412 if ( pointContainedInLine( beginCurrentGeomVertex.get(), line ) == 1 )
3413 nEndpointsOnOriginalLine += 1;
3414
3415 if ( pointContainedInLine( endCurrentGeomVertex.get(), line ) == 1 )
3416 nEndpointsOnOriginalLine += 1;
3417
3418 //check how many endpoints equal the endpoints of the original line
3419 int nEndpointsSameAsOriginalLine = 0;
3420 if ( GEOSEquals_r( context, beginCurrentGeomVertex.get(), beginLineVertex.get() ) == 1
3421 || GEOSEquals_r( context, beginCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
3422 nEndpointsSameAsOriginalLine += 1;
3423
3424 if ( GEOSEquals_r( context, endCurrentGeomVertex.get(), beginLineVertex.get() ) == 1
3425 || GEOSEquals_r( context, endCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
3426 nEndpointsSameAsOriginalLine += 1;
3427
3428 //check if the current geometry overlaps the original geometry (GEOSOverlap does not seem to work with linestrings)
3429 bool currentGeomOverlapsOriginalGeom = false;
3430 bool currentGeomOverlapsReshapeLine = false;
3431 if ( lineContainedInLine( currentGeom, line ) == 1 )
3432 currentGeomOverlapsOriginalGeom = true;
3433
3434 if ( lineContainedInLine( currentGeom, reshapeLineGeos ) == 1 )
3435 currentGeomOverlapsReshapeLine = true;
3436
3437 //logic to decide if this part belongs to the result
3438 if ( !isRing && nEndpointsSameAsOriginalLine == 1 && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
3439 {
3440 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3441 }
3442 //for closed rings, we take one segment from the candidate list
3443 else if ( isRing && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
3444 {
3445 probableParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3446 }
3447 else if ( nEndpointsOnOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
3448 {
3449 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3450 }
3451 else if ( nEndpointsSameAsOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
3452 {
3453 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3454 }
3455 else if ( currentGeomOverlapsOriginalGeom && currentGeomOverlapsReshapeLine )
3456 {
3457 resultLineParts.push_back( GEOSGeom_clone_r( context, currentGeom ) );
3458 }
3459 }
3460
3461 //add the longest segment from the probable list for rings (only used for polygon rings)
3462 if ( isRing && !probableParts.isEmpty() )
3463 {
3464 geos::unique_ptr maxGeom; //the longest geometry in the probabla list
3465 GEOSGeometry *currentGeom = nullptr;
3466 double maxLength = -std::numeric_limits<double>::max();
3467 double currentLength = 0;
3468 for ( int i = 0; i < probableParts.size(); ++i )
3469 {
3470 currentGeom = probableParts.at( i );
3471 GEOSLength_r( context, currentGeom, &currentLength );
3472 if ( currentLength > maxLength )
3473 {
3474 maxLength = currentLength;
3475 maxGeom.reset( currentGeom );
3476 }
3477 else
3478 {
3479 GEOSGeom_destroy_r( context, currentGeom );
3480 }
3481 }
3482 resultLineParts.push_back( maxGeom.release() );
3483 }
3484
3485 geos::unique_ptr result;
3486 if ( resultLineParts.empty() )
3487 return nullptr;
3488
3489 if ( resultLineParts.size() == 1 ) //the whole result was reshaped
3490 {
3491 result.reset( resultLineParts[0] );
3492 }
3493 else //>1
3494 {
3495 GEOSGeometry **lineArray = new GEOSGeometry*[resultLineParts.size()];
3496 for ( int i = 0; i < resultLineParts.size(); ++i )
3497 {
3498 lineArray[i] = resultLineParts[i];
3499 }
3500
3501 //create multiline from resultLineParts
3502 geos::unique_ptr multiLineGeom( GEOSGeom_createCollection_r( context, GEOS_MULTILINESTRING, lineArray, resultLineParts.size() ) );
3503 delete [] lineArray;
3504
3505 //then do a linemerge with the newly combined partstrings
3506 result.reset( GEOSLineMerge_r( context, multiLineGeom.get() ) );
3507 }
3508
3509 //now test if the result is a linestring. Otherwise something went wrong
3510 if ( GEOSGeomTypeId_r( context, result.get() ) != GEOS_LINESTRING )
3511 {
3512 return nullptr;
3513 }
3514
3515 return result;
3516}
3517
3518geos::unique_ptr QgsGeos::reshapePolygon( const GEOSGeometry *polygon, const GEOSGeometry *reshapeLineGeos, double precision )
3519{
3520 //go through outer shell and all inner rings and check if there is exactly one intersection of a ring and the reshape line
3521 int nIntersections = 0;
3522 int lastIntersectingRing = -2;
3523 const GEOSGeometry *lastIntersectingGeom = nullptr;
3524
3525 GEOSContextHandle_t context = QgsGeosContext::get();
3526 int nRings = GEOSGetNumInteriorRings_r( context, polygon );
3527 if ( nRings < 0 )
3528 return nullptr;
3529
3530 //does outer ring intersect?
3531 const GEOSGeometry *outerRing = GEOSGetExteriorRing_r( context, polygon );
3532 if ( GEOSIntersects_r( context, outerRing, reshapeLineGeos ) == 1 )
3533 {
3534 ++nIntersections;
3535 lastIntersectingRing = -1;
3536 lastIntersectingGeom = outerRing;
3537 }
3538
3539 //do inner rings intersect?
3540 const GEOSGeometry **innerRings = new const GEOSGeometry*[nRings];
3541
3542 try
3543 {
3544 for ( int i = 0; i < nRings; ++i )
3545 {
3546 innerRings[i] = GEOSGetInteriorRingN_r( context, polygon, i );
3547 if ( GEOSIntersects_r( context, innerRings[i], reshapeLineGeos ) == 1 )
3548 {
3549 ++nIntersections;
3550 lastIntersectingRing = i;
3551 lastIntersectingGeom = innerRings[i];
3552 }
3553 }
3554 }
3555 catch ( GEOSException & )
3556 {
3557 nIntersections = 0;
3558 }
3559
3560 if ( nIntersections != 1 ) //reshape line is only allowed to intersect one ring
3561 {
3562 delete [] innerRings;
3563 return nullptr;
3564 }
3565
3566 //we have one intersecting ring, let's try to reshape it
3567 geos::unique_ptr reshapeResult = reshapeLine( lastIntersectingGeom, reshapeLineGeos, precision );
3568 if ( !reshapeResult )
3569 {
3570 delete [] innerRings;
3571 return nullptr;
3572 }
3573
3574 //if reshaping took place, we need to reassemble the polygon and its rings
3575 GEOSGeometry *newRing = nullptr;
3576 const GEOSCoordSequence *reshapeSequence = GEOSGeom_getCoordSeq_r( context, reshapeResult.get() );
3577 GEOSCoordSequence *newCoordSequence = GEOSCoordSeq_clone_r( context, reshapeSequence );
3578
3579 reshapeResult.reset();
3580
3581 newRing = GEOSGeom_createLinearRing_r( context, newCoordSequence );
3582 if ( !newRing )
3583 {
3584 delete [] innerRings;
3585 return nullptr;
3586 }
3587
3588 GEOSGeometry *newOuterRing = nullptr;
3589 if ( lastIntersectingRing == -1 )
3590 newOuterRing = newRing;
3591 else
3592 newOuterRing = GEOSGeom_clone_r( context, outerRing );
3593
3594 //check if all the rings are still inside the outer boundary
3595 QVector<GEOSGeometry *> ringList;
3596 if ( nRings > 0 )
3597 {
3598 GEOSGeometry *outerRingPoly = GEOSGeom_createPolygon_r( context, GEOSGeom_clone_r( context, newOuterRing ), nullptr, 0 );
3599 if ( outerRingPoly )
3600 {
3601 ringList.reserve( nRings );
3602 GEOSGeometry *currentRing = nullptr;
3603 for ( int i = 0; i < nRings; ++i )
3604 {
3605 if ( lastIntersectingRing == i )
3606 currentRing = newRing;
3607 else
3608 currentRing = GEOSGeom_clone_r( context, innerRings[i] );
3609
3610 //possibly a ring is no longer contained in the result polygon after reshape
3611 if ( GEOSContains_r( context, outerRingPoly, currentRing ) == 1 )
3612 ringList.push_back( currentRing );
3613 else
3614 GEOSGeom_destroy_r( context, currentRing );
3615 }
3616 }
3617 GEOSGeom_destroy_r( context, outerRingPoly );
3618 }
3619
3620 GEOSGeometry **newInnerRings = new GEOSGeometry*[ringList.size()];
3621 for ( int i = 0; i < ringList.size(); ++i )
3622 newInnerRings[i] = ringList.at( i );
3623
3624 delete [] innerRings;
3625
3626 geos::unique_ptr reshapedPolygon( GEOSGeom_createPolygon_r( context, newOuterRing, newInnerRings, ringList.size() ) );
3627 delete[] newInnerRings;
3628
3629 return reshapedPolygon;
3630}
3631
3632int QgsGeos::lineContainedInLine( const GEOSGeometry *line1, const GEOSGeometry *line2 )
3633{
3634 if ( !line1 || !line2 )
3635 {
3636 return -1;
3637 }
3638
3639 double bufferDistance = std::pow( 10.0L, geomDigits( line2 ) - 11 );
3640
3641 GEOSContextHandle_t context = QgsGeosContext::get();
3642 geos::unique_ptr bufferGeom( GEOSBuffer_r( context, line2, bufferDistance, DEFAULT_QUADRANT_SEGMENTS ) );
3643 if ( !bufferGeom )
3644 return -2;
3645
3646 geos::unique_ptr intersectionGeom( GEOSIntersection_r( context, bufferGeom.get(), line1 ) );
3647
3648 //compare ratio between line1Length and intersectGeomLength (usually close to 1 if line1 is contained in line2)
3649 double intersectGeomLength;
3650 double line1Length;
3651
3652 GEOSLength_r( context, intersectionGeom.get(), &intersectGeomLength );
3653 GEOSLength_r( context, line1, &line1Length );
3654
3655 double intersectRatio = line1Length / intersectGeomLength;
3656 if ( intersectRatio > 0.9 && intersectRatio < 1.1 )
3657 return 1;
3658
3659 return 0;
3660}
3661
3662int QgsGeos::pointContainedInLine( const GEOSGeometry *point, const GEOSGeometry *line )
3663{
3664 if ( !point || !line )
3665 return -1;
3666
3667 double bufferDistance = std::pow( 10.0L, geomDigits( line ) - 11 );
3668
3669 GEOSContextHandle_t context = QgsGeosContext::get();
3670 geos::unique_ptr lineBuffer( GEOSBuffer_r( context, line, bufferDistance, 8 ) );
3671 if ( !lineBuffer )
3672 return -2;
3673
3674 bool contained = false;
3675 if ( GEOSContains_r( context, lineBuffer.get(), point ) == 1 )
3676 contained = true;
3677
3678 return contained;
3679}
3680
3681int QgsGeos::geomDigits( const GEOSGeometry *geom )
3682{
3683 GEOSContextHandle_t context = QgsGeosContext::get();
3684 geos::unique_ptr bbox( GEOSEnvelope_r( context, geom ) );
3685 if ( !bbox.get() )
3686 return -1;
3687
3688 const GEOSGeometry *bBoxRing = GEOSGetExteriorRing_r( context, bbox.get() );
3689 if ( !bBoxRing )
3690 return -1;
3691
3692 const GEOSCoordSequence *bBoxCoordSeq = GEOSGeom_getCoordSeq_r( context, bBoxRing );
3693
3694 if ( !bBoxCoordSeq )
3695 return -1;
3696
3697 unsigned int nCoords = 0;
3698 if ( !GEOSCoordSeq_getSize_r( context, bBoxCoordSeq, &nCoords ) )
3699 return -1;
3700
3701 int maxDigits = -1;
3702 for ( unsigned int i = 0; i < nCoords - 1; ++i )
3703 {
3704 double t;
3705 GEOSCoordSeq_getX_r( context, bBoxCoordSeq, i, &t );
3706
3707 int digits;
3708 digits = std::ceil( std::log10( std::fabs( t ) ) );
3709 if ( digits > maxDigits )
3710 maxDigits = digits;
3711
3712 GEOSCoordSeq_getY_r( context, bBoxCoordSeq, i, &t );
3713 digits = std::ceil( std::log10( std::fabs( t ) ) );
3714 if ( digits > maxDigits )
3715 maxDigits = digits;
3716 }
3717
3718 return maxDigits;
3719}
The Qgis class provides global constants for use throughout the application.
Definition qgis.h:54
BufferSide
Side of line to buffer.
Definition qgis.h:1766
@ Right
Buffer to right of line.
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:1712
@ 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:1791
EndCapStyle
End cap styles for buffers.
Definition qgis.h:1778
CoverageValidityResult
Coverage validity results.
Definition qgis.h:1804
@ 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:1817
@ 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:3173
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:3196
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:2801
std::unique_ptr< QgsAbstractGeometry > maximumInscribedCircle(double tolerance, QString *errorMsg=nullptr) const
Returns the maximum inscribed circle.
Definition qgsgeos.cpp:2679
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:2781
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:2765
double minimumClearance(QString *errorMsg=nullptr) const
Computes the minimum clearance of a geometry.
Definition qgsgeos.cpp:2731
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:2652
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:2982
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:2938
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:2749
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:2715
bool isSimple(QString *errorMsg=nullptr) const override
Determines whether the geometry is simple (according to OGC definition).
Definition qgsgeos.cpp:2342
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:2308
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:2918
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:3100
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:2633
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:3040
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:2328
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:3140
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:2695
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:5465
QMap< QString, QString > QgsStringMap
Definition qgis.h:6003
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