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