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