QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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
143 : QgsGeometryEngine( geometry )
144 , mGeos( nullptr )
145 , mPrecision( precision )
146{
147 cacheGeos();
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();
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() 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 );
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 QVector< GEOSGeometry * > geosGeometries;
441 geosGeometries.reserve( geomList.size() );
442 for ( const QgsAbstractGeometry *g : geomList )
443 {
444 if ( !g )
445 continue;
446
447 geosGeometries << asGeos( g, mPrecision ).release();
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 QVector< GEOSGeometry * > geosGeometries;
472 geosGeometries.reserve( geomList.size() );
473 for ( const QgsGeometry &g : geomList )
474 {
475 if ( g.isNull() )
476 continue;
477
478 geosGeometries << asGeos( g.constGet(), mPrecision ).release();
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< QgsAbstractGeometry > splitGeom( fromGeos( GEOSsplitPoint ) );
1025 std::unique_ptr< QgsMultiPoint > splitPoints( qgsgeometry_cast<QgsMultiPoint *>( splitGeom->clone() ) );
1026 if ( !splitPoints )
1027 {
1028 QgsPoint *splitPoint = qgsgeometry_cast<QgsPoint *>( splitGeom->clone() );
1029 if ( !splitPoint )
1030 {
1031 return nullptr;
1032 }
1033 splitPoints.reset( new QgsMultiPoint() );
1034 splitPoints->addGeometry( splitPoint );
1035 }
1036
1037 QgsMultiCurve lines;
1038
1039 //For each part
1040 for ( int i = 0; i < multiCurve->numGeometries(); ++i )
1041 {
1042 const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( multiCurve->geometryN( i ) );
1043 if ( !line )
1044 {
1045 const QgsCurve *curve = qgsgeometry_cast<const QgsCurve *>( multiCurve->geometryN( i ) );
1046 line = curve->curveToLine();
1047 }
1048 if ( !line )
1049 {
1050 return nullptr;
1051 }
1052 // we gather the intersection points and their distance from previous node grouped by segment
1053 QMap< int, QVector< QPair< double, QgsPoint > > >pointMap;
1054 for ( int p = 0; p < splitPoints->numGeometries(); ++p )
1055 {
1056 const QgsPoint *intersectionPoint = splitPoints->pointN( p );
1057 QgsPoint segmentPoint2D;
1058 QgsVertexId nextVertex;
1059 // With closestSegment we only get a 2D point so we need to interpolate if we
1060 // don't want to lose Z data
1061 line->closestSegment( *intersectionPoint, segmentPoint2D, nextVertex );
1062 const QgsLineString segment = QgsLineString( line->pointN( nextVertex.vertex - 1 ), line->pointN( nextVertex.vertex ) );
1063 const double distance = segmentPoint2D.distance( line->pointN( nextVertex.vertex - 1 ) );
1064
1065 // Due to precision issues, distance can be a tad larger than the actual segment length, making interpolatePoint() return nullptr
1066 // In that case we'll use the segment's endpoint instead of interpolating
1067 std::unique_ptr< QgsPoint > correctSegmentPoint( distance > segment.length() ? segment.endPoint().clone() : segment.interpolatePoint( distance ) );
1068
1069 const QPair< double, QgsPoint > pair = qMakePair( distance, *correctSegmentPoint.get() );
1070 if ( pointMap.contains( nextVertex.vertex - 1 ) )
1071 pointMap[ nextVertex.vertex - 1 ].append( pair );
1072 else
1073 pointMap[ nextVertex.vertex - 1 ] = QVector< QPair< double, QgsPoint > >() << pair;
1074 }
1075
1076 // When we have more than one intersection point on a segment we need those points
1077 // to be sorted by their distance from the previous geometry vertex
1078 for ( auto &p : pointMap )
1079 {
1080 std::sort( p.begin(), p.end(), []( const QPair< double, QgsPoint > &a, const QPair< double, QgsPoint > &b ) { return a.first < b.first; } );
1081 }
1082
1083 //For each segment
1084 QgsLineString newLine;
1085 int nVertices = line->numPoints();
1086 QgsPoint splitPoint;
1087 for ( int j = 0; j < nVertices; ++j )
1088 {
1089 QgsPoint currentPoint = line->pointN( j );
1090 newLine.addVertex( currentPoint );
1091 if ( pointMap.contains( j ) )
1092 {
1093 // For each intersecting point
1094 for ( int k = 0; k < pointMap[ j ].size(); ++k )
1095 {
1096 splitPoint = pointMap[ j ][k].second;
1097 if ( splitPoint == currentPoint )
1098 {
1099 lines.addGeometry( newLine.clone() );
1100 newLine = QgsLineString();
1101 newLine.addVertex( currentPoint );
1102 }
1103 else if ( splitPoint == line->pointN( j + 1 ) )
1104 {
1105 newLine.addVertex( line->pointN( j + 1 ) );
1106 lines.addGeometry( newLine.clone() );
1107 newLine = QgsLineString();
1108 }
1109 else
1110 {
1111 newLine.addVertex( splitPoint );
1112 lines.addGeometry( newLine.clone() );
1113 newLine = QgsLineString();
1114 newLine.addVertex( splitPoint );
1115 }
1116 }
1117 }
1118 }
1119 lines.addGeometry( newLine.clone() );
1120 }
1121
1122 return asGeos( &lines, mPrecision );
1123}
1124
1125QgsGeometryEngine::EngineOperationResult QgsGeos::splitLinearGeometry( GEOSGeometry *splitLine, QVector<QgsGeometry> &newGeometries, bool skipIntersectionCheck ) const
1126{
1127 Q_UNUSED( skipIntersectionCheck )
1128 if ( !splitLine )
1129 return InvalidInput;
1130
1131 if ( !mGeos )
1132 return InvalidBaseGeometry;
1133
1134 geos::unique_ptr intersectGeom( GEOSIntersection_r( geosinit()->ctxt, splitLine, mGeos.get() ) );
1135 if ( !intersectGeom || GEOSisEmpty_r( geosinit()->ctxt, intersectGeom.get() ) )
1136 return NothingHappened;
1137
1138 //check that split line has no linear intersection
1139 int linearIntersect = GEOSRelatePattern_r( geosinit()->ctxt, mGeos.get(), splitLine, "1********" );
1140 if ( linearIntersect > 0 )
1141 return InvalidInput;
1142
1143 geos::unique_ptr splitGeom;
1144 splitGeom = linePointDifference( intersectGeom.get() );
1145
1146 if ( !splitGeom )
1147 return InvalidBaseGeometry;
1148
1149 QVector<GEOSGeometry *> lineGeoms;
1150
1151 int splitType = GEOSGeomTypeId_r( geosinit()->ctxt, splitGeom.get() );
1152 if ( splitType == GEOS_MULTILINESTRING )
1153 {
1154 int nGeoms = GEOSGetNumGeometries_r( geosinit()->ctxt, splitGeom.get() );
1155 lineGeoms.reserve( nGeoms );
1156 for ( int i = 0; i < nGeoms; ++i )
1157 lineGeoms << GEOSGeom_clone_r( geosinit()->ctxt, GEOSGetGeometryN_r( geosinit()->ctxt, splitGeom.get(), i ) );
1158
1159 }
1160 else
1161 {
1162 lineGeoms << GEOSGeom_clone_r( geosinit()->ctxt, splitGeom.get() );
1163 }
1164
1165 mergeGeometriesMultiTypeSplit( lineGeoms );
1166
1167 for ( int i = 0; i < lineGeoms.size(); ++i )
1168 {
1169 newGeometries << QgsGeometry( fromGeos( lineGeoms[i] ) );
1170 GEOSGeom_destroy_r( geosinit()->ctxt, lineGeoms[i] );
1171 }
1172
1173 return Success;
1174}
1175
1176QgsGeometryEngine::EngineOperationResult QgsGeos::splitPolygonGeometry( GEOSGeometry *splitLine, QVector<QgsGeometry> &newGeometries, bool skipIntersectionCheck ) const
1177{
1178 if ( !splitLine )
1179 return InvalidInput;
1180
1181 if ( !mGeos )
1182 return InvalidBaseGeometry;
1183
1184 // we will need prepared geometry for intersection tests
1185 const_cast<QgsGeos *>( this )->prepareGeometry();
1186 if ( !mGeosPrepared )
1187 return EngineError;
1188
1189 //first test if linestring intersects geometry. If not, return straight away
1190 if ( !skipIntersectionCheck && !GEOSPreparedIntersects_r( geosinit()->ctxt, mGeosPrepared.get(), splitLine ) )
1191 return NothingHappened;
1192
1193 //first union all the polygon rings together (to get them noded, see JTS developer guide)
1194 geos::unique_ptr nodedGeometry = nodeGeometries( splitLine, mGeos.get() );
1195 if ( !nodedGeometry )
1196 return NodedGeometryError; //an error occurred during noding
1197
1198 const GEOSGeometry *noded = nodedGeometry.get();
1199 geos::unique_ptr polygons( GEOSPolygonize_r( geosinit()->ctxt, &noded, 1 ) );
1200 if ( !polygons || numberOfGeometries( polygons.get() ) == 0 )
1201 {
1202 return InvalidBaseGeometry;
1203 }
1204
1205 //test every polygon is contained in original geometry
1206 //include in result if yes
1207 QVector<GEOSGeometry *> testedGeometries;
1208
1209 // test whether the polygon parts returned from polygonize algorithm actually
1210 // belong to the source polygon geometry (if the source polygon contains some holes,
1211 // those would be also returned by polygonize and we need to skip them)
1212 for ( int i = 0; i < numberOfGeometries( polygons.get() ); i++ )
1213 {
1214 const GEOSGeometry *polygon = GEOSGetGeometryN_r( geosinit()->ctxt, polygons.get(), i );
1215
1216 geos::unique_ptr pointOnSurface( GEOSPointOnSurface_r( geosinit()->ctxt, polygon ) );
1217 if ( pointOnSurface && GEOSPreparedIntersects_r( geosinit()->ctxt, mGeosPrepared.get(), pointOnSurface.get() ) )
1218 testedGeometries << GEOSGeom_clone_r( geosinit()->ctxt, polygon );
1219 }
1220
1221 int nGeometriesThis = numberOfGeometries( mGeos.get() ); //original number of geometries
1222 if ( testedGeometries.empty() || testedGeometries.size() == nGeometriesThis )
1223 {
1224 //no split done, preserve original geometry
1225 for ( int i = 0; i < testedGeometries.size(); ++i )
1226 {
1227 GEOSGeom_destroy_r( geosinit()->ctxt, testedGeometries[i] );
1228 }
1229 return NothingHappened;
1230 }
1231
1232 // For multi-part geometries, try to identify parts that have been unchanged and try to merge them back
1233 // to a single multi-part geometry. For example, if there's a multi-polygon with three parts, but only
1234 // one part is being split, this function makes sure that the other two parts will be kept in a multi-part
1235 // geometry rather than being separated into two single-part geometries.
1236 mergeGeometriesMultiTypeSplit( testedGeometries );
1237
1238 int i;
1239 for ( i = 0; i < testedGeometries.size() && GEOSisValid_r( geosinit()->ctxt, testedGeometries[i] ); ++i )
1240 ;
1241
1242 if ( i < testedGeometries.size() )
1243 {
1244 for ( i = 0; i < testedGeometries.size(); ++i )
1245 GEOSGeom_destroy_r( geosinit()->ctxt, testedGeometries[i] );
1246
1247 return InvalidBaseGeometry;
1248 }
1249
1250 for ( i = 0; i < testedGeometries.size(); ++i )
1251 {
1252 newGeometries << QgsGeometry( fromGeos( testedGeometries[i] ) );
1253 GEOSGeom_destroy_r( geosinit()->ctxt, testedGeometries[i] );
1254 }
1255
1256 return Success;
1257}
1258
1259geos::unique_ptr QgsGeos::nodeGeometries( const GEOSGeometry *splitLine, const GEOSGeometry *geom )
1260{
1261 if ( !splitLine || !geom )
1262 return nullptr;
1263
1264 geos::unique_ptr geometryBoundary;
1265 if ( GEOSGeomTypeId_r( geosinit()->ctxt, geom ) == GEOS_POLYGON || GEOSGeomTypeId_r( geosinit()->ctxt, geom ) == GEOS_MULTIPOLYGON )
1266 geometryBoundary.reset( GEOSBoundary_r( geosinit()->ctxt, geom ) );
1267 else
1268 geometryBoundary.reset( GEOSGeom_clone_r( geosinit()->ctxt, geom ) );
1269
1270 geos::unique_ptr splitLineClone( GEOSGeom_clone_r( geosinit()->ctxt, splitLine ) );
1271 geos::unique_ptr unionGeometry( GEOSUnion_r( geosinit()->ctxt, splitLineClone.get(), geometryBoundary.get() ) );
1272
1273 return unionGeometry;
1274}
1275
1276int QgsGeos::mergeGeometriesMultiTypeSplit( QVector<GEOSGeometry *> &splitResult ) const
1277{
1278 if ( !mGeos )
1279 return 1;
1280
1281 //convert mGeos to geometry collection
1282 int type = GEOSGeomTypeId_r( geosinit()->ctxt, mGeos.get() );
1283 if ( type != GEOS_GEOMETRYCOLLECTION &&
1284 type != GEOS_MULTILINESTRING &&
1285 type != GEOS_MULTIPOLYGON &&
1286 type != GEOS_MULTIPOINT )
1287 return 0;
1288
1289 QVector<GEOSGeometry *> copyList = splitResult;
1290 splitResult.clear();
1291
1292 //collect all the geometries that belong to the initial multifeature
1293 QVector<GEOSGeometry *> unionGeom;
1294
1295 for ( int i = 0; i < copyList.size(); ++i )
1296 {
1297 //is this geometry a part of the original multitype?
1298 bool isPart = false;
1299 for ( int j = 0; j < GEOSGetNumGeometries_r( geosinit()->ctxt, mGeos.get() ); j++ )
1300 {
1301 if ( GEOSEquals_r( geosinit()->ctxt, copyList[i], GEOSGetGeometryN_r( geosinit()->ctxt, mGeos.get(), j ) ) )
1302 {
1303 isPart = true;
1304 break;
1305 }
1306 }
1307
1308 if ( isPart )
1309 {
1310 unionGeom << copyList[i];
1311 }
1312 else
1313 {
1314 QVector<GEOSGeometry *> geomVector;
1315 geomVector << copyList[i];
1316
1317 if ( type == GEOS_MULTILINESTRING )
1318 splitResult << createGeosCollection( GEOS_MULTILINESTRING, geomVector ).release();
1319 else if ( type == GEOS_MULTIPOLYGON )
1320 splitResult << createGeosCollection( GEOS_MULTIPOLYGON, geomVector ).release();
1321 else
1322 GEOSGeom_destroy_r( geosinit()->ctxt, copyList[i] );
1323 }
1324 }
1325
1326 //make multifeature out of unionGeom
1327 if ( !unionGeom.isEmpty() )
1328 {
1329 if ( type == GEOS_MULTILINESTRING )
1330 splitResult << createGeosCollection( GEOS_MULTILINESTRING, unionGeom ).release();
1331 else if ( type == GEOS_MULTIPOLYGON )
1332 splitResult << createGeosCollection( GEOS_MULTIPOLYGON, unionGeom ).release();
1333 }
1334 else
1335 {
1336 unionGeom.clear();
1337 }
1338
1339 return 0;
1340}
1341
1342geos::unique_ptr QgsGeos::createGeosCollection( int typeId, const QVector<GEOSGeometry *> &geoms )
1343{
1344 int nNullGeoms = geoms.count( nullptr );
1345 int nNotNullGeoms = geoms.size() - nNullGeoms;
1346
1347 GEOSGeometry **geomarr = new GEOSGeometry*[ nNotNullGeoms ];
1348 if ( !geomarr )
1349 {
1350 return nullptr;
1351 }
1352
1353 int i = 0;
1354 QVector<GEOSGeometry *>::const_iterator geomIt = geoms.constBegin();
1355 for ( ; geomIt != geoms.constEnd(); ++geomIt )
1356 {
1357 if ( *geomIt )
1358 {
1359 if ( GEOSisEmpty_r( geosinit()->ctxt, *geomIt ) )
1360 {
1361 // don't add empty parts to a geos collection, it can cause crashes in GEOS
1362 nNullGeoms++;
1363 nNotNullGeoms--;
1364 GEOSGeom_destroy_r( geosinit()->ctxt, *geomIt );
1365 }
1366 else
1367 {
1368 geomarr[i] = *geomIt;
1369 ++i;
1370 }
1371 }
1372 }
1373 geos::unique_ptr geom;
1374
1375 try
1376 {
1377 geom.reset( GEOSGeom_createCollection_r( geosinit()->ctxt, typeId, geomarr, nNotNullGeoms ) );
1378 }
1379 catch ( GEOSException & )
1380 {
1381 }
1382
1383 delete [] geomarr;
1384
1385 return geom;
1386}
1387
1388std::unique_ptr<QgsAbstractGeometry> QgsGeos::fromGeos( const GEOSGeometry *geos )
1389{
1390 if ( !geos )
1391 {
1392 return nullptr;
1393 }
1394
1395 int nCoordDims = GEOSGeom_getCoordinateDimension_r( geosinit()->ctxt, geos );
1396 int nDims = GEOSGeom_getDimensions_r( geosinit()->ctxt, geos );
1397 bool hasZ = ( nCoordDims == 3 );
1398 bool hasM = ( ( nDims - nCoordDims ) == 1 );
1399
1400 switch ( GEOSGeomTypeId_r( geosinit()->ctxt, geos ) )
1401 {
1402 case GEOS_POINT: // a point
1403 {
1404 if ( GEOSisEmpty_r( geosinit()->ctxt, geos ) )
1405 return nullptr;
1406
1407 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, geos );
1408 unsigned int nPoints = 0;
1409 GEOSCoordSeq_getSize_r( geosinit()->ctxt, cs, &nPoints );
1410 return nPoints > 0 ? std::unique_ptr<QgsAbstractGeometry>( coordSeqPoint( cs, 0, hasZ, hasM ).clone() ) : nullptr;
1411 }
1412 case GEOS_LINESTRING:
1413 {
1414 return sequenceToLinestring( geos, hasZ, hasM );
1415 }
1416 case GEOS_POLYGON:
1417 {
1418 return fromGeosPolygon( geos );
1419 }
1420 case GEOS_MULTIPOINT:
1421 {
1422 std::unique_ptr< QgsMultiPoint > multiPoint( new QgsMultiPoint() );
1423 int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1424 multiPoint->reserve( nParts );
1425 for ( int i = 0; i < nParts; ++i )
1426 {
1427 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ) );
1428 if ( cs )
1429 {
1430 unsigned int nPoints = 0;
1431 GEOSCoordSeq_getSize_r( geosinit()->ctxt, cs, &nPoints );
1432 if ( nPoints > 0 )
1433 multiPoint->addGeometry( coordSeqPoint( cs, 0, hasZ, hasM ).clone() );
1434 }
1435 }
1436 return std::move( multiPoint );
1437 }
1438 case GEOS_MULTILINESTRING:
1439 {
1440 std::unique_ptr< QgsMultiLineString > multiLineString( new QgsMultiLineString() );
1441 int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1442 multiLineString->reserve( nParts );
1443 for ( int i = 0; i < nParts; ++i )
1444 {
1445 std::unique_ptr< QgsLineString >line( sequenceToLinestring( GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ), hasZ, hasM ) );
1446 if ( line )
1447 {
1448 multiLineString->addGeometry( line.release() );
1449 }
1450 }
1451 return std::move( multiLineString );
1452 }
1453 case GEOS_MULTIPOLYGON:
1454 {
1455 std::unique_ptr< QgsMultiPolygon > multiPolygon( new QgsMultiPolygon() );
1456
1457 int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1458 multiPolygon->reserve( nParts );
1459 for ( int i = 0; i < nParts; ++i )
1460 {
1461 std::unique_ptr< QgsPolygon > poly = fromGeosPolygon( GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ) );
1462 if ( poly )
1463 {
1464 multiPolygon->addGeometry( poly.release() );
1465 }
1466 }
1467 return std::move( multiPolygon );
1468 }
1469 case GEOS_GEOMETRYCOLLECTION:
1470 {
1471 std::unique_ptr< QgsGeometryCollection > geomCollection( new QgsGeometryCollection() );
1472 int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1473 geomCollection->reserve( nParts );
1474 for ( int i = 0; i < nParts; ++i )
1475 {
1476 std::unique_ptr< QgsAbstractGeometry > geom( fromGeos( GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ) ) );
1477 if ( geom )
1478 {
1479 geomCollection->addGeometry( geom.release() );
1480 }
1481 }
1482 return std::move( geomCollection );
1483 }
1484 }
1485 return nullptr;
1486}
1487
1488std::unique_ptr<QgsPolygon> QgsGeos::fromGeosPolygon( const GEOSGeometry *geos )
1489{
1490 if ( GEOSGeomTypeId_r( geosinit()->ctxt, geos ) != GEOS_POLYGON )
1491 {
1492 return nullptr;
1493 }
1494
1495 int nCoordDims = GEOSGeom_getCoordinateDimension_r( geosinit()->ctxt, geos );
1496 int nDims = GEOSGeom_getDimensions_r( geosinit()->ctxt, geos );
1497 bool hasZ = ( nCoordDims == 3 );
1498 bool hasM = ( ( nDims - nCoordDims ) == 1 );
1499
1500 std::unique_ptr< QgsPolygon > polygon( new QgsPolygon() );
1501
1502 const GEOSGeometry *ring = GEOSGetExteriorRing_r( geosinit()->ctxt, geos );
1503 if ( ring )
1504 {
1505 polygon->setExteriorRing( sequenceToLinestring( ring, hasZ, hasM ).release() );
1506 }
1507
1508 QVector<QgsCurve *> interiorRings;
1509 const int ringCount = GEOSGetNumInteriorRings_r( geosinit()->ctxt, geos );
1510 interiorRings.reserve( ringCount );
1511 for ( int i = 0; i < ringCount; ++i )
1512 {
1513 ring = GEOSGetInteriorRingN_r( geosinit()->ctxt, geos, i );
1514 if ( ring )
1515 {
1516 interiorRings.push_back( sequenceToLinestring( ring, hasZ, hasM ).release() );
1517 }
1518 }
1519 polygon->setInteriorRings( interiorRings );
1520
1521 return polygon;
1522}
1523
1524std::unique_ptr<QgsLineString> QgsGeos::sequenceToLinestring( const GEOSGeometry *geos, bool hasZ, bool hasM )
1525{
1526 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, geos );
1527
1528 unsigned int nPoints;
1529 GEOSCoordSeq_getSize_r( geosinit()->ctxt, cs, &nPoints );
1530
1531 QVector< double > xOut( nPoints );
1532 QVector< double > yOut( nPoints );
1533 QVector< double > zOut;
1534 if ( hasZ )
1535 zOut.resize( nPoints );
1536 QVector< double > mOut;
1537 if ( hasM )
1538 mOut.resize( nPoints );
1539
1540 double *x = xOut.data();
1541 double *y = yOut.data();
1542 double *z = zOut.data();
1543 double *m = mOut.data();
1544
1545#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
1546 GEOSCoordSeq_copyToArrays_r( geosinit()->ctxt, cs, x, y, hasZ ? z : nullptr, hasM ? m : nullptr );
1547#else
1548 for ( unsigned int i = 0; i < nPoints; ++i )
1549 {
1550 if ( hasZ )
1551 GEOSCoordSeq_getXYZ_r( geosinit()->ctxt, cs, i, x++, y++, z++ );
1552 else
1553 GEOSCoordSeq_getXY_r( geosinit()->ctxt, cs, i, x++, y++ );
1554 if ( hasM )
1555 {
1556 GEOSCoordSeq_getOrdinate_r( geosinit()->ctxt, cs, i, 3, m++ );
1557 }
1558 }
1559#endif
1560 std::unique_ptr< QgsLineString > line( new QgsLineString( xOut, yOut, zOut, mOut ) );
1561 return line;
1562}
1563
1564int QgsGeos::numberOfGeometries( GEOSGeometry *g )
1565{
1566 if ( !g )
1567 return 0;
1568
1569 int geometryType = GEOSGeomTypeId_r( geosinit()->ctxt, g );
1570 if ( geometryType == GEOS_POINT || geometryType == GEOS_LINESTRING || geometryType == GEOS_LINEARRING
1571 || geometryType == GEOS_POLYGON )
1572 return 1;
1573
1574 //calling GEOSGetNumGeometries is save for multi types and collections also in geos2
1575 return GEOSGetNumGeometries_r( geosinit()->ctxt, g );
1576}
1577
1578QgsPoint QgsGeos::coordSeqPoint( const GEOSCoordSequence *cs, int i, bool hasZ, bool hasM )
1579{
1580 if ( !cs )
1581 {
1582 return QgsPoint();
1583 }
1584
1585 double x, y;
1586 double z = 0;
1587 double m = 0;
1588 if ( hasZ )
1589 GEOSCoordSeq_getXYZ_r( geosinit()->ctxt, cs, i, &x, &y, &z );
1590 else
1591 GEOSCoordSeq_getXY_r( geosinit()->ctxt, cs, i, &x, &y );
1592 if ( hasM )
1593 {
1594 GEOSCoordSeq_getOrdinate_r( geosinit()->ctxt, cs, i, 3, &m );
1595 }
1596
1598 if ( hasZ && hasM )
1599 {
1601 }
1602 else if ( hasZ )
1603 {
1605 }
1606 else if ( hasM )
1607 {
1609 }
1610 return QgsPoint( t, x, y, z, m );
1611}
1612
1614{
1615 if ( !geom )
1616 return nullptr;
1617
1618 int coordDims = 2;
1619 if ( geom->is3D() )
1620 {
1621 ++coordDims;
1622 }
1623 if ( geom->isMeasure() )
1624 {
1625 ++coordDims;
1626 }
1627
1629 {
1630 int geosType = GEOS_GEOMETRYCOLLECTION;
1631
1633 {
1634 switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
1635 {
1636 case Qgis::GeometryType::Point:
1637 geosType = GEOS_MULTIPOINT;
1638 break;
1639
1640 case Qgis::GeometryType::Line:
1641 geosType = GEOS_MULTILINESTRING;
1642 break;
1643
1644 case Qgis::GeometryType::Polygon:
1645 geosType = GEOS_MULTIPOLYGON;
1646 break;
1647
1648 case Qgis::GeometryType::Unknown:
1649 case Qgis::GeometryType::Null:
1650 return nullptr;
1651 }
1652 }
1653
1654
1655 const QgsGeometryCollection *c = qgsgeometry_cast<const QgsGeometryCollection *>( geom );
1656
1657 if ( !c )
1658 return nullptr;
1659
1660 QVector< GEOSGeometry * > geomVector( c->numGeometries() );
1661 for ( int i = 0; i < c->numGeometries(); ++i )
1662 {
1663 geomVector[i] = asGeos( c->geometryN( i ), precision ).release();
1664 }
1665 return createGeosCollection( geosType, geomVector );
1666 }
1667 else
1668 {
1669 switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
1670 {
1671 case Qgis::GeometryType::Point:
1672 return createGeosPoint( static_cast<const QgsPoint *>( geom ), coordDims, precision );
1673
1674 case Qgis::GeometryType::Line:
1675 return createGeosLinestring( static_cast<const QgsLineString *>( geom ), precision );
1676
1677 case Qgis::GeometryType::Polygon:
1678 return createGeosPolygon( static_cast<const QgsPolygon *>( geom ), precision );
1679
1680 case Qgis::GeometryType::Unknown:
1681 case Qgis::GeometryType::Null:
1682 return nullptr;
1683 }
1684 }
1685 return nullptr;
1686}
1687
1688std::unique_ptr<QgsAbstractGeometry> QgsGeos::overlay( const QgsAbstractGeometry *geom, Overlay op, QString *errorMsg, const QgsGeometryParameters &parameters ) const
1689{
1690 if ( !mGeos || !geom )
1691 {
1692 return nullptr;
1693 }
1694
1695 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
1696 if ( !geosGeom )
1697 {
1698 return nullptr;
1699 }
1700
1701 const double gridSize = parameters.gridSize();
1702
1703 try
1704 {
1705 geos::unique_ptr opGeom;
1706 switch ( op )
1707 {
1708 case OverlayIntersection:
1709 if ( gridSize > 0 )
1710 {
1711 opGeom.reset( GEOSIntersectionPrec_r( geosinit()->ctxt, mGeos.get(), geosGeom.get(), gridSize ) );
1712 }
1713 else
1714 {
1715 opGeom.reset( GEOSIntersection_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1716 }
1717 break;
1718
1719 case OverlayDifference:
1720 if ( gridSize > 0 )
1721 {
1722 opGeom.reset( GEOSDifferencePrec_r( geosinit()->ctxt, mGeos.get(), geosGeom.get(), gridSize ) );
1723 }
1724 else
1725 {
1726 opGeom.reset( GEOSDifference_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1727 }
1728 break;
1729
1730 case OverlayUnion:
1731 {
1732 geos::unique_ptr unionGeometry;
1733 if ( gridSize > 0 )
1734 {
1735 unionGeometry.reset( GEOSUnionPrec_r( geosinit()->ctxt, mGeos.get(), geosGeom.get(), gridSize ) );
1736 }
1737 else
1738 {
1739 unionGeometry.reset( GEOSUnion_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1740 }
1741
1742 if ( unionGeometry && GEOSGeomTypeId_r( geosinit()->ctxt, unionGeometry.get() ) == GEOS_MULTILINESTRING )
1743 {
1744 geos::unique_ptr mergedLines( GEOSLineMerge_r( geosinit()->ctxt, unionGeometry.get() ) );
1745 if ( mergedLines )
1746 {
1747 unionGeometry = std::move( mergedLines );
1748 }
1749 }
1750
1751 opGeom = std::move( unionGeometry );
1752 }
1753 break;
1754
1755 case OverlaySymDifference:
1756 if ( gridSize > 0 )
1757 {
1758 opGeom.reset( GEOSSymDifferencePrec_r( geosinit()->ctxt, mGeos.get(), geosGeom.get(), gridSize ) );
1759 }
1760 else
1761 {
1762 opGeom.reset( GEOSSymDifference_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1763 }
1764 break;
1765 }
1766 return fromGeos( opGeom.get() );
1767 }
1768 catch ( GEOSException &e )
1769 {
1770 logError( QStringLiteral( "GEOS" ), e.what() );
1771 if ( errorMsg )
1772 {
1773 *errorMsg = e.what();
1774 }
1775 return nullptr;
1776 }
1777}
1778
1779bool QgsGeos::relation( const QgsAbstractGeometry *geom, Relation r, QString *errorMsg ) const
1780{
1781 if ( !mGeos || !geom )
1782 {
1783 return false;
1784 }
1785
1786 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
1787 if ( !geosGeom )
1788 {
1789 return false;
1790 }
1791
1792 bool result = false;
1793 try
1794 {
1795 if ( mGeosPrepared ) //use faster version with prepared geometry
1796 {
1797 switch ( r )
1798 {
1799 case RelationIntersects:
1800 result = ( GEOSPreparedIntersects_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1801 break;
1802 case RelationTouches:
1803 result = ( GEOSPreparedTouches_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1804 break;
1805 case RelationCrosses:
1806 result = ( GEOSPreparedCrosses_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1807 break;
1808 case RelationWithin:
1809 result = ( GEOSPreparedWithin_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1810 break;
1811 case RelationContains:
1812 result = ( GEOSPreparedContains_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1813 break;
1814 case RelationDisjoint:
1815 result = ( GEOSPreparedDisjoint_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1816 break;
1817 case RelationOverlaps:
1818 result = ( GEOSPreparedOverlaps_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1819 break;
1820 }
1821 return result;
1822 }
1823
1824 switch ( r )
1825 {
1826 case RelationIntersects:
1827 result = ( GEOSIntersects_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1828 break;
1829 case RelationTouches:
1830 result = ( GEOSTouches_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1831 break;
1832 case RelationCrosses:
1833 result = ( GEOSCrosses_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1834 break;
1835 case RelationWithin:
1836 result = ( GEOSWithin_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1837 break;
1838 case RelationContains:
1839 result = ( GEOSContains_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1840 break;
1841 case RelationDisjoint:
1842 result = ( GEOSDisjoint_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1843 break;
1844 case RelationOverlaps:
1845 result = ( GEOSOverlaps_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1846 break;
1847 }
1848 }
1849 catch ( GEOSException &e )
1850 {
1851 logError( QStringLiteral( "GEOS" ), e.what() );
1852 if ( errorMsg )
1853 {
1854 *errorMsg = e.what();
1855 }
1856 return false;
1857 }
1858
1859 return result;
1860}
1861
1862QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, QString *errorMsg ) const
1863{
1864 if ( !mGeos )
1865 {
1866 return nullptr;
1867 }
1868
1870 try
1871 {
1872 geos.reset( GEOSBuffer_r( geosinit()->ctxt, mGeos.get(), distance, segments ) );
1873 }
1874 CATCH_GEOS_WITH_ERRMSG( nullptr )
1875 return fromGeos( geos.get() ).release();
1876}
1877
1878QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, Qgis::EndCapStyle endCapStyle, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg ) const
1879{
1880 if ( !mGeos )
1881 {
1882 return nullptr;
1883 }
1884
1886 try
1887 {
1888 geos.reset( GEOSBufferWithStyle_r( geosinit()->ctxt, mGeos.get(), distance, segments, static_cast< int >( endCapStyle ), static_cast< int >( joinStyle ), miterLimit ) );
1889 }
1890 CATCH_GEOS_WITH_ERRMSG( nullptr )
1891 return fromGeos( geos.get() ).release();
1892}
1893
1894QgsAbstractGeometry *QgsGeos::simplify( double tolerance, QString *errorMsg ) const
1895{
1896 if ( !mGeos )
1897 {
1898 return nullptr;
1899 }
1901 try
1902 {
1903 geos.reset( GEOSTopologyPreserveSimplify_r( geosinit()->ctxt, mGeos.get(), tolerance ) );
1904 }
1905 CATCH_GEOS_WITH_ERRMSG( nullptr )
1906 return fromGeos( geos.get() ).release();
1907}
1908
1909QgsAbstractGeometry *QgsGeos::interpolate( double distance, QString *errorMsg ) const
1910{
1911 if ( !mGeos )
1912 {
1913 return nullptr;
1914 }
1916 try
1917 {
1918 geos.reset( GEOSInterpolate_r( geosinit()->ctxt, mGeos.get(), distance ) );
1919 }
1920 CATCH_GEOS_WITH_ERRMSG( nullptr )
1921 return fromGeos( geos.get() ).release();
1922}
1923
1924QgsPoint *QgsGeos::centroid( QString *errorMsg ) const
1925{
1926 if ( !mGeos )
1927 {
1928 return nullptr;
1929 }
1930
1932 double x;
1933 double y;
1934
1935 try
1936 {
1937 geos.reset( GEOSGetCentroid_r( geosinit()->ctxt, mGeos.get() ) );
1938
1939 if ( !geos )
1940 return nullptr;
1941
1942 GEOSGeomGetX_r( geosinit()->ctxt, geos.get(), &x );
1943 GEOSGeomGetY_r( geosinit()->ctxt, geos.get(), &y );
1944 }
1945 CATCH_GEOS_WITH_ERRMSG( nullptr )
1946
1947 return new QgsPoint( x, y );
1948}
1949
1950QgsAbstractGeometry *QgsGeos::envelope( QString *errorMsg ) const
1951{
1952 if ( !mGeos )
1953 {
1954 return nullptr;
1955 }
1957 try
1958 {
1959 geos.reset( GEOSEnvelope_r( geosinit()->ctxt, mGeos.get() ) );
1960 }
1961 CATCH_GEOS_WITH_ERRMSG( nullptr )
1962 return fromGeos( geos.get() ).release();
1963}
1964
1965QgsPoint *QgsGeos::pointOnSurface( QString *errorMsg ) const
1966{
1967 if ( !mGeos )
1968 {
1969 return nullptr;
1970 }
1971
1972 double x;
1973 double y;
1974
1976 try
1977 {
1978 geos.reset( GEOSPointOnSurface_r( geosinit()->ctxt, mGeos.get() ) );
1979
1980 if ( !geos || GEOSisEmpty_r( geosinit()->ctxt, geos.get() ) != 0 )
1981 {
1982 return nullptr;
1983 }
1984
1985 GEOSGeomGetX_r( geosinit()->ctxt, geos.get(), &x );
1986 GEOSGeomGetY_r( geosinit()->ctxt, geos.get(), &y );
1987 }
1988 CATCH_GEOS_WITH_ERRMSG( nullptr )
1989
1990 return new QgsPoint( x, y );
1991}
1992
1993QgsAbstractGeometry *QgsGeos::convexHull( QString *errorMsg ) const
1994{
1995 if ( !mGeos )
1996 {
1997 return nullptr;
1998 }
1999
2000 try
2001 {
2002 geos::unique_ptr cHull( GEOSConvexHull_r( geosinit()->ctxt, mGeos.get() ) );
2003 std::unique_ptr< QgsAbstractGeometry > cHullGeom = fromGeos( cHull.get() );
2004 return cHullGeom.release();
2005 }
2006 CATCH_GEOS_WITH_ERRMSG( nullptr )
2007}
2008
2009QgsAbstractGeometry *QgsGeos::concaveHull( double targetPercent, bool allowHoles, QString *errorMsg ) const
2010{
2011#if GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR<11
2012 ( void )allowHoles;
2013 ( void )targetPercent;
2014 ( void )errorMsg;
2015 throw QgsNotSupportedException( QObject::tr( "Calculating concaveHull requires a QGIS build based on GEOS 3.11 or later" ) );
2016#else
2017 if ( !mGeos )
2018 {
2019 return nullptr;
2020 }
2021
2022 try
2023 {
2024 geos::unique_ptr concaveHull( GEOSConcaveHull_r( geosinit()->ctxt, mGeos.get(), targetPercent, allowHoles ) );
2025 std::unique_ptr< QgsAbstractGeometry > concaveHullGeom = fromGeos( concaveHull.get() );
2026 return concaveHullGeom.release();
2027 }
2028 CATCH_GEOS_WITH_ERRMSG( nullptr )
2029#endif
2030}
2031
2032bool QgsGeos::isValid( QString *errorMsg, const bool allowSelfTouchingHoles, QgsGeometry *errorLoc ) const
2033{
2034 if ( !mGeos )
2035 {
2036 return false;
2037 }
2038
2039 try
2040 {
2041 GEOSGeometry *g1 = nullptr;
2042 char *r = nullptr;
2043 char res = GEOSisValidDetail_r( geosinit()->ctxt, mGeos.get(), allowSelfTouchingHoles ? GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE : 0, &r, &g1 );
2044 const bool invalid = res != 1;
2045
2046 QString error;
2047 if ( r )
2048 {
2049 error = QString( r );
2050 GEOSFree_r( geosinit()->ctxt, r );
2051 }
2052
2053 if ( invalid && errorMsg )
2054 {
2055 static QgsStringMap translatedErrors;
2056
2057 if ( translatedErrors.empty() )
2058 {
2059 // Copied from https://git.osgeo.org/gitea/geos/geos/src/branch/master/src/operation/valid/TopologyValidationError.cpp
2060 translatedErrors.insert( QStringLiteral( "topology validation error" ), QObject::tr( "Topology validation error", "GEOS Error" ) );
2061 translatedErrors.insert( QStringLiteral( "repeated point" ), QObject::tr( "Repeated point", "GEOS Error" ) );
2062 translatedErrors.insert( QStringLiteral( "hole lies outside shell" ), QObject::tr( "Hole lies outside shell", "GEOS Error" ) );
2063 translatedErrors.insert( QStringLiteral( "holes are nested" ), QObject::tr( "Holes are nested", "GEOS Error" ) );
2064 translatedErrors.insert( QStringLiteral( "interior is disconnected" ), QObject::tr( "Interior is disconnected", "GEOS Error" ) );
2065 translatedErrors.insert( QStringLiteral( "self-intersection" ), QObject::tr( "Self-intersection", "GEOS Error" ) );
2066 translatedErrors.insert( QStringLiteral( "ring self-intersection" ), QObject::tr( "Ring self-intersection", "GEOS Error" ) );
2067 translatedErrors.insert( QStringLiteral( "nested shells" ), QObject::tr( "Nested shells", "GEOS Error" ) );
2068 translatedErrors.insert( QStringLiteral( "duplicate rings" ), QObject::tr( "Duplicate rings", "GEOS Error" ) );
2069 translatedErrors.insert( QStringLiteral( "too few points in geometry component" ), QObject::tr( "Too few points in geometry component", "GEOS Error" ) );
2070 translatedErrors.insert( QStringLiteral( "invalid coordinate" ), QObject::tr( "Invalid coordinate", "GEOS Error" ) );
2071 translatedErrors.insert( QStringLiteral( "ring is not closed" ), QObject::tr( "Ring is not closed", "GEOS Error" ) );
2072 }
2073
2074 *errorMsg = translatedErrors.value( error.toLower(), error );
2075
2076 if ( g1 && errorLoc )
2077 {
2078 *errorLoc = geometryFromGeos( g1 );
2079 }
2080 else if ( g1 )
2081 {
2082 GEOSGeom_destroy_r( geosinit()->ctxt, g1 );
2083 }
2084 }
2085 return !invalid;
2086 }
2087 CATCH_GEOS_WITH_ERRMSG( false )
2088}
2089
2090bool QgsGeos::isEqual( const QgsAbstractGeometry *geom, QString *errorMsg ) const
2091{
2092 if ( !mGeos || !geom )
2093 {
2094 return false;
2095 }
2096
2097 try
2098 {
2099 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2100 if ( !geosGeom )
2101 {
2102 return false;
2103 }
2104 bool equal = GEOSEquals_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() );
2105 return equal;
2106 }
2107 CATCH_GEOS_WITH_ERRMSG( false )
2108}
2109
2110bool QgsGeos::isEmpty( QString *errorMsg ) const
2111{
2112 if ( !mGeos )
2113 {
2114 return false;
2115 }
2116
2117 try
2118 {
2119 return GEOSisEmpty_r( geosinit()->ctxt, mGeos.get() );
2120 }
2121 CATCH_GEOS_WITH_ERRMSG( false )
2122}
2123
2124bool QgsGeos::isSimple( QString *errorMsg ) const
2125{
2126 if ( !mGeos )
2127 {
2128 return false;
2129 }
2130
2131 try
2132 {
2133 return GEOSisSimple_r( geosinit()->ctxt, mGeos.get() );
2134 }
2135 CATCH_GEOS_WITH_ERRMSG( false )
2136}
2137
2138GEOSCoordSequence *QgsGeos::createCoordinateSequence( const QgsCurve *curve, double precision, bool forceClose )
2139{
2140 GEOSContextHandle_t ctxt = geosinit()->ctxt;
2141
2142 std::unique_ptr< QgsLineString > segmentized;
2143 const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( curve );
2144
2145 if ( !line )
2146 {
2147 segmentized.reset( curve->curveToLine() );
2148 line = segmentized.get();
2149 }
2150
2151 if ( !line )
2152 {
2153 return nullptr;
2154 }
2155 GEOSCoordSequence *coordSeq = nullptr;
2156
2157 const int numPoints = line->numPoints();
2158
2159 const bool hasZ = line->is3D();
2160
2161#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
2162 if ( qgsDoubleNear( precision, 0 ) )
2163 {
2164 if ( !forceClose || ( line->pointN( 0 ) == line->pointN( numPoints - 1 ) ) )
2165 {
2166 // use optimised method if we don't have to force close an open ring
2167 try
2168 {
2169 coordSeq = GEOSCoordSeq_copyFromArrays_r( ctxt, line->xData(), line->yData(), line->zData(), nullptr, numPoints );
2170 if ( !coordSeq )
2171 {
2172 QgsDebugError( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for %1 points" ).arg( numPoints ) );
2173 return nullptr;
2174 }
2175 }
2176 CATCH_GEOS( nullptr )
2177 }
2178 else
2179 {
2180 QVector< double > x = line->xVector();
2181 if ( numPoints > 0 )
2182 x.append( x.at( 0 ) );
2183 QVector< double > y = line->yVector();
2184 if ( numPoints > 0 )
2185 y.append( y.at( 0 ) );
2186 QVector< double > z = line->zVector();
2187 if ( hasZ && numPoints > 0 )
2188 z.append( z.at( 0 ) );
2189 try
2190 {
2191 coordSeq = GEOSCoordSeq_copyFromArrays_r( ctxt, x.constData(), y.constData(), !hasZ ? nullptr : z.constData(), nullptr, numPoints + 1 );
2192 if ( !coordSeq )
2193 {
2194 QgsDebugError( QStringLiteral( "GEOS Exception: Could not create closed coordinate sequence for %1 points" ).arg( numPoints + 1 ) );
2195 return nullptr;
2196 }
2197 }
2198 CATCH_GEOS( nullptr )
2199 }
2200 return coordSeq;
2201 }
2202#endif
2203
2204 int coordDims = 2;
2205 const bool hasM = false; //line->isMeasure(); //disabled until geos supports m-coordinates
2206
2207 if ( hasZ )
2208 {
2209 ++coordDims;
2210 }
2211 if ( hasM )
2212 {
2213 ++coordDims;
2214 }
2215
2216 int numOutPoints = numPoints;
2217 if ( forceClose && ( line->pointN( 0 ) != line->pointN( numPoints - 1 ) ) )
2218 {
2219 ++numOutPoints;
2220 }
2221
2222 try
2223 {
2224 coordSeq = GEOSCoordSeq_create_r( ctxt, numOutPoints, coordDims );
2225 if ( !coordSeq )
2226 {
2227 QgsDebugError( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for %1 points in %2 dimensions" ).arg( numPoints ).arg( coordDims ) );
2228 return nullptr;
2229 }
2230
2231 const double *xData = line->xData();
2232 const double *yData = line->yData();
2233 const double *zData = hasZ ? line->zData() : nullptr;
2234 const double *mData = hasM ? line->mData() : nullptr;
2235
2236 if ( precision > 0. )
2237 {
2238 for ( int i = 0; i < numOutPoints; ++i )
2239 {
2240 if ( i >= numPoints )
2241 {
2242 // start reading back from start of line
2243 xData = line->xData();
2244 yData = line->yData();
2245 zData = hasZ ? line->zData() : nullptr;
2246 mData = hasM ? line->mData() : nullptr;
2247 }
2248 if ( hasZ )
2249 {
2250 GEOSCoordSeq_setXYZ_r( ctxt, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision, std::round( *zData++ / precision ) * precision );
2251 }
2252 else
2253 {
2254 GEOSCoordSeq_setXY_r( ctxt, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision );
2255 }
2256 if ( hasM )
2257 {
2258 GEOSCoordSeq_setOrdinate_r( ctxt, coordSeq, i, 3, *mData++ );
2259 }
2260 }
2261 }
2262 else
2263 {
2264 for ( int i = 0; i < numOutPoints; ++i )
2265 {
2266 if ( i >= numPoints )
2267 {
2268 // start reading back from start of line
2269 xData = line->xData();
2270 yData = line->yData();
2271 zData = hasZ ? line->zData() : nullptr;
2272 mData = hasM ? line->mData() : nullptr;
2273 }
2274 if ( hasZ )
2275 {
2276 GEOSCoordSeq_setXYZ_r( ctxt, coordSeq, i, *xData++, *yData++, *zData++ );
2277 }
2278 else
2279 {
2280 GEOSCoordSeq_setXY_r( ctxt, coordSeq, i, *xData++, *yData++ );
2281 }
2282 if ( hasM )
2283 {
2284 GEOSCoordSeq_setOrdinate_r( ctxt, coordSeq, i, 3, *mData++ );
2285 }
2286 }
2287 }
2288 }
2289 CATCH_GEOS( nullptr )
2290
2291 return coordSeq;
2292}
2293
2294geos::unique_ptr QgsGeos::createGeosPoint( const QgsAbstractGeometry *point, int coordDims, double precision )
2295{
2296 const QgsPoint *pt = qgsgeometry_cast<const QgsPoint *>( point );
2297 if ( !pt )
2298 return nullptr;
2299
2300 return createGeosPointXY( pt->x(), pt->y(), pt->is3D(), pt->z(), pt->isMeasure(), pt->m(), coordDims, precision );
2301}
2302
2303geos::unique_ptr QgsGeos::createGeosPointXY( double x, double y, bool hasZ, double z, bool hasM, double m, int coordDims, double precision )
2304{
2305 Q_UNUSED( hasM )
2306 Q_UNUSED( m )
2307
2308 geos::unique_ptr geosPoint;
2309 try
2310 {
2311 if ( coordDims == 2 )
2312 {
2313 // optimised constructor
2314 if ( precision > 0. )
2315 geosPoint.reset( GEOSGeom_createPointFromXY_r( geosinit()->ctxt, std::round( x / precision ) * precision, std::round( y / precision ) * precision ) );
2316 else
2317 geosPoint.reset( GEOSGeom_createPointFromXY_r( geosinit()->ctxt, x, y ) );
2318 return geosPoint;
2319 }
2320
2321 GEOSCoordSequence *coordSeq = GEOSCoordSeq_create_r( geosinit()->ctxt, 1, coordDims );
2322 if ( !coordSeq )
2323 {
2324 QgsDebugError( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for point with %1 dimensions" ).arg( coordDims ) );
2325 return nullptr;
2326 }
2327 if ( precision > 0. )
2328 {
2329 GEOSCoordSeq_setX_r( geosinit()->ctxt, coordSeq, 0, std::round( x / precision ) * precision );
2330 GEOSCoordSeq_setY_r( geosinit()->ctxt, coordSeq, 0, std::round( y / precision ) * precision );
2331 if ( hasZ )
2332 {
2333 GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, 0, 2, std::round( z / precision ) * precision );
2334 }
2335 }
2336 else
2337 {
2338 GEOSCoordSeq_setX_r( geosinit()->ctxt, coordSeq, 0, x );
2339 GEOSCoordSeq_setY_r( geosinit()->ctxt, coordSeq, 0, y );
2340 if ( hasZ )
2341 {
2342 GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, 0, 2, z );
2343 }
2344 }
2345#if 0 //disabled until geos supports m-coordinates
2346 if ( hasM )
2347 {
2348 GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, 0, 3, m );
2349 }
2350#endif
2351 geosPoint.reset( GEOSGeom_createPoint_r( geosinit()->ctxt, coordSeq ) );
2352 }
2353 CATCH_GEOS( nullptr )
2354 return geosPoint;
2355}
2356
2357geos::unique_ptr QgsGeos::createGeosLinestring( const QgsAbstractGeometry *curve, double precision )
2358{
2359 const QgsCurve *c = qgsgeometry_cast<const QgsCurve *>( curve );
2360 if ( !c )
2361 return nullptr;
2362
2363 GEOSCoordSequence *coordSeq = createCoordinateSequence( c, precision );
2364 if ( !coordSeq )
2365 return nullptr;
2366
2367 geos::unique_ptr geosGeom;
2368 try
2369 {
2370 geosGeom.reset( GEOSGeom_createLineString_r( geosinit()->ctxt, coordSeq ) );
2371 }
2372 CATCH_GEOS( nullptr )
2373 return geosGeom;
2374}
2375
2376geos::unique_ptr QgsGeos::createGeosPolygon( const QgsAbstractGeometry *poly, double precision )
2377{
2378 const QgsCurvePolygon *polygon = qgsgeometry_cast<const QgsCurvePolygon *>( poly );
2379 if ( !polygon )
2380 return nullptr;
2381
2382 const QgsCurve *exteriorRing = polygon->exteriorRing();
2383 if ( !exteriorRing )
2384 {
2385 return nullptr;
2386 }
2387
2388 geos::unique_ptr geosPolygon;
2389 try
2390 {
2391 geos::unique_ptr exteriorRingGeos( GEOSGeom_createLinearRing_r( geosinit()->ctxt, createCoordinateSequence( exteriorRing, precision, true ) ) );
2392
2393 int nHoles = polygon->numInteriorRings();
2394 GEOSGeometry **holes = nullptr;
2395 if ( nHoles > 0 )
2396 {
2397 holes = new GEOSGeometry*[ nHoles ];
2398 }
2399
2400 for ( int i = 0; i < nHoles; ++i )
2401 {
2402 const QgsCurve *interiorRing = polygon->interiorRing( i );
2403 holes[i] = GEOSGeom_createLinearRing_r( geosinit()->ctxt, createCoordinateSequence( interiorRing, precision, true ) );
2404 }
2405 geosPolygon.reset( GEOSGeom_createPolygon_r( geosinit()->ctxt, exteriorRingGeos.release(), holes, nHoles ) );
2406 delete[] holes;
2407 }
2408 CATCH_GEOS( nullptr )
2409
2410 return geosPolygon;
2411}
2412
2413QgsAbstractGeometry *QgsGeos::offsetCurve( double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg ) const
2414{
2415 if ( !mGeos )
2416 return nullptr;
2417
2418 geos::unique_ptr offset;
2419 try
2420 {
2421 // Force quadrant segments to be at least 8, see
2422 // https://github.com/qgis/QGIS/issues/53165#issuecomment-1563470832
2423 if ( segments < 8 )
2424 segments = 8;
2425 offset.reset( GEOSOffsetCurve_r( geosinit()->ctxt, mGeos.get(), distance, segments, static_cast< int >( joinStyle ), miterLimit ) );
2426 }
2427 CATCH_GEOS_WITH_ERRMSG( nullptr )
2428 std::unique_ptr< QgsAbstractGeometry > offsetGeom = fromGeos( offset.get() );
2429 return offsetGeom.release();
2430}
2431
2432std::unique_ptr<QgsAbstractGeometry> QgsGeos::singleSidedBuffer( double distance, int segments, Qgis::BufferSide side, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg ) const
2433{
2434 if ( !mGeos )
2435 {
2436 return nullptr;
2437 }
2438
2440 try
2441 {
2442 geos::buffer_params_unique_ptr bp( GEOSBufferParams_create_r( geosinit()->ctxt ) );
2443 GEOSBufferParams_setSingleSided_r( geosinit()->ctxt, bp.get(), 1 );
2444 GEOSBufferParams_setQuadrantSegments_r( geosinit()->ctxt, bp.get(), segments );
2445 GEOSBufferParams_setJoinStyle_r( geosinit()->ctxt, bp.get(), static_cast< int >( joinStyle ) );
2446 GEOSBufferParams_setMitreLimit_r( geosinit()->ctxt, bp.get(), miterLimit ); //#spellok
2447
2448 if ( side == Qgis::BufferSide::Right )
2449 {
2450 distance = -distance;
2451 }
2452 geos.reset( GEOSBufferWithParams_r( geosinit()->ctxt, mGeos.get(), bp.get(), distance ) );
2453 }
2454 CATCH_GEOS_WITH_ERRMSG( nullptr )
2455 return fromGeos( geos.get() );
2456}
2457
2458std::unique_ptr<QgsAbstractGeometry> QgsGeos::maximumInscribedCircle( double tolerance, QString *errorMsg ) const
2459{
2460 if ( !mGeos )
2461 {
2462 return nullptr;
2463 }
2464
2466 try
2467 {
2468 geos.reset( GEOSMaximumInscribedCircle_r( geosinit()->ctxt, mGeos.get(), tolerance ) );
2469 }
2470 CATCH_GEOS_WITH_ERRMSG( nullptr )
2471 return fromGeos( geos.get() );
2472}
2473
2474std::unique_ptr<QgsAbstractGeometry> QgsGeos::largestEmptyCircle( double tolerance, const QgsAbstractGeometry *boundary, QString *errorMsg ) const
2475{
2476 if ( !mGeos )
2477 {
2478 return nullptr;
2479 }
2480
2482 try
2483 {
2484 geos::unique_ptr boundaryGeos;
2485 if ( boundary )
2486 boundaryGeos = asGeos( boundary );
2487
2488 geos.reset( GEOSLargestEmptyCircle_r( geosinit()->ctxt, mGeos.get(), boundaryGeos.get(), tolerance ) );
2489 }
2490 CATCH_GEOS_WITH_ERRMSG( nullptr )
2491 return fromGeos( geos.get() );
2492}
2493
2494std::unique_ptr<QgsAbstractGeometry> QgsGeos::minimumWidth( QString *errorMsg ) const
2495{
2496 if ( !mGeos )
2497 {
2498 return nullptr;
2499 }
2500
2502 try
2503 {
2504 geos.reset( GEOSMinimumWidth_r( geosinit()->ctxt, mGeos.get() ) );
2505 }
2506 CATCH_GEOS_WITH_ERRMSG( nullptr )
2507 return fromGeos( geos.get() );
2508}
2509
2510double QgsGeos::minimumClearance( QString *errorMsg ) const
2511{
2512 if ( !mGeos )
2513 {
2514 return std::numeric_limits< double >::quiet_NaN();
2515 }
2516
2518 double res = 0;
2519 try
2520 {
2521 if ( GEOSMinimumClearance_r( geosinit()->ctxt, mGeos.get(), &res ) != 0 )
2522 return std::numeric_limits< double >::quiet_NaN();
2523 }
2524 CATCH_GEOS_WITH_ERRMSG( std::numeric_limits< double >::quiet_NaN() )
2525 return res;
2526}
2527
2528std::unique_ptr<QgsAbstractGeometry> QgsGeos::minimumClearanceLine( QString *errorMsg ) const
2529{
2530 if ( !mGeos )
2531 {
2532 return nullptr;
2533 }
2534
2536 try
2537 {
2538 geos.reset( GEOSMinimumClearanceLine_r( geosinit()->ctxt, mGeos.get() ) );
2539 }
2540 CATCH_GEOS_WITH_ERRMSG( nullptr )
2541 return fromGeos( geos.get() );
2542}
2543
2544std::unique_ptr<QgsAbstractGeometry> QgsGeos::node( QString *errorMsg ) const
2545{
2546 if ( !mGeos )
2547 {
2548 return nullptr;
2549 }
2550
2552 try
2553 {
2554 geos.reset( GEOSNode_r( geosinit()->ctxt, mGeos.get() ) );
2555 }
2556 CATCH_GEOS_WITH_ERRMSG( nullptr )
2557 return fromGeos( geos.get() );
2558}
2559
2560std::unique_ptr<QgsAbstractGeometry> QgsGeos::sharedPaths( const QgsAbstractGeometry *other, QString *errorMsg ) const
2561{
2562 if ( !mGeos || !other )
2563 {
2564 return nullptr;
2565 }
2566
2568 try
2569 {
2570 geos::unique_ptr otherGeos = asGeos( other );
2571 if ( !otherGeos )
2572 return nullptr;
2573
2574 geos.reset( GEOSSharedPaths_r( geosinit()->ctxt, mGeos.get(), otherGeos.get() ) );
2575 }
2576 CATCH_GEOS_WITH_ERRMSG( nullptr )
2577 return fromGeos( geos.get() );
2578}
2579
2580std::unique_ptr<QgsAbstractGeometry> QgsGeos::reshapeGeometry( const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg ) const
2581{
2582 if ( !mGeos || mGeometry->dimension() == 0 )
2583 {
2584 if ( errorCode ) { *errorCode = InvalidBaseGeometry; }
2585 return nullptr;
2586 }
2587
2588 if ( reshapeWithLine.numPoints() < 2 )
2589 {
2590 if ( errorCode ) { *errorCode = InvalidInput; }
2591 return nullptr;
2592 }
2593
2594 geos::unique_ptr reshapeLineGeos = createGeosLinestring( &reshapeWithLine, mPrecision );
2595
2596 //single or multi?
2597 int numGeoms = GEOSGetNumGeometries_r( geosinit()->ctxt, mGeos.get() );
2598 if ( numGeoms == -1 )
2599 {
2600 if ( errorCode )
2601 {
2602 *errorCode = InvalidBaseGeometry;
2603 }
2604 return nullptr;
2605 }
2606
2607 bool isMultiGeom = false;
2608 int geosTypeId = GEOSGeomTypeId_r( geosinit()->ctxt, mGeos.get() );
2609 if ( geosTypeId == GEOS_MULTILINESTRING || geosTypeId == GEOS_MULTIPOLYGON )
2610 isMultiGeom = true;
2611
2612 bool isLine = ( mGeometry->dimension() == 1 );
2613
2614 if ( !isMultiGeom )
2615 {
2616 geos::unique_ptr reshapedGeometry;
2617 if ( isLine )
2618 {
2619 reshapedGeometry = reshapeLine( mGeos.get(), reshapeLineGeos.get(), mPrecision );
2620 }
2621 else
2622 {
2623 reshapedGeometry = reshapePolygon( mGeos.get(), reshapeLineGeos.get(), mPrecision );
2624 }
2625
2626 if ( errorCode )
2627 *errorCode = Success;
2628 std::unique_ptr< QgsAbstractGeometry > reshapeResult = fromGeos( reshapedGeometry.get() );
2629 return reshapeResult;
2630 }
2631 else
2632 {
2633 try
2634 {
2635 //call reshape for each geometry part and replace mGeos with new geometry if reshape took place
2636 bool reshapeTookPlace = false;
2637
2638 geos::unique_ptr currentReshapeGeometry;
2639 GEOSGeometry **newGeoms = new GEOSGeometry*[numGeoms];
2640
2641 for ( int i = 0; i < numGeoms; ++i )
2642 {
2643 if ( isLine )
2644 currentReshapeGeometry = reshapeLine( GEOSGetGeometryN_r( geosinit()->ctxt, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
2645 else
2646 currentReshapeGeometry = reshapePolygon( GEOSGetGeometryN_r( geosinit()->ctxt, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
2647
2648 if ( currentReshapeGeometry )
2649 {
2650 newGeoms[i] = currentReshapeGeometry.release();
2651 reshapeTookPlace = true;
2652 }
2653 else
2654 {
2655 newGeoms[i] = GEOSGeom_clone_r( geosinit()->ctxt, GEOSGetGeometryN_r( geosinit()->ctxt, mGeos.get(), i ) );
2656 }
2657 }
2658
2659 geos::unique_ptr newMultiGeom;
2660 if ( isLine )
2661 {
2662 newMultiGeom.reset( GEOSGeom_createCollection_r( geosinit()->ctxt, GEOS_MULTILINESTRING, newGeoms, numGeoms ) );
2663 }
2664 else //multipolygon
2665 {
2666 newMultiGeom.reset( GEOSGeom_createCollection_r( geosinit()->ctxt, GEOS_MULTIPOLYGON, newGeoms, numGeoms ) );
2667 }
2668
2669 delete[] newGeoms;
2670 if ( !newMultiGeom )
2671 {
2672 if ( errorCode ) { *errorCode = EngineError; }
2673 return nullptr;
2674 }
2675
2676 if ( reshapeTookPlace )
2677 {
2678 if ( errorCode )
2679 *errorCode = Success;
2680 std::unique_ptr< QgsAbstractGeometry > reshapedMultiGeom = fromGeos( newMultiGeom.get() );
2681 return reshapedMultiGeom;
2682 }
2683 else
2684 {
2685 if ( errorCode )
2686 {
2687 *errorCode = NothingHappened;
2688 }
2689 return nullptr;
2690 }
2691 }
2692 CATCH_GEOS_WITH_ERRMSG( nullptr )
2693 }
2694}
2695
2696QgsGeometry QgsGeos::mergeLines( QString *errorMsg ) const
2697{
2698 if ( !mGeos )
2699 {
2700 return QgsGeometry();
2701 }
2702
2703 if ( GEOSGeomTypeId_r( geosinit()->ctxt, mGeos.get() ) != GEOS_MULTILINESTRING )
2704 return QgsGeometry();
2705
2707 try
2708 {
2709 geos.reset( GEOSLineMerge_r( geosinit()->ctxt, mGeos.get() ) );
2710 }
2712 return QgsGeometry( fromGeos( geos.get() ) );
2713}
2714
2715QgsGeometry QgsGeos::closestPoint( const QgsGeometry &other, QString *errorMsg ) const
2716{
2717 if ( !mGeos || isEmpty() || other.isEmpty() )
2718 {
2719 return QgsGeometry();
2720 }
2721
2722 geos::unique_ptr otherGeom( asGeos( other.constGet(), mPrecision ) );
2723 if ( !otherGeom )
2724 {
2725 return QgsGeometry();
2726 }
2727
2728 double nx = 0.0;
2729 double ny = 0.0;
2730 try
2731 {
2733 if ( mGeosPrepared ) // use faster version with prepared geometry
2734 {
2735 nearestCoord.reset( GEOSPreparedNearestPoints_r( geosinit()->ctxt, mGeosPrepared.get(), otherGeom.get() ) );
2736 }
2737 else
2738 {
2739 nearestCoord.reset( GEOSNearestPoints_r( geosinit()->ctxt, mGeos.get(), otherGeom.get() ) );
2740 }
2741
2742 ( void )GEOSCoordSeq_getX_r( geosinit()->ctxt, nearestCoord.get(), 0, &nx );
2743 ( void )GEOSCoordSeq_getY_r( geosinit()->ctxt, nearestCoord.get(), 0, &ny );
2744 }
2745 catch ( GEOSException &e )
2746 {
2747 logError( QStringLiteral( "GEOS" ), e.what() );
2748 if ( errorMsg )
2749 {
2750 *errorMsg = e.what();
2751 }
2752 return QgsGeometry();
2753 }
2754
2755 return QgsGeometry( new QgsPoint( nx, ny ) );
2756}
2757
2758QgsGeometry QgsGeos::shortestLine( const QgsGeometry &other, QString *errorMsg ) const
2759{
2760 if ( !mGeos || other.isEmpty() )
2761 {
2762 return QgsGeometry();
2763 }
2764
2765 return shortestLine( other.constGet(), errorMsg );
2766}
2767
2768QgsGeometry QgsGeos::shortestLine( const QgsAbstractGeometry *other, QString *errorMsg ) const
2769{
2770 if ( !other || other->isEmpty() )
2771 return QgsGeometry();
2772
2773 geos::unique_ptr otherGeom( asGeos( other, mPrecision ) );
2774 if ( !otherGeom )
2775 {
2776 return QgsGeometry();
2777 }
2778
2779 double nx1 = 0.0;
2780 double ny1 = 0.0;
2781 double nx2 = 0.0;
2782 double ny2 = 0.0;
2783 try
2784 {
2785 geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( geosinit()->ctxt, mGeos.get(), otherGeom.get() ) );
2786
2787 if ( !nearestCoord )
2788 {
2789 if ( errorMsg )
2790 *errorMsg = QStringLiteral( "GEOS returned no nearest points" );
2791 return QgsGeometry();
2792 }
2793
2794 ( void )GEOSCoordSeq_getX_r( geosinit()->ctxt, nearestCoord.get(), 0, &nx1 );
2795 ( void )GEOSCoordSeq_getY_r( geosinit()->ctxt, nearestCoord.get(), 0, &ny1 );
2796 ( void )GEOSCoordSeq_getX_r( geosinit()->ctxt, nearestCoord.get(), 1, &nx2 );
2797 ( void )GEOSCoordSeq_getY_r( geosinit()->ctxt, nearestCoord.get(), 1, &ny2 );
2798 }
2799 catch ( GEOSException &e )
2800 {
2801 logError( QStringLiteral( "GEOS" ), e.what() );
2802 if ( errorMsg )
2803 {
2804 *errorMsg = e.what();
2805 }
2806 return QgsGeometry();
2807 }
2808
2809 QgsLineString *line = new QgsLineString();
2810 line->addVertex( QgsPoint( nx1, ny1 ) );
2811 line->addVertex( QgsPoint( nx2, ny2 ) );
2812 return QgsGeometry( line );
2813}
2814
2815double QgsGeos::lineLocatePoint( const QgsPoint &point, QString *errorMsg ) const
2816{
2817 if ( !mGeos )
2818 {
2819 return -1;
2820 }
2821
2822 geos::unique_ptr otherGeom( asGeos( &point, mPrecision ) );
2823 if ( !otherGeom )
2824 {
2825 return -1;
2826 }
2827
2828 double distance = -1;
2829 try
2830 {
2831 distance = GEOSProject_r( geosinit()->ctxt, mGeos.get(), otherGeom.get() );
2832 }
2833 catch ( GEOSException &e )
2834 {
2835 logError( QStringLiteral( "GEOS" ), e.what() );
2836 if ( errorMsg )
2837 {
2838 *errorMsg = e.what();
2839 }
2840 return -1;
2841 }
2842
2843 return distance;
2844}
2845
2846double QgsGeos::lineLocatePoint( double x, double y, QString *errorMsg ) const
2847{
2848 if ( !mGeos )
2849 {
2850 return -1;
2851 }
2852
2853 geos::unique_ptr point = createGeosPointXY( x, y, false, 0, false, 0, 2, 0 );
2854 if ( !point )
2855 return false;
2856
2857 double distance = -1;
2858 try
2859 {
2860 distance = GEOSProject_r( geosinit()->ctxt, mGeos.get(), point.get() );
2861 }
2862 catch ( GEOSException &e )
2863 {
2864 logError( QStringLiteral( "GEOS" ), e.what() );
2865 if ( errorMsg )
2866 {
2867 *errorMsg = e.what();
2868 }
2869 return -1;
2870 }
2871
2872 return distance;
2873}
2874
2875QgsGeometry QgsGeos::polygonize( const QVector<const QgsAbstractGeometry *> &geometries, QString *errorMsg )
2876{
2877 GEOSGeometry **const lineGeosGeometries = new GEOSGeometry*[ geometries.size()];
2878 int validLines = 0;
2879 for ( const QgsAbstractGeometry *g : geometries )
2880 {
2881 geos::unique_ptr l = asGeos( g );
2882 if ( l )
2883 {
2884 lineGeosGeometries[validLines] = l.release();
2885 validLines++;
2886 }
2887 }
2888
2889 try
2890 {
2891 geos::unique_ptr result( GEOSPolygonize_r( geosinit()->ctxt, lineGeosGeometries, validLines ) );
2892 for ( int i = 0; i < validLines; ++i )
2893 {
2894 GEOSGeom_destroy_r( geosinit()->ctxt, lineGeosGeometries[i] );
2895 }
2896 delete[] lineGeosGeometries;
2897 return QgsGeometry( fromGeos( result.get() ) );
2898 }
2899 catch ( GEOSException &e )
2900 {
2901 if ( errorMsg )
2902 {
2903 *errorMsg = e.what();
2904 }
2905 for ( int i = 0; i < validLines; ++i )
2906 {
2907 GEOSGeom_destroy_r( geosinit()->ctxt, lineGeosGeometries[i] );
2908 }
2909 delete[] lineGeosGeometries;
2910 return QgsGeometry();
2911 }
2912}
2913
2914QgsGeometry QgsGeos::voronoiDiagram( const QgsAbstractGeometry *extent, double tolerance, bool edgesOnly, QString *errorMsg ) const
2915{
2916 if ( !mGeos )
2917 {
2918 return QgsGeometry();
2919 }
2920
2921 geos::unique_ptr extentGeosGeom;
2922 if ( extent )
2923 {
2924 extentGeosGeom = asGeos( extent, mPrecision );
2925 if ( !extentGeosGeom )
2926 {
2927 return QgsGeometry();
2928 }
2929 }
2930
2932 try
2933 {
2934 geos.reset( GEOSVoronoiDiagram_r( geosinit()->ctxt, mGeos.get(), extentGeosGeom.get(), tolerance, edgesOnly ) );
2935
2936 if ( !geos || GEOSisEmpty_r( geosinit()->ctxt, geos.get() ) != 0 )
2937 {
2938 return QgsGeometry();
2939 }
2940
2941 return QgsGeometry( fromGeos( geos.get() ) );
2942 }
2944}
2945
2946QgsGeometry QgsGeos::delaunayTriangulation( double tolerance, bool edgesOnly, QString *errorMsg ) const
2947{
2948 if ( !mGeos )
2949 {
2950 return QgsGeometry();
2951 }
2952
2954 try
2955 {
2956 geos.reset( GEOSDelaunayTriangulation_r( geosinit()->ctxt, mGeos.get(), tolerance, edgesOnly ) );
2957
2958 if ( !geos || GEOSisEmpty_r( geosinit()->ctxt, geos.get() ) != 0 )
2959 {
2960 return QgsGeometry();
2961 }
2962
2963 return QgsGeometry( fromGeos( geos.get() ) );
2964 }
2966}
2967
2968
2970static bool _linestringEndpoints( const GEOSGeometry *linestring, double &x1, double &y1, double &x2, double &y2 )
2971{
2972 const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, linestring );
2973 if ( !coordSeq )
2974 return false;
2975
2976 unsigned int coordSeqSize;
2977 if ( GEOSCoordSeq_getSize_r( geosinit()->ctxt, coordSeq, &coordSeqSize ) == 0 )
2978 return false;
2979
2980 if ( coordSeqSize < 2 )
2981 return false;
2982
2983 GEOSCoordSeq_getX_r( geosinit()->ctxt, coordSeq, 0, &x1 );
2984 GEOSCoordSeq_getY_r( geosinit()->ctxt, coordSeq, 0, &y1 );
2985 GEOSCoordSeq_getX_r( geosinit()->ctxt, coordSeq, coordSeqSize - 1, &x2 );
2986 GEOSCoordSeq_getY_r( geosinit()->ctxt, coordSeq, coordSeqSize - 1, &y2 );
2987 return true;
2988}
2989
2990
2992static geos::unique_ptr _mergeLinestrings( const GEOSGeometry *line1, const GEOSGeometry *line2, const QgsPointXY &intersectionPoint )
2993{
2994 double x1, y1, x2, y2;
2995 if ( !_linestringEndpoints( line1, x1, y1, x2, y2 ) )
2996 return nullptr;
2997
2998 double rx1, ry1, rx2, ry2;
2999 if ( !_linestringEndpoints( line2, rx1, ry1, rx2, ry2 ) )
3000 return nullptr;
3001
3002 bool intersectionAtOrigLineEndpoint =
3003 ( intersectionPoint.x() == x1 && intersectionPoint.y() == y1 ) !=
3004 ( intersectionPoint.x() == x2 && intersectionPoint.y() == y2 );
3005 bool intersectionAtReshapeLineEndpoint =
3006 ( intersectionPoint.x() == rx1 && intersectionPoint.y() == ry1 ) ||
3007 ( intersectionPoint.x() == rx2 && intersectionPoint.y() == ry2 );
3008
3009 // the intersection must be at the begin/end of both lines
3010 if ( intersectionAtOrigLineEndpoint && intersectionAtReshapeLineEndpoint )
3011 {
3012 geos::unique_ptr g1( GEOSGeom_clone_r( geosinit()->ctxt, line1 ) );
3013 geos::unique_ptr g2( GEOSGeom_clone_r( geosinit()->ctxt, line2 ) );
3014 GEOSGeometry *geoms[2] = { g1.release(), g2.release() };
3015 geos::unique_ptr multiGeom( GEOSGeom_createCollection_r( geosinit()->ctxt, GEOS_MULTILINESTRING, geoms, 2 ) );
3016 geos::unique_ptr res( GEOSLineMerge_r( geosinit()->ctxt, multiGeom.get() ) );
3017 return res;
3018 }
3019 else
3020 return nullptr;
3021}
3022
3023
3024geos::unique_ptr QgsGeos::reshapeLine( const GEOSGeometry *line, const GEOSGeometry *reshapeLineGeos, double precision )
3025{
3026 if ( !line || !reshapeLineGeos )
3027 return nullptr;
3028
3029 bool atLeastTwoIntersections = false;
3030 bool oneIntersection = false;
3031 QgsPointXY oneIntersectionPoint;
3032
3033 try
3034 {
3035 //make sure there are at least two intersection between line and reshape geometry
3036 geos::unique_ptr intersectGeom( GEOSIntersection_r( geosinit()->ctxt, line, reshapeLineGeos ) );
3037 if ( intersectGeom )
3038 {
3039 const int geomType = GEOSGeomTypeId_r( geosinit()->ctxt, intersectGeom.get() );
3040 atLeastTwoIntersections = ( geomType == GEOS_MULTIPOINT && GEOSGetNumGeometries_r( geosinit()->ctxt, intersectGeom.get() ) > 1 )
3041 || ( geomType == GEOS_GEOMETRYCOLLECTION && GEOSGetNumGeometries_r( geosinit()->ctxt, intersectGeom.get() ) > 0 ) // a collection implies at least two points!
3042 || ( geomType == GEOS_MULTILINESTRING && GEOSGetNumGeometries_r( geosinit()->ctxt, intersectGeom.get() ) > 0 );
3043 // one point is enough when extending line at its endpoint
3044 if ( GEOSGeomTypeId_r( geosinit()->ctxt, intersectGeom.get() ) == GEOS_POINT )
3045 {
3046 const GEOSCoordSequence *intersectionCoordSeq = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, intersectGeom.get() );
3047 double xi, yi;
3048 GEOSCoordSeq_getX_r( geosinit()->ctxt, intersectionCoordSeq, 0, &xi );
3049 GEOSCoordSeq_getY_r( geosinit()->ctxt, intersectionCoordSeq, 0, &yi );
3050 oneIntersection = true;
3051 oneIntersectionPoint = QgsPointXY( xi, yi );
3052 }
3053 }
3054 }
3055 catch ( GEOSException & )
3056 {
3057 atLeastTwoIntersections = false;
3058 }
3059
3060 // special case when extending line at its endpoint
3061 if ( oneIntersection )
3062 return _mergeLinestrings( line, reshapeLineGeos, oneIntersectionPoint );
3063
3064 if ( !atLeastTwoIntersections )
3065 return nullptr;
3066
3067 //begin and end point of original line
3068 double x1, y1, x2, y2;
3069 if ( !_linestringEndpoints( line, x1, y1, x2, y2 ) )
3070 return nullptr;
3071
3072 geos::unique_ptr beginLineVertex = createGeosPointXY( x1, y1, false, 0, false, 0, 2, precision );
3073 geos::unique_ptr endLineVertex = createGeosPointXY( x2, y2, false, 0, false, 0, 2, precision );
3074
3075 bool isRing = false;
3076 if ( GEOSGeomTypeId_r( geosinit()->ctxt, line ) == GEOS_LINEARRING
3077 || GEOSEquals_r( geosinit()->ctxt, beginLineVertex.get(), endLineVertex.get() ) == 1 )
3078 isRing = true;
3079
3080 //node line and reshape line
3081 geos::unique_ptr nodedGeometry = nodeGeometries( reshapeLineGeos, line );
3082 if ( !nodedGeometry )
3083 {
3084 return nullptr;
3085 }
3086
3087 //and merge them together
3088 geos::unique_ptr mergedLines( GEOSLineMerge_r( geosinit()->ctxt, nodedGeometry.get() ) );
3089 if ( !mergedLines )
3090 {
3091 return nullptr;
3092 }
3093
3094 int numMergedLines = GEOSGetNumGeometries_r( geosinit()->ctxt, mergedLines.get() );
3095 if ( numMergedLines < 2 ) //some special cases. Normally it is >2
3096 {
3097 if ( numMergedLines == 1 ) //reshape line is from begin to endpoint. So we keep the reshapeline
3098 {
3099 geos::unique_ptr result( GEOSGeom_clone_r( geosinit()->ctxt, reshapeLineGeos ) );
3100 return result;
3101 }
3102 else
3103 return nullptr;
3104 }
3105
3106 QVector<GEOSGeometry *> resultLineParts; //collection with the line segments that will be contained in result
3107 QVector<GEOSGeometry *> probableParts; //parts where we can decide on inclusion only after going through all the candidates
3108
3109 for ( int i = 0; i < numMergedLines; ++i )
3110 {
3111 const GEOSGeometry *currentGeom = GEOSGetGeometryN_r( geosinit()->ctxt, mergedLines.get(), i );
3112
3113 // have we already added this part?
3114 bool alreadyAdded = false;
3115 double distance = 0;
3116 double bufferDistance = std::pow( 10.0L, geomDigits( currentGeom ) - 11 );
3117 for ( const GEOSGeometry *other : std::as_const( resultLineParts ) )
3118 {
3119 GEOSHausdorffDistance_r( geosinit()->ctxt, currentGeom, other, &distance );
3120 if ( distance < bufferDistance )
3121 {
3122 alreadyAdded = true;
3123 break;
3124 }
3125 }
3126 if ( alreadyAdded )
3127 continue;
3128
3129 const GEOSCoordSequence *currentCoordSeq = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, currentGeom );
3130 unsigned int currentCoordSeqSize;
3131 GEOSCoordSeq_getSize_r( geosinit()->ctxt, currentCoordSeq, &currentCoordSeqSize );
3132 if ( currentCoordSeqSize < 2 )
3133 continue;
3134
3135 //get the two endpoints of the current line merge result
3136 double xBegin, xEnd, yBegin, yEnd;
3137 GEOSCoordSeq_getX_r( geosinit()->ctxt, currentCoordSeq, 0, &xBegin );
3138 GEOSCoordSeq_getY_r( geosinit()->ctxt, currentCoordSeq, 0, &yBegin );
3139 GEOSCoordSeq_getX_r( geosinit()->ctxt, currentCoordSeq, currentCoordSeqSize - 1, &xEnd );
3140 GEOSCoordSeq_getY_r( geosinit()->ctxt, currentCoordSeq, currentCoordSeqSize - 1, &yEnd );
3141 geos::unique_ptr beginCurrentGeomVertex = createGeosPointXY( xBegin, yBegin, false, 0, false, 0, 2, precision );
3142 geos::unique_ptr endCurrentGeomVertex = createGeosPointXY( xEnd, yEnd, false, 0, false, 0, 2, precision );
3143
3144 //check how many endpoints of the line merge result are on the (original) line
3145 int nEndpointsOnOriginalLine = 0;
3146 if ( pointContainedInLine( beginCurrentGeomVertex.get(), line ) == 1 )
3147 nEndpointsOnOriginalLine += 1;
3148
3149 if ( pointContainedInLine( endCurrentGeomVertex.get(), line ) == 1 )
3150 nEndpointsOnOriginalLine += 1;
3151
3152 //check how many endpoints equal the endpoints of the original line
3153 int nEndpointsSameAsOriginalLine = 0;
3154 if ( GEOSEquals_r( geosinit()->ctxt, beginCurrentGeomVertex.get(), beginLineVertex.get() ) == 1
3155 || GEOSEquals_r( geosinit()->ctxt, beginCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
3156 nEndpointsSameAsOriginalLine += 1;
3157
3158 if ( GEOSEquals_r( geosinit()->ctxt, endCurrentGeomVertex.get(), beginLineVertex.get() ) == 1
3159 || GEOSEquals_r( geosinit()->ctxt, endCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
3160 nEndpointsSameAsOriginalLine += 1;
3161
3162 //check if the current geometry overlaps the original geometry (GEOSOverlap does not seem to work with linestrings)
3163 bool currentGeomOverlapsOriginalGeom = false;
3164 bool currentGeomOverlapsReshapeLine = false;
3165 if ( lineContainedInLine( currentGeom, line ) == 1 )
3166 currentGeomOverlapsOriginalGeom = true;
3167
3168 if ( lineContainedInLine( currentGeom, reshapeLineGeos ) == 1 )
3169 currentGeomOverlapsReshapeLine = true;
3170
3171 //logic to decide if this part belongs to the result
3172 if ( !isRing && nEndpointsSameAsOriginalLine == 1 && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
3173 {
3174 resultLineParts.push_back( GEOSGeom_clone_r( geosinit()->ctxt, currentGeom ) );
3175 }
3176 //for closed rings, we take one segment from the candidate list
3177 else if ( isRing && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
3178 {
3179 probableParts.push_back( GEOSGeom_clone_r( geosinit()->ctxt, currentGeom ) );
3180 }
3181 else if ( nEndpointsOnOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
3182 {
3183 resultLineParts.push_back( GEOSGeom_clone_r( geosinit()->ctxt, currentGeom ) );
3184 }
3185 else if ( nEndpointsSameAsOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
3186 {
3187 resultLineParts.push_back( GEOSGeom_clone_r( geosinit()->ctxt, currentGeom ) );
3188 }
3189 else if ( currentGeomOverlapsOriginalGeom && currentGeomOverlapsReshapeLine )
3190 {
3191 resultLineParts.push_back( GEOSGeom_clone_r( geosinit()->ctxt, currentGeom ) );
3192 }
3193 }
3194
3195 //add the longest segment from the probable list for rings (only used for polygon rings)
3196 if ( isRing && !probableParts.isEmpty() )
3197 {
3198 geos::unique_ptr maxGeom; //the longest geometry in the probabla list
3199 GEOSGeometry *currentGeom = nullptr;
3200 double maxLength = -std::numeric_limits<double>::max();
3201 double currentLength = 0;
3202 for ( int i = 0; i < probableParts.size(); ++i )
3203 {
3204 currentGeom = probableParts.at( i );
3205 GEOSLength_r( geosinit()->ctxt, currentGeom, &currentLength );
3206 if ( currentLength > maxLength )
3207 {
3208 maxLength = currentLength;
3209 maxGeom.reset( currentGeom );
3210 }
3211 else
3212 {
3213 GEOSGeom_destroy_r( geosinit()->ctxt, currentGeom );
3214 }
3215 }
3216 resultLineParts.push_back( maxGeom.release() );
3217 }
3218
3219 geos::unique_ptr result;
3220 if ( resultLineParts.empty() )
3221 return nullptr;
3222
3223 if ( resultLineParts.size() == 1 ) //the whole result was reshaped
3224 {
3225 result.reset( resultLineParts[0] );
3226 }
3227 else //>1
3228 {
3229 GEOSGeometry **lineArray = new GEOSGeometry*[resultLineParts.size()];
3230 for ( int i = 0; i < resultLineParts.size(); ++i )
3231 {
3232 lineArray[i] = resultLineParts[i];
3233 }
3234
3235 //create multiline from resultLineParts
3236 geos::unique_ptr multiLineGeom( GEOSGeom_createCollection_r( geosinit()->ctxt, GEOS_MULTILINESTRING, lineArray, resultLineParts.size() ) );
3237 delete [] lineArray;
3238
3239 //then do a linemerge with the newly combined partstrings
3240 result.reset( GEOSLineMerge_r( geosinit()->ctxt, multiLineGeom.get() ) );
3241 }
3242
3243 //now test if the result is a linestring. Otherwise something went wrong
3244 if ( GEOSGeomTypeId_r( geosinit()->ctxt, result.get() ) != GEOS_LINESTRING )
3245 {
3246 return nullptr;
3247 }
3248
3249 return result;
3250}
3251
3252geos::unique_ptr QgsGeos::reshapePolygon( const GEOSGeometry *polygon, const GEOSGeometry *reshapeLineGeos, double precision )
3253{
3254 //go through outer shell and all inner rings and check if there is exactly one intersection of a ring and the reshape line
3255 int nIntersections = 0;
3256 int lastIntersectingRing = -2;
3257 const GEOSGeometry *lastIntersectingGeom = nullptr;
3258
3259 int nRings = GEOSGetNumInteriorRings_r( geosinit()->ctxt, polygon );
3260 if ( nRings < 0 )
3261 return nullptr;
3262
3263 //does outer ring intersect?
3264 const GEOSGeometry *outerRing = GEOSGetExteriorRing_r( geosinit()->ctxt, polygon );
3265 if ( GEOSIntersects_r( geosinit()->ctxt, outerRing, reshapeLineGeos ) == 1 )
3266 {
3267 ++nIntersections;
3268 lastIntersectingRing = -1;
3269 lastIntersectingGeom = outerRing;
3270 }
3271
3272 //do inner rings intersect?
3273 const GEOSGeometry **innerRings = new const GEOSGeometry*[nRings];
3274
3275 try
3276 {
3277 for ( int i = 0; i < nRings; ++i )
3278 {
3279 innerRings[i] = GEOSGetInteriorRingN_r( geosinit()->ctxt, polygon, i );
3280 if ( GEOSIntersects_r( geosinit()->ctxt, innerRings[i], reshapeLineGeos ) == 1 )
3281 {
3282 ++nIntersections;
3283 lastIntersectingRing = i;
3284 lastIntersectingGeom = innerRings[i];
3285 }
3286 }
3287 }
3288 catch ( GEOSException & )
3289 {
3290 nIntersections = 0;
3291 }
3292
3293 if ( nIntersections != 1 ) //reshape line is only allowed to intersect one ring
3294 {
3295 delete [] innerRings;
3296 return nullptr;
3297 }
3298
3299 //we have one intersecting ring, let's try to reshape it
3300 geos::unique_ptr reshapeResult = reshapeLine( lastIntersectingGeom, reshapeLineGeos, precision );
3301 if ( !reshapeResult )
3302 {
3303 delete [] innerRings;
3304 return nullptr;
3305 }
3306
3307 //if reshaping took place, we need to reassemble the polygon and its rings
3308 GEOSGeometry *newRing = nullptr;
3309 const GEOSCoordSequence *reshapeSequence = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, reshapeResult.get() );
3310 GEOSCoordSequence *newCoordSequence = GEOSCoordSeq_clone_r( geosinit()->ctxt, reshapeSequence );
3311
3312 reshapeResult.reset();
3313
3314 newRing = GEOSGeom_createLinearRing_r( geosinit()->ctxt, newCoordSequence );
3315 if ( !newRing )
3316 {
3317 delete [] innerRings;
3318 return nullptr;
3319 }
3320
3321 GEOSGeometry *newOuterRing = nullptr;
3322 if ( lastIntersectingRing == -1 )
3323 newOuterRing = newRing;
3324 else
3325 newOuterRing = GEOSGeom_clone_r( geosinit()->ctxt, outerRing );
3326
3327 //check if all the rings are still inside the outer boundary
3328 QVector<GEOSGeometry *> ringList;
3329 if ( nRings > 0 )
3330 {
3331 GEOSGeometry *outerRingPoly = GEOSGeom_createPolygon_r( geosinit()->ctxt, GEOSGeom_clone_r( geosinit()->ctxt, newOuterRing ), nullptr, 0 );
3332 if ( outerRingPoly )
3333 {
3334 ringList.reserve( nRings );
3335 GEOSGeometry *currentRing = nullptr;
3336 for ( int i = 0; i < nRings; ++i )
3337 {
3338 if ( lastIntersectingRing == i )
3339 currentRing = newRing;
3340 else
3341 currentRing = GEOSGeom_clone_r( geosinit()->ctxt, innerRings[i] );
3342
3343 //possibly a ring is no longer contained in the result polygon after reshape
3344 if ( GEOSContains_r( geosinit()->ctxt, outerRingPoly, currentRing ) == 1 )
3345 ringList.push_back( currentRing );
3346 else
3347 GEOSGeom_destroy_r( geosinit()->ctxt, currentRing );
3348 }
3349 }
3350 GEOSGeom_destroy_r( geosinit()->ctxt, outerRingPoly );
3351 }
3352
3353 GEOSGeometry **newInnerRings = new GEOSGeometry*[ringList.size()];
3354 for ( int i = 0; i < ringList.size(); ++i )
3355 newInnerRings[i] = ringList.at( i );
3356
3357 delete [] innerRings;
3358
3359 geos::unique_ptr reshapedPolygon( GEOSGeom_createPolygon_r( geosinit()->ctxt, newOuterRing, newInnerRings, ringList.size() ) );
3360 delete[] newInnerRings;
3361
3362 return reshapedPolygon;
3363}
3364
3365int QgsGeos::lineContainedInLine( const GEOSGeometry *line1, const GEOSGeometry *line2 )
3366{
3367 if ( !line1 || !line2 )
3368 {
3369 return -1;
3370 }
3371
3372 double bufferDistance = std::pow( 10.0L, geomDigits( line2 ) - 11 );
3373
3374 geos::unique_ptr bufferGeom( GEOSBuffer_r( geosinit()->ctxt, line2, bufferDistance, DEFAULT_QUADRANT_SEGMENTS ) );
3375 if ( !bufferGeom )
3376 return -2;
3377
3378 geos::unique_ptr intersectionGeom( GEOSIntersection_r( geosinit()->ctxt, bufferGeom.get(), line1 ) );
3379
3380 //compare ratio between line1Length and intersectGeomLength (usually close to 1 if line1 is contained in line2)
3381 double intersectGeomLength;
3382 double line1Length;
3383
3384 GEOSLength_r( geosinit()->ctxt, intersectionGeom.get(), &intersectGeomLength );
3385 GEOSLength_r( geosinit()->ctxt, line1, &line1Length );
3386
3387 double intersectRatio = line1Length / intersectGeomLength;
3388 if ( intersectRatio > 0.9 && intersectRatio < 1.1 )
3389 return 1;
3390
3391 return 0;
3392}
3393
3394int QgsGeos::pointContainedInLine( const GEOSGeometry *point, const GEOSGeometry *line )
3395{
3396 if ( !point || !line )
3397 return -1;
3398
3399 double bufferDistance = std::pow( 10.0L, geomDigits( line ) - 11 );
3400
3401 geos::unique_ptr lineBuffer( GEOSBuffer_r( geosinit()->ctxt, line, bufferDistance, 8 ) );
3402 if ( !lineBuffer )
3403 return -2;
3404
3405 bool contained = false;
3406 if ( GEOSContains_r( geosinit()->ctxt, lineBuffer.get(), point ) == 1 )
3407 contained = true;
3408
3409 return contained;
3410}
3411
3412int QgsGeos::geomDigits( const GEOSGeometry *geom )
3413{
3414 geos::unique_ptr bbox( GEOSEnvelope_r( geosinit()->ctxt, geom ) );
3415 if ( !bbox.get() )
3416 return -1;
3417
3418 const GEOSGeometry *bBoxRing = GEOSGetExteriorRing_r( geosinit()->ctxt, bbox.get() );
3419 if ( !bBoxRing )
3420 return -1;
3421
3422 const GEOSCoordSequence *bBoxCoordSeq = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, bBoxRing );
3423
3424 if ( !bBoxCoordSeq )
3425 return -1;
3426
3427 unsigned int nCoords = 0;
3428 if ( !GEOSCoordSeq_getSize_r( geosinit()->ctxt, bBoxCoordSeq, &nCoords ) )
3429 return -1;
3430
3431 int maxDigits = -1;
3432 for ( unsigned int i = 0; i < nCoords - 1; ++i )
3433 {
3434 double t;
3435 GEOSCoordSeq_getX_r( geosinit()->ctxt, bBoxCoordSeq, i, &t );
3436
3437 int digits;
3438 digits = std::ceil( std::log10( std::fabs( t ) ) );
3439 if ( digits > maxDigits )
3440 maxDigits = digits;
3441
3442 GEOSCoordSeq_getY_r( geosinit()->ctxt, bBoxCoordSeq, i, &t );
3443 digits = std::ceil( std::log10( std::fabs( t ) ) );
3444 if ( digits > maxDigits )
3445 maxDigits = digits;
3446 }
3447
3448 return maxDigits;
3449}
3450
3451GEOSContextHandle_t QgsGeos::getGEOSHandler()
3452{
3453 return geosinit()->ctxt;
3454}
The Qgis class provides global constants for use throughout the application.
Definition: qgis.h:54
BufferSide
Side of line to buffer.
Definition: qgis.h:1477
GeometryOperationResult
Success or failure of a geometry operation.
Definition: qgis.h:1424
@ AddPartNotMultiGeometry
The source geometry is not multi.
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
JoinStyle
Join styles for buffers.
Definition: qgis.h:1502
EndCapStyle
End cap styles for buffers.
Definition: qgis.h:1489
MakeValidMethod
Algorithms to use when repairing invalid geometries.
Definition: qgis.h:1515
@ 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:154
@ PointM
PointM.
@ PointZ
PointZ.
@ GeometryCollection
GeometryCollection.
@ PointZM
PointZM.
Abstract base class for all geometries.
bool is3D() const SIP_HOLDGIL
Returns true if the geometry is 3D and contains a z-value.
virtual QgsRectangle boundingBox() const =0
Returns the minimal bounding box for the geometry.
virtual bool isEmpty() const
Returns true if the geometry is empty.
Qgis::WkbType wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
virtual int dimension() const =0
Returns the inherent dimension of the geometry.
bool isMeasure() const SIP_HOLDGIL
Returns true if the geometry contains m values.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
Curve polygon geometry type.
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
int numInteriorRings() const SIP_HOLDGIL
Returns the number of interior rings contained with the curve polygon.
Abstract base class for curved geometry type.
Definition: qgscurve.h:36
virtual QgsLineString * curveToLine(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const =0
Returns a new line string geometry corresponding to a segmentized approximation of the curve.
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:111
double gridSize() const
Returns the grid size which will be used to snap vertices of a geometry.
Definition: qgsgeometry.h:124
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Q_GADGET bool isNull
Definition: qgsgeometry.h:166
QgsAbstractGeometry * get()
Returns a modifiable (non-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:99
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:2946
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:2032
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
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:1862
QgsAbstractGeometry * concaveHull(double targetPercent, bool allowHoles=false, QString *errorMsg=nullptr) const
Returns a possibly concave geometry that encloses the input geometry.
Definition: qgsgeos.cpp:2009
std::unique_ptr< QgsAbstractGeometry > reshapeGeometry(const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg=nullptr) const
Reshapes the geometry using a line.
Definition: qgsgeos.cpp:2580
std::unique_ptr< QgsAbstractGeometry > maximumInscribedCircle(double tolerance, QString *errorMsg=nullptr) const
Returns the maximum inscribed circle.
Definition: qgsgeos.cpp:2458
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:2560
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:2544
double minimumClearance(QString *errorMsg=nullptr) const
Computes the minimum clearance of a geometry.
Definition: qgsgeos.cpp:2510
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:2432
bool disjoint(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom is disjoint from this.
Definition: qgsgeos.cpp:759
QgsGeos(const QgsAbstractGeometry *geometry, double precision=0)
GEOS geometry engine constructor.
Definition: qgsgeos.cpp:142
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:2758
QgsAbstractGeometry * simplify(double tolerance, QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:1894
QgsGeometry closestPoint(const QgsGeometry &other, QString *errorMsg=nullptr) const
Returns the closest point on the geometry to the other geometry.
Definition: qgsgeos.cpp:2715
static std::unique_ptr< QgsPolygon > fromGeosPolygon(const GEOSGeometry *geos)
Definition: qgsgeos.cpp:1488
std::unique_ptr< QgsAbstractGeometry > minimumClearanceLine(QString *errorMsg=nullptr) const
Returns a LineString whose endpoints define the minimum clearance of a geometry.
Definition: qgsgeos.cpp:2528
QgsAbstractGeometry * envelope(QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:1950
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:1993
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:2494
bool isSimple(QString *errorMsg=nullptr) const override
Determines whether the geometry is simple (according to OGC definition).
Definition: qgsgeos.cpp:2124
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:1388
QgsAbstractGeometry * interpolate(double distance, QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:1909
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:2090
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
QgsGeometry mergeLines(QString *errorMsg=nullptr) const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
Definition: qgsgeos.cpp:2696
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:2875
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:2413
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:3451
QgsPoint * centroid(QString *errorMsg=nullptr) const override
Calculates the centroid of this.
Definition: qgsgeos.cpp:1924
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:2815
bool isEmpty(QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:2110
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:2914
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:2474
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:1578
QgsPoint * pointOnSurface(QString *errorMsg=nullptr) const override
Calculate a point that is guaranteed to be on the surface of this.
Definition: qgsgeos.cpp:1965
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 SIP_HOLDGIL
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:30
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:30
Multi polygon geometry collection.
Custom exception class which is raised when an operation is not supported.
Definition: qgsexception.h:119
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
double distance(double x, double y) const SIP_HOLDGIL
Returns the Cartesian 2D distance between this point and a specified x, y coordinate.
Definition: qgspoint.h:343
Q_GADGET double x
Definition: qgspoint.h:52
double z
Definition: qgspoint.h:54
double m
Definition: qgspoint.h:55
double y
Definition: qgspoint.h:53
Polygon geometry type.
Definition: qgspolygon.h:34
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
static Qgis::GeometryType geometryType(Qgis::WkbType type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:865
static bool isMultiType(Qgis::WkbType type) SIP_HOLDGIL
Returns true if the WKB type is a multi type.
Definition: qgswkbtypes.h:759
static Qgis::WkbType flatType(Qgis::WkbType type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:629
Contains geos related utilities and functions.
Definition: qgsgeos.h:37
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:74
std::unique_ptr< GEOSCoordSequence, GeosDeleter > coord_sequence_unique_ptr
Scoped GEOS coordinate sequence pointer.
Definition: qgsgeos.h:89
std::unique_ptr< GEOSBufferParams, GeosDeleter > buffer_params_unique_ptr
Scoped GEOS buffer params pointer.
Definition: qgsgeos.h:84
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:3988
QMap< QString, QString > QgsStringMap
Definition: qgis.h:4533
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:31
int vertex
Vertex number.
Definition: qgsvertexid.h:95
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