QGIS API Documentation 3.28.11-Firenze (exported)
Loading...
Searching...
No Matches
qgsgeos.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgeos.cpp
3 -------------------------------------------------------------------
4Date : 22 Sept 2014
5Copyright : (C) 2014 by Marco Hugentobler
6email : marco.hugentobler at sourcepole dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsgeos.h"
17#include "qgsabstractgeometry.h"
19#include "qgsgeometryfactory.h"
20#include "qgslinestring.h"
21#include "qgsmulticurve.h"
22#include "qgsmultilinestring.h"
23#include "qgsmultipoint.h"
24#include "qgsmultipolygon.h"
25#include "qgslogger.h"
26#include "qgspolygon.h"
28#include <limits>
29#include <cstdio>
30
31#define DEFAULT_QUADRANT_SEGMENTS 8
32
33#define CATCH_GEOS(r) \
34 catch (GEOSException &) \
35 { \
36 return r; \
37 }
38
39#define CATCH_GEOS_WITH_ERRMSG(r) \
40 catch (GEOSException &e) \
41 { \
42 if ( errorMsg ) \
43 { \
44 *errorMsg = e.what(); \
45 } \
46 return r; \
47 }
48
50
51static void throwGEOSException( const char *fmt, ... )
52{
53 va_list ap;
54 char buffer[1024];
55
56 va_start( ap, fmt );
57 vsnprintf( buffer, sizeof buffer, fmt, ap );
58 va_end( ap );
59
60 QString message = QString::fromUtf8( buffer );
61
62#ifdef _MSC_VER
63 // stupid stupid MSVC, *SOMETIMES* raises it's own exception if we throw GEOSException, resulting in a crash!
64 // see https://github.com/qgis/QGIS/issues/22709
65 // if you want to test alternative fixes for this, run the testqgsexpression.cpp test suite - that will crash
66 // and burn on the "line_interpolate_point point" test if a GEOSException is thrown.
67 // TODO - find a real fix for the underlying issue
68 try
69 {
70 throw GEOSException( message );
71 }
72 catch ( ... )
73 {
74 // oops, msvc threw an exception when we tried to throw the exception!
75 // just throw nothing instead (except your mouse at your monitor)
76 }
77#else
78 throw GEOSException( message );
79#endif
80}
81
82
83static void printGEOSNotice( const char *fmt, ... )
84{
85#if defined(QGISDEBUG)
86 va_list ap;
87 char buffer[1024];
88
89 va_start( ap, fmt );
90 vsnprintf( buffer, sizeof buffer, fmt, ap );
91 va_end( ap );
92#else
93 Q_UNUSED( fmt )
94#endif
95}
96
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
162
163std::unique_ptr<QgsAbstractGeometry> QgsGeos::makeValid( Qgis::MakeValidMethod method, bool keepCollapsed, QString *errorMsg ) const
164{
165 if ( !mGeos )
166 {
167 return nullptr;
168 }
169
170#if GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR<10
171 if ( method != Qgis::MakeValidMethod::Linework )
172 throw QgsNotSupportedException( QObject::tr( "The structured method to make geometries valid requires a QGIS build based on GEOS 3.10 or later" ) );
173
174 if ( keepCollapsed )
175 throw QgsNotSupportedException( QObject::tr( "The keep collapsed option for making geometries valid requires a QGIS build based on GEOS 3.10 or later" ) );
177 try
178 {
179 geos.reset( GEOSMakeValid_r( geosinit()->ctxt, mGeos.get() ) );
180 }
181 CATCH_GEOS_WITH_ERRMSG( nullptr )
182#else
183
184 GEOSMakeValidParams *params = GEOSMakeValidParams_create_r( geosinit()->ctxt );
185 switch ( method )
186 {
188 GEOSMakeValidParams_setMethod_r( geosinit()->ctxt, params, GEOS_MAKE_VALID_LINEWORK );
189 break;
190
192 GEOSMakeValidParams_setMethod_r( geosinit()->ctxt, params, GEOS_MAKE_VALID_STRUCTURE );
193 break;
194 }
195
196 GEOSMakeValidParams_setKeepCollapsed_r( geosinit()->ctxt,
197 params,
198 keepCollapsed ? 1 : 0 );
199
201 try
202 {
203 geos.reset( GEOSMakeValidWithParams_r( geosinit()->ctxt, mGeos.get(), params ) );
204 GEOSMakeValidParams_destroy_r( geosinit()->ctxt, params );
205 }
206 catch ( GEOSException &e )
207 {
208 if ( errorMsg )
209 {
210 *errorMsg = e.what();
211 }
212 GEOSMakeValidParams_destroy_r( geosinit()->ctxt, params );
213 return nullptr;
214 }
215#endif
216
217 return fromGeos( geos.get() );
218}
219
221{
222 if ( geometry.isNull() )
223 {
224 return nullptr;
225 }
226
227 return asGeos( geometry.constGet(), precision );
228}
229
231{
232 if ( geometry.isNull() )
233 {
235 }
236 if ( !newPart )
237 {
239 }
240
241 std::unique_ptr< QgsAbstractGeometry > geom = fromGeos( newPart );
242 return QgsGeometryEditUtils::addPart( geometry.get(), std::move( geom ) );
243}
244
246{
247 mGeos.reset();
248 mGeosPrepared.reset();
249 cacheGeos();
250}
251
253{
254 if ( mGeosPrepared )
255 {
256 // Already prepared
257 return;
258 }
259 if ( mGeos )
260 {
261 mGeosPrepared.reset( GEOSPrepare_r( geosinit()->ctxt, mGeos.get() ) );
262 }
263}
264
265void QgsGeos::cacheGeos() const
266{
267 if ( mGeos )
268 {
269 // Already cached
270 return;
271 }
272 if ( !mGeometry )
273 {
274 return;
275 }
276
277 mGeos = asGeos( mGeometry, mPrecision );
278}
279
280QgsAbstractGeometry *QgsGeos::intersection( const QgsAbstractGeometry *geom, QString *errorMsg, const QgsGeometryParameters &parameters ) const
281{
282 return overlay( geom, OverlayIntersection, errorMsg, parameters ).release();
283}
284
285QgsAbstractGeometry *QgsGeos::difference( const QgsAbstractGeometry *geom, QString *errorMsg, const QgsGeometryParameters &parameters ) const
286{
287 return overlay( geom, OverlayDifference, errorMsg, parameters ).release();
288}
289
290std::unique_ptr<QgsAbstractGeometry> QgsGeos::clip( const QgsRectangle &rect, QString *errorMsg ) const
291{
292 if ( !mGeos || rect.isNull() || rect.isEmpty() )
293 {
294 return nullptr;
295 }
296
297 try
298 {
299 geos::unique_ptr opGeom( GEOSClipByRect_r( geosinit()->ctxt, mGeos.get(), rect.xMinimum(), rect.yMinimum(), rect.xMaximum(), rect.yMaximum() ) );
300 return fromGeos( opGeom.get() );
301 }
302 catch ( GEOSException &e )
303 {
304 logError( QStringLiteral( "GEOS" ), e.what() );
305 if ( errorMsg )
306 {
307 *errorMsg = e.what();
308 }
309 return nullptr;
310 }
311}
312
313
314
315
316void QgsGeos::subdivideRecursive( const GEOSGeometry *currentPart, int maxNodes, int depth, QgsGeometryCollection *parts, const QgsRectangle &clipRect, double gridSize ) const
317{
318 int partType = GEOSGeomTypeId_r( geosinit()->ctxt, currentPart );
319 if ( qgsDoubleNear( clipRect.width(), 0.0 ) && qgsDoubleNear( clipRect.height(), 0.0 ) )
320 {
321 if ( partType == GEOS_POINT )
322 {
323 parts->addGeometry( fromGeos( currentPart ).release() );
324 return;
325 }
326 else
327 {
328 return;
329 }
330 }
331
332 if ( partType == GEOS_MULTILINESTRING || partType == GEOS_MULTIPOLYGON || partType == GEOS_GEOMETRYCOLLECTION )
333 {
334 int partCount = GEOSGetNumGeometries_r( geosinit()->ctxt, currentPart );
335 for ( int i = 0; i < partCount; ++i )
336 {
337 subdivideRecursive( GEOSGetGeometryN_r( geosinit()->ctxt, currentPart, i ), maxNodes, depth, parts, clipRect, gridSize );
338 }
339 return;
340 }
341
342 if ( depth > 50 )
343 {
344 parts->addGeometry( fromGeos( currentPart ).release() );
345 return;
346 }
347
348 int vertexCount = GEOSGetNumCoordinates_r( geosinit()->ctxt, currentPart );
349 if ( vertexCount == 0 )
350 {
351 return;
352 }
353 else if ( vertexCount < maxNodes )
354 {
355 parts->addGeometry( fromGeos( currentPart ).release() );
356 return;
357 }
358
359 // chop clipping rect in half by longest side
360 double width = clipRect.width();
361 double height = clipRect.height();
362 QgsRectangle halfClipRect1 = clipRect;
363 QgsRectangle halfClipRect2 = clipRect;
364 if ( width > height )
365 {
366 halfClipRect1.setXMaximum( clipRect.xMinimum() + width / 2.0 );
367 halfClipRect2.setXMinimum( halfClipRect1.xMaximum() );
368 }
369 else
370 {
371 halfClipRect1.setYMaximum( clipRect.yMinimum() + height / 2.0 );
372 halfClipRect2.setYMinimum( halfClipRect1.yMaximum() );
373 }
374
375 if ( height <= 0 )
376 {
377 halfClipRect1.setYMinimum( halfClipRect1.yMinimum() - std::numeric_limits<double>::epsilon() );
378 halfClipRect2.setYMinimum( halfClipRect2.yMinimum() - std::numeric_limits<double>::epsilon() );
379 halfClipRect1.setYMaximum( halfClipRect1.yMaximum() + std::numeric_limits<double>::epsilon() );
380 halfClipRect2.setYMaximum( halfClipRect2.yMaximum() + std::numeric_limits<double>::epsilon() );
381 }
382 if ( width <= 0 )
383 {
384 halfClipRect1.setXMinimum( halfClipRect1.xMinimum() - std::numeric_limits<double>::epsilon() );
385 halfClipRect2.setXMinimum( halfClipRect2.xMinimum() - std::numeric_limits<double>::epsilon() );
386 halfClipRect1.setXMaximum( halfClipRect1.xMaximum() + std::numeric_limits<double>::epsilon() );
387 halfClipRect2.setXMaximum( halfClipRect2.xMaximum() + std::numeric_limits<double>::epsilon() );
388 }
389
390 geos::unique_ptr clipPart1( GEOSClipByRect_r( geosinit()->ctxt, currentPart, halfClipRect1.xMinimum(), halfClipRect1.yMinimum(), halfClipRect1.xMaximum(), halfClipRect1.yMaximum() ) );
391 geos::unique_ptr clipPart2( GEOSClipByRect_r( geosinit()->ctxt, currentPart, halfClipRect2.xMinimum(), halfClipRect2.yMinimum(), halfClipRect2.xMaximum(), halfClipRect2.yMaximum() ) );
392
393 ++depth;
394
395 if ( clipPart1 )
396 {
397 if ( gridSize > 0 )
398 {
399 clipPart1.reset( GEOSIntersectionPrec_r( geosinit()->ctxt, mGeos.get(), clipPart1.get(), gridSize ) );
400 }
401 subdivideRecursive( clipPart1.get(), maxNodes, depth, parts, halfClipRect1, gridSize );
402 }
403 if ( clipPart2 )
404 {
405 if ( gridSize > 0 )
406 {
407 clipPart2.reset( GEOSIntersectionPrec_r( geosinit()->ctxt, mGeos.get(), clipPart2.get(), gridSize ) );
408 }
409 subdivideRecursive( clipPart2.get(), maxNodes, depth, parts, halfClipRect2, gridSize );
410 }
411}
412
413std::unique_ptr<QgsAbstractGeometry> QgsGeos::subdivide( int maxNodes, QString *errorMsg, const QgsGeometryParameters &parameters ) const
414{
415 if ( !mGeos )
416 {
417 return nullptr;
418 }
419
420 // minimum allowed max is 8
421 maxNodes = std::max( maxNodes, 8 );
422
423 std::unique_ptr< QgsGeometryCollection > parts = QgsGeometryFactory::createCollectionOfType( mGeometry->wkbType() );
424 try
425 {
426 subdivideRecursive( mGeos.get(), maxNodes, 0, parts.get(), mGeometry->boundingBox(), parameters.gridSize() );
427 }
428 CATCH_GEOS_WITH_ERRMSG( nullptr )
429
430 return std::move( parts );
431}
432
433QgsAbstractGeometry *QgsGeos::combine( const QgsAbstractGeometry *geom, QString *errorMsg, const QgsGeometryParameters &parameters ) const
434{
435 return overlay( geom, OverlayUnion, errorMsg, parameters ).release();
436}
437
438QgsAbstractGeometry *QgsGeos::combine( const QVector<QgsAbstractGeometry *> &geomList, QString *errorMsg, const QgsGeometryParameters &parameters ) const
439{
440 QVector< GEOSGeometry * > geosGeometries;
441 geosGeometries.reserve( geomList.size() );
442 for ( const QgsAbstractGeometry *g : geomList )
443 {
444 if ( !g )
445 continue;
446
447 geosGeometries << asGeos( g, mPrecision ).release();
448 }
449
450 geos::unique_ptr geomUnion;
451 try
452 {
453 geos::unique_ptr geomCollection = createGeosCollection( GEOS_GEOMETRYCOLLECTION, geosGeometries );
454 if ( parameters.gridSize() > 0 )
455 {
456 geomUnion.reset( GEOSUnaryUnionPrec_r( geosinit()->ctxt, geomCollection.get(), parameters.gridSize() ) );
457 }
458 else
459 {
460 geomUnion.reset( GEOSUnaryUnion_r( geosinit()->ctxt, geomCollection.get() ) );
461 }
462 }
463 CATCH_GEOS_WITH_ERRMSG( nullptr )
464
465 std::unique_ptr< QgsAbstractGeometry > result = fromGeos( geomUnion.get() );
466 return result.release();
467}
468
469QgsAbstractGeometry *QgsGeos::combine( const QVector<QgsGeometry> &geomList, QString *errorMsg, const QgsGeometryParameters &parameters ) const
470{
471 QVector< GEOSGeometry * > geosGeometries;
472 geosGeometries.reserve( geomList.size() );
473 for ( const QgsGeometry &g : geomList )
474 {
475 if ( g.isNull() )
476 continue;
477
478 geosGeometries << asGeos( g.constGet(), mPrecision ).release();
479 }
480
481 geos::unique_ptr geomUnion;
482 try
483 {
484 geos::unique_ptr geomCollection = createGeosCollection( GEOS_GEOMETRYCOLLECTION, geosGeometries );
485
486 if ( parameters.gridSize() > 0 )
487 {
488 geomUnion.reset( GEOSUnaryUnionPrec_r( geosinit()->ctxt, geomCollection.get(), parameters.gridSize() ) );
489 }
490 else
491 {
492 geomUnion.reset( GEOSUnaryUnion_r( geosinit()->ctxt, geomCollection.get() ) );
493 }
494
495 }
496 CATCH_GEOS_WITH_ERRMSG( nullptr )
497
498 std::unique_ptr< QgsAbstractGeometry > result = fromGeos( geomUnion.get() );
499 return result.release();
500}
501
502QgsAbstractGeometry *QgsGeos::symDifference( const QgsAbstractGeometry *geom, QString *errorMsg, const QgsGeometryParameters &parameters ) const
503{
504 return overlay( geom, OverlaySymDifference, errorMsg, parameters ).release();
505}
506
507double QgsGeos::distance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
508{
509 double distance = -1.0;
510 if ( !mGeos )
511 {
512 return distance;
513 }
514
515 geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
516 if ( !otherGeosGeom )
517 {
518 return distance;
519 }
520
521 try
522 {
523 if ( mGeosPrepared )
524 {
525 GEOSPreparedDistance_r( geosinit()->ctxt, mGeosPrepared.get(), otherGeosGeom.get(), &distance );
526 }
527 else
528 {
529 GEOSDistance_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), &distance );
530 }
531 }
533
534 return distance;
535}
536
537double QgsGeos::distance( double x, double y, QString *errorMsg ) const
538{
539 double distance = -1.0;
540 if ( !mGeos )
541 {
542 return distance;
543 }
544
545 geos::unique_ptr point = createGeosPointXY( x, y, false, 0, false, 0, 2, 0 );
546 if ( !point )
547 return distance;
548
549 try
550 {
551 if ( mGeosPrepared )
552 {
553 GEOSPreparedDistance_r( geosinit()->ctxt, mGeosPrepared.get(), point.get(), &distance );
554 }
555 else
556 {
557 GEOSDistance_r( geosinit()->ctxt, mGeos.get(), point.get(), &distance );
558 }
559 }
561
562 return distance;
563}
564
565bool QgsGeos::distanceWithin( const QgsAbstractGeometry *geom, double maxdist, QString *errorMsg ) const
566{
567 if ( !mGeos )
568 {
569 return false;
570 }
571
572 geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
573 if ( !otherGeosGeom )
574 {
575 return false;
576 }
577
578 // TODO: optimize implementation of this function to early-exit if
579 // any part of othergeosGeom is found to be within the given
580 // distance
581 double distance;
582
583
584 try
585 {
586 if ( mGeosPrepared )
587 {
588#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
589 return GEOSPreparedDistanceWithin_r( geosinit()->ctxt, mGeosPrepared.get(), otherGeosGeom.get(), maxdist );
590#else
591 GEOSPreparedDistance_r( geosinit()->ctxt, mGeosPrepared.get(), otherGeosGeom.get(), &distance );
592#endif
593 }
594 else
595 {
596#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
597 return GEOSDistanceWithin_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), maxdist );
598#else
599 GEOSDistance_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), &distance );
600#endif
601 }
602 }
604
605 return distance <= maxdist;
606}
607
608bool QgsGeos::contains( double x, double y, QString *errorMsg ) const
609{
610 geos::unique_ptr point = createGeosPointXY( x, y, false, 0, false, 0, 2, 0 );
611 if ( !point )
612 return false;
613
614 bool result = false;
615 try
616 {
617 if ( mGeosPrepared ) //use faster version with prepared geometry
618 {
619 return GEOSPreparedContains_r( geosinit()->ctxt, mGeosPrepared.get(), point.get() ) == 1;
620 }
621
622 result = ( GEOSContains_r( geosinit()->ctxt, mGeos.get(), point.get() ) == 1 );
623 }
624 catch ( GEOSException &e )
625 {
626 logError( QStringLiteral( "GEOS" ), e.what() );
627 if ( errorMsg )
628 {
629 *errorMsg = e.what();
630 }
631 return false;
632 }
633
634 return result;
635}
636
637double QgsGeos::hausdorffDistance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
638{
639 double distance = -1.0;
640 if ( !mGeos )
641 {
642 return distance;
643 }
644
645 geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
646 if ( !otherGeosGeom )
647 {
648 return distance;
649 }
650
651 try
652 {
653 GEOSHausdorffDistance_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), &distance );
654 }
656
657 return distance;
658}
659
660double QgsGeos::hausdorffDistanceDensify( const QgsAbstractGeometry *geom, double densifyFraction, QString *errorMsg ) const
661{
662 double distance = -1.0;
663 if ( !mGeos )
664 {
665 return distance;
666 }
667
668 geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
669 if ( !otherGeosGeom )
670 {
671 return distance;
672 }
673
674 try
675 {
676 GEOSHausdorffDistanceDensify_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), densifyFraction, &distance );
677 }
679
680 return distance;
681}
682
683double QgsGeos::frechetDistance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
684{
685 double distance = -1.0;
686 if ( !mGeos )
687 {
688 return distance;
689 }
690
691 geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
692 if ( !otherGeosGeom )
693 {
694 return distance;
695 }
696
697 try
698 {
699 GEOSFrechetDistance_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), &distance );
700 }
702
703 return distance;
704}
705
706double QgsGeos::frechetDistanceDensify( const QgsAbstractGeometry *geom, double densifyFraction, QString *errorMsg ) const
707{
708 double distance = -1.0;
709 if ( !mGeos )
710 {
711 return distance;
712 }
713
714 geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
715 if ( !otherGeosGeom )
716 {
717 return distance;
718 }
719
720 try
721 {
722 GEOSFrechetDistanceDensify_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), densifyFraction, &distance );
723 }
725
726 return distance;
727}
728
729bool QgsGeos::intersects( const QgsAbstractGeometry *geom, QString *errorMsg ) const
730{
731 return relation( geom, RelationIntersects, errorMsg );
732}
733
734bool QgsGeos::touches( const QgsAbstractGeometry *geom, QString *errorMsg ) const
735{
736 return relation( geom, RelationTouches, errorMsg );
737}
738
739bool QgsGeos::crosses( const QgsAbstractGeometry *geom, QString *errorMsg ) const
740{
741 return relation( geom, RelationCrosses, errorMsg );
742}
743
744bool QgsGeos::within( const QgsAbstractGeometry *geom, QString *errorMsg ) const
745{
746 return relation( geom, RelationWithin, errorMsg );
747}
748
749bool QgsGeos::overlaps( const QgsAbstractGeometry *geom, QString *errorMsg ) const
750{
751 return relation( geom, RelationOverlaps, errorMsg );
752}
753
754bool QgsGeos::contains( const QgsAbstractGeometry *geom, QString *errorMsg ) const
755{
756 return relation( geom, RelationContains, errorMsg );
757}
758
759bool QgsGeos::disjoint( const QgsAbstractGeometry *geom, QString *errorMsg ) const
760{
761 return relation( geom, RelationDisjoint, errorMsg );
762}
763
764QString QgsGeos::relate( const QgsAbstractGeometry *geom, QString *errorMsg ) const
765{
766 if ( !mGeos )
767 {
768 return QString();
769 }
770
771 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
772 if ( !geosGeom )
773 {
774 return QString();
775 }
776
777 QString result;
778 try
779 {
780 char *r = GEOSRelate_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() );
781 if ( r )
782 {
783 result = QString( r );
784 GEOSFree_r( geosinit()->ctxt, r );
785 }
786 }
787 catch ( GEOSException &e )
788 {
789 logError( QStringLiteral( "GEOS" ), e.what() );
790 if ( errorMsg )
791 {
792 *errorMsg = e.what();
793 }
794 }
795
796 return result;
797}
798
799bool QgsGeos::relatePattern( const QgsAbstractGeometry *geom, const QString &pattern, QString *errorMsg ) const
800{
801 if ( !mGeos || !geom )
802 {
803 return false;
804 }
805
806 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
807 if ( !geosGeom )
808 {
809 return false;
810 }
811
812 bool result = false;
813 try
814 {
815 result = ( GEOSRelatePattern_r( geosinit()->ctxt, mGeos.get(), geosGeom.get(), pattern.toLocal8Bit().constData() ) == 1 );
816 }
817 catch ( GEOSException &e )
818 {
819 logError( QStringLiteral( "GEOS" ), e.what() );
820 if ( errorMsg )
821 {
822 *errorMsg = e.what();
823 }
824 }
825
826 return result;
827}
828
829double QgsGeos::area( QString *errorMsg ) const
830{
831 double area = -1.0;
832 if ( !mGeos )
833 {
834 return area;
835 }
836
837 try
838 {
839 if ( GEOSArea_r( geosinit()->ctxt, mGeos.get(), &area ) != 1 )
840 return -1.0;
841 }
843 return area;
844}
845
846double QgsGeos::length( QString *errorMsg ) const
847{
848 double length = -1.0;
849 if ( !mGeos )
850 {
851 return length;
852 }
853 try
854 {
855 if ( GEOSLength_r( geosinit()->ctxt, mGeos.get(), &length ) != 1 )
856 return -1.0;
857 }
859 return length;
860}
861
863 QVector<QgsGeometry> &newGeometries,
864 bool topological,
865 QgsPointSequence &topologyTestPoints,
866 QString *errorMsg, bool skipIntersectionCheck ) const
867{
868
869 EngineOperationResult returnCode = Success;
870 if ( !mGeos || !mGeometry )
871 {
872 return InvalidBaseGeometry;
873 }
874
875 //return if this type is point/multipoint
876 if ( mGeometry->dimension() == 0 )
877 {
878 return SplitCannotSplitPoint; //cannot split points
879 }
880
881 if ( !GEOSisValid_r( geosinit()->ctxt, mGeos.get() ) )
882 return InvalidBaseGeometry;
883
884 //make sure splitLine is valid
885 if ( ( mGeometry->dimension() == 1 && splitLine.numPoints() < 1 ) ||
886 ( mGeometry->dimension() == 2 && splitLine.numPoints() < 2 ) )
887 return InvalidInput;
888
889 newGeometries.clear();
890 geos::unique_ptr splitLineGeos;
891
892 try
893 {
894 if ( splitLine.numPoints() > 1 )
895 {
896 splitLineGeos = createGeosLinestring( &splitLine, mPrecision );
897 }
898 else if ( splitLine.numPoints() == 1 )
899 {
900 splitLineGeos = createGeosPointXY( splitLine.xAt( 0 ), splitLine.yAt( 0 ), false, 0, false, 0, 2, mPrecision );
901 }
902 else
903 {
904 return InvalidInput;
905 }
906
907 if ( !GEOSisValid_r( geosinit()->ctxt, splitLineGeos.get() ) || !GEOSisSimple_r( geosinit()->ctxt, splitLineGeos.get() ) )
908 {
909 return InvalidInput;
910 }
911
912 if ( topological )
913 {
914 //find out candidate points for topological corrections
915 if ( !topologicalTestPointsSplit( splitLineGeos.get(), topologyTestPoints ) )
916 {
917 return InvalidInput; // TODO: is it really an invalid input?
918 }
919 }
920
921 //call split function depending on geometry type
922 if ( mGeometry->dimension() == 1 )
923 {
924 returnCode = splitLinearGeometry( splitLineGeos.get(), newGeometries, skipIntersectionCheck );
925 }
926 else if ( mGeometry->dimension() == 2 )
927 {
928 returnCode = splitPolygonGeometry( splitLineGeos.get(), newGeometries, skipIntersectionCheck );
929 }
930 else
931 {
932 return InvalidInput;
933 }
934 }
936
937 return returnCode;
938}
939
940
941
942bool QgsGeos::topologicalTestPointsSplit( const GEOSGeometry *splitLine, QgsPointSequence &testPoints, QString *errorMsg ) const
943{
944 //Find out the intersection points between splitLineGeos and this geometry.
945 //These points need to be tested for topological correctness by the calling function
946 //if topological editing is enabled
947
948 if ( !mGeos )
949 {
950 return false;
951 }
952
953 try
954 {
955 testPoints.clear();
956 geos::unique_ptr intersectionGeom( GEOSIntersection_r( geosinit()->ctxt, mGeos.get(), splitLine ) );
957 if ( !intersectionGeom )
958 return false;
959
960 bool simple = false;
961 int nIntersectGeoms = 1;
962 if ( GEOSGeomTypeId_r( geosinit()->ctxt, intersectionGeom.get() ) == GEOS_LINESTRING
963 || GEOSGeomTypeId_r( geosinit()->ctxt, intersectionGeom.get() ) == GEOS_POINT )
964 simple = true;
965
966 if ( !simple )
967 nIntersectGeoms = GEOSGetNumGeometries_r( geosinit()->ctxt, intersectionGeom.get() );
968
969 for ( int i = 0; i < nIntersectGeoms; ++i )
970 {
971 const GEOSGeometry *currentIntersectGeom = nullptr;
972 if ( simple )
973 currentIntersectGeom = intersectionGeom.get();
974 else
975 currentIntersectGeom = GEOSGetGeometryN_r( geosinit()->ctxt, intersectionGeom.get(), i );
976
977 const GEOSCoordSequence *lineSequence = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, currentIntersectGeom );
978 unsigned int sequenceSize = 0;
979 double x, y, z;
980 if ( GEOSCoordSeq_getSize_r( geosinit()->ctxt, lineSequence, &sequenceSize ) != 0 )
981 {
982 for ( unsigned int i = 0; i < sequenceSize; ++i )
983 {
984 if ( GEOSCoordSeq_getXYZ_r( geosinit()->ctxt, lineSequence, i, &x, &y, &z ) )
985 {
986 testPoints.push_back( QgsPoint( x, y, z ) );
987 }
988 }
989 }
990 }
991 }
993
994 return true;
995}
996
997geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) const
998{
999 int type = GEOSGeomTypeId_r( geosinit()->ctxt, mGeos.get() );
1000
1001 std::unique_ptr< QgsMultiCurve > multiCurve;
1002 if ( type == GEOS_MULTILINESTRING )
1003 {
1004 multiCurve.reset( qgsgeometry_cast<QgsMultiCurve *>( mGeometry->clone() ) );
1005 }
1006 else if ( type == GEOS_LINESTRING )
1007 {
1008 multiCurve.reset( new QgsMultiCurve() );
1009 multiCurve->addGeometry( mGeometry->clone() );
1010 }
1011 else
1012 {
1013 return nullptr;
1014 }
1015
1016 if ( !multiCurve )
1017 {
1018 return nullptr;
1019 }
1020
1021
1022 // we might have a point or a multipoint, depending on number of
1023 // intersections between the geometry and the split geometry
1024 std::unique_ptr< QgsAbstractGeometry > splitGeom( fromGeos( GEOSsplitPoint ) );
1025 std::unique_ptr< QgsMultiPoint > splitPoints( qgsgeometry_cast<QgsMultiPoint *>( splitGeom->clone() ) );
1026 if ( !splitPoints )
1027 {
1028 QgsPoint *splitPoint = qgsgeometry_cast<QgsPoint *>( splitGeom->clone() );
1029 if ( !splitPoint )
1030 {
1031 return nullptr;
1032 }
1033 splitPoints.reset( new QgsMultiPoint() );
1034 splitPoints->addGeometry( splitPoint );
1035 }
1036
1037 QgsMultiCurve lines;
1038
1039 //For each part
1040 for ( int i = 0; i < multiCurve->numGeometries(); ++i )
1041 {
1042 const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( multiCurve->geometryN( i ) );
1043 if ( !line )
1044 {
1045 const QgsCurve *curve = qgsgeometry_cast<const QgsCurve *>( multiCurve->geometryN( i ) );
1046 line = curve->curveToLine();
1047 }
1048 if ( !line )
1049 {
1050 return nullptr;
1051 }
1052 // we gather the intersection points and their distance from previous node grouped by segment
1053 QMap< int, QVector< QPair< double, QgsPoint > > >pointMap;
1054 for ( int p = 0; p < splitPoints->numGeometries(); ++p )
1055 {
1056 const QgsPoint *intersectionPoint = splitPoints->pointN( p );
1057 QgsPoint segmentPoint2D;
1058 QgsVertexId nextVertex;
1059 // With closestSegment we only get a 2D point so we need to interpolate if we
1060 // don't want to lose Z data
1061 line->closestSegment( *intersectionPoint, segmentPoint2D, nextVertex );
1062 const QgsLineString segment = QgsLineString( line->pointN( nextVertex.vertex - 1 ), line->pointN( nextVertex.vertex ) );
1063 const double distance = segmentPoint2D.distance( line->pointN( nextVertex.vertex - 1 ) );
1064
1065 // Due to precision issues, distance can be a tad larger than the actual segment length, making interpolatePoint() return nullptr
1066 // In that case we'll use the segment's endpoint instead of interpolating
1067 std::unique_ptr< QgsPoint > correctSegmentPoint( distance > segment.length() ? segment.endPoint().clone() : segment.interpolatePoint( distance ) );
1068
1069 const QPair< double, QgsPoint > pair = qMakePair( distance, *correctSegmentPoint.get() );
1070 if ( pointMap.contains( nextVertex.vertex - 1 ) )
1071 pointMap[ nextVertex.vertex - 1 ].append( pair );
1072 else
1073 pointMap[ nextVertex.vertex - 1 ] = QVector< QPair< double, QgsPoint > >() << pair;
1074 }
1075
1076 // When we have more than one intersection point on a segment we need those points
1077 // to be sorted by their distance from the previous geometry vertex
1078 for ( auto &p : pointMap )
1079 {
1080 std::sort( p.begin(), p.end(), []( const QPair< double, QgsPoint > &a, const QPair< double, QgsPoint > &b ) { return a.first < b.first; } );
1081 }
1082
1083 //For each segment
1084 QgsLineString newLine;
1085 int nVertices = line->numPoints();
1086 QgsPoint splitPoint;
1087 for ( int j = 0; j < nVertices; ++j )
1088 {
1089 QgsPoint currentPoint = line->pointN( j );
1090 newLine.addVertex( currentPoint );
1091 if ( pointMap.contains( j ) )
1092 {
1093 // For each intersecting point
1094 for ( int k = 0; k < pointMap[ j ].size(); ++k )
1095 {
1096 splitPoint = pointMap[ j ][k].second;
1097 if ( splitPoint == currentPoint )
1098 {
1099 lines.addGeometry( newLine.clone() );
1100 newLine = QgsLineString();
1101 newLine.addVertex( currentPoint );
1102 }
1103 else if ( splitPoint == line->pointN( j + 1 ) )
1104 {
1105 newLine.addVertex( line->pointN( j + 1 ) );
1106 lines.addGeometry( newLine.clone() );
1107 newLine = QgsLineString();
1108 }
1109 else
1110 {
1111 newLine.addVertex( splitPoint );
1112 lines.addGeometry( newLine.clone() );
1113 newLine = QgsLineString();
1114 newLine.addVertex( splitPoint );
1115 }
1116 }
1117 }
1118 }
1119 lines.addGeometry( newLine.clone() );
1120 }
1121
1122 return asGeos( &lines, mPrecision );
1123}
1124
1125QgsGeometryEngine::EngineOperationResult QgsGeos::splitLinearGeometry( GEOSGeometry *splitLine, QVector<QgsGeometry> &newGeometries, bool skipIntersectionCheck ) const
1126{
1127 Q_UNUSED( skipIntersectionCheck )
1128 if ( !splitLine )
1129 return InvalidInput;
1130
1131 if ( !mGeos )
1132 return InvalidBaseGeometry;
1133
1134 geos::unique_ptr intersectGeom( GEOSIntersection_r( geosinit()->ctxt, splitLine, mGeos.get() ) );
1135 if ( !intersectGeom || GEOSisEmpty_r( geosinit()->ctxt, intersectGeom.get() ) )
1136 return NothingHappened;
1137
1138 //check that split line has no linear intersection
1139 int linearIntersect = GEOSRelatePattern_r( geosinit()->ctxt, mGeos.get(), splitLine, "1********" );
1140 if ( linearIntersect > 0 )
1141 return InvalidInput;
1142
1143 geos::unique_ptr splitGeom;
1144 splitGeom = linePointDifference( intersectGeom.get() );
1145
1146 if ( !splitGeom )
1147 return InvalidBaseGeometry;
1148
1149 QVector<GEOSGeometry *> lineGeoms;
1150
1151 int splitType = GEOSGeomTypeId_r( geosinit()->ctxt, splitGeom.get() );
1152 if ( splitType == GEOS_MULTILINESTRING )
1153 {
1154 int nGeoms = GEOSGetNumGeometries_r( geosinit()->ctxt, splitGeom.get() );
1155 lineGeoms.reserve( nGeoms );
1156 for ( int i = 0; i < nGeoms; ++i )
1157 lineGeoms << GEOSGeom_clone_r( geosinit()->ctxt, GEOSGetGeometryN_r( geosinit()->ctxt, splitGeom.get(), i ) );
1158
1159 }
1160 else
1161 {
1162 lineGeoms << GEOSGeom_clone_r( geosinit()->ctxt, splitGeom.get() );
1163 }
1164
1165 mergeGeometriesMultiTypeSplit( lineGeoms );
1166
1167 for ( int i = 0; i < lineGeoms.size(); ++i )
1168 {
1169 newGeometries << QgsGeometry( fromGeos( lineGeoms[i] ) );
1170 GEOSGeom_destroy_r( geosinit()->ctxt, lineGeoms[i] );
1171 }
1172
1173 return Success;
1174}
1175
1176QgsGeometryEngine::EngineOperationResult QgsGeos::splitPolygonGeometry( GEOSGeometry *splitLine, QVector<QgsGeometry> &newGeometries, bool skipIntersectionCheck ) const
1177{
1178 if ( !splitLine )
1179 return InvalidInput;
1180
1181 if ( !mGeos )
1182 return InvalidBaseGeometry;
1183
1184 // we will need prepared geometry for intersection tests
1185 const_cast<QgsGeos *>( this )->prepareGeometry();
1186 if ( !mGeosPrepared )
1187 return EngineError;
1188
1189 //first test if linestring intersects geometry. If not, return straight away
1190 if ( !skipIntersectionCheck && !GEOSPreparedIntersects_r( geosinit()->ctxt, mGeosPrepared.get(), splitLine ) )
1191 return NothingHappened;
1192
1193 //first union all the polygon rings together (to get them noded, see JTS developer guide)
1194 geos::unique_ptr nodedGeometry = nodeGeometries( splitLine, mGeos.get() );
1195 if ( !nodedGeometry )
1196 return NodedGeometryError; //an error occurred during noding
1197
1198 const GEOSGeometry *noded = nodedGeometry.get();
1199 geos::unique_ptr polygons( GEOSPolygonize_r( geosinit()->ctxt, &noded, 1 ) );
1200 if ( !polygons || numberOfGeometries( polygons.get() ) == 0 )
1201 {
1202 return InvalidBaseGeometry;
1203 }
1204
1205 //test every polygon is contained in original geometry
1206 //include in result if yes
1207 QVector<GEOSGeometry *> testedGeometries;
1208
1209 // test whether the polygon parts returned from polygonize algorithm actually
1210 // belong to the source polygon geometry (if the source polygon contains some holes,
1211 // those would be also returned by polygonize and we need to skip them)
1212 for ( int i = 0; i < numberOfGeometries( polygons.get() ); i++ )
1213 {
1214 const GEOSGeometry *polygon = GEOSGetGeometryN_r( geosinit()->ctxt, polygons.get(), i );
1215
1216 geos::unique_ptr pointOnSurface( GEOSPointOnSurface_r( geosinit()->ctxt, polygon ) );
1217 if ( pointOnSurface && GEOSPreparedIntersects_r( geosinit()->ctxt, mGeosPrepared.get(), pointOnSurface.get() ) )
1218 testedGeometries << GEOSGeom_clone_r( geosinit()->ctxt, polygon );
1219 }
1220
1221 int nGeometriesThis = numberOfGeometries( mGeos.get() ); //original number of geometries
1222 if ( testedGeometries.empty() || testedGeometries.size() == nGeometriesThis )
1223 {
1224 //no split done, preserve original geometry
1225 for ( int i = 0; i < testedGeometries.size(); ++i )
1226 {
1227 GEOSGeom_destroy_r( geosinit()->ctxt, testedGeometries[i] );
1228 }
1229 return NothingHappened;
1230 }
1231
1232 // For multi-part geometries, try to identify parts that have been unchanged and try to merge them back
1233 // to a single multi-part geometry. For example, if there's a multi-polygon with three parts, but only
1234 // one part is being split, this function makes sure that the other two parts will be kept in a multi-part
1235 // geometry rather than being separated into two single-part geometries.
1236 mergeGeometriesMultiTypeSplit( testedGeometries );
1237
1238 int i;
1239 for ( i = 0; i < testedGeometries.size() && GEOSisValid_r( geosinit()->ctxt, testedGeometries[i] ); ++i )
1240 ;
1241
1242 if ( i < testedGeometries.size() )
1243 {
1244 for ( i = 0; i < testedGeometries.size(); ++i )
1245 GEOSGeom_destroy_r( geosinit()->ctxt, testedGeometries[i] );
1246
1247 return InvalidBaseGeometry;
1248 }
1249
1250 for ( i = 0; i < testedGeometries.size(); ++i )
1251 {
1252 newGeometries << QgsGeometry( fromGeos( testedGeometries[i] ) );
1253 GEOSGeom_destroy_r( geosinit()->ctxt, testedGeometries[i] );
1254 }
1255
1256 return Success;
1257}
1258
1259geos::unique_ptr QgsGeos::nodeGeometries( const GEOSGeometry *splitLine, const GEOSGeometry *geom )
1260{
1261 if ( !splitLine || !geom )
1262 return nullptr;
1263
1264 geos::unique_ptr geometryBoundary;
1265 if ( GEOSGeomTypeId_r( geosinit()->ctxt, geom ) == GEOS_POLYGON || GEOSGeomTypeId_r( geosinit()->ctxt, geom ) == GEOS_MULTIPOLYGON )
1266 geometryBoundary.reset( GEOSBoundary_r( geosinit()->ctxt, geom ) );
1267 else
1268 geometryBoundary.reset( GEOSGeom_clone_r( geosinit()->ctxt, geom ) );
1269
1270 geos::unique_ptr splitLineClone( GEOSGeom_clone_r( geosinit()->ctxt, splitLine ) );
1271 geos::unique_ptr unionGeometry( GEOSUnion_r( geosinit()->ctxt, splitLineClone.get(), geometryBoundary.get() ) );
1272
1273 return unionGeometry;
1274}
1275
1276int QgsGeos::mergeGeometriesMultiTypeSplit( QVector<GEOSGeometry *> &splitResult ) const
1277{
1278 if ( !mGeos )
1279 return 1;
1280
1281 //convert mGeos to geometry collection
1282 int type = GEOSGeomTypeId_r( geosinit()->ctxt, mGeos.get() );
1283 if ( type != GEOS_GEOMETRYCOLLECTION &&
1284 type != GEOS_MULTILINESTRING &&
1285 type != GEOS_MULTIPOLYGON &&
1286 type != GEOS_MULTIPOINT )
1287 return 0;
1288
1289 QVector<GEOSGeometry *> copyList = splitResult;
1290 splitResult.clear();
1291
1292 //collect all the geometries that belong to the initial multifeature
1293 QVector<GEOSGeometry *> unionGeom;
1294
1295 for ( int i = 0; i < copyList.size(); ++i )
1296 {
1297 //is this geometry a part of the original multitype?
1298 bool isPart = false;
1299 for ( int j = 0; j < GEOSGetNumGeometries_r( geosinit()->ctxt, mGeos.get() ); j++ )
1300 {
1301 if ( GEOSEquals_r( geosinit()->ctxt, copyList[i], GEOSGetGeometryN_r( geosinit()->ctxt, mGeos.get(), j ) ) )
1302 {
1303 isPart = true;
1304 break;
1305 }
1306 }
1307
1308 if ( isPart )
1309 {
1310 unionGeom << copyList[i];
1311 }
1312 else
1313 {
1314 QVector<GEOSGeometry *> geomVector;
1315 geomVector << copyList[i];
1316
1317 if ( type == GEOS_MULTILINESTRING )
1318 splitResult << createGeosCollection( GEOS_MULTILINESTRING, geomVector ).release();
1319 else if ( type == GEOS_MULTIPOLYGON )
1320 splitResult << createGeosCollection( GEOS_MULTIPOLYGON, geomVector ).release();
1321 else
1322 GEOSGeom_destroy_r( geosinit()->ctxt, copyList[i] );
1323 }
1324 }
1325
1326 //make multifeature out of unionGeom
1327 if ( !unionGeom.isEmpty() )
1328 {
1329 if ( type == GEOS_MULTILINESTRING )
1330 splitResult << createGeosCollection( GEOS_MULTILINESTRING, unionGeom ).release();
1331 else if ( type == GEOS_MULTIPOLYGON )
1332 splitResult << createGeosCollection( GEOS_MULTIPOLYGON, unionGeom ).release();
1333 }
1334 else
1335 {
1336 unionGeom.clear();
1337 }
1338
1339 return 0;
1340}
1341
1342geos::unique_ptr QgsGeos::createGeosCollection( int typeId, const QVector<GEOSGeometry *> &geoms )
1343{
1344 int nNullGeoms = geoms.count( nullptr );
1345 int nNotNullGeoms = geoms.size() - nNullGeoms;
1346
1347 GEOSGeometry **geomarr = new GEOSGeometry*[ nNotNullGeoms ];
1348 if ( !geomarr )
1349 {
1350 return nullptr;
1351 }
1352
1353 int i = 0;
1354 QVector<GEOSGeometry *>::const_iterator geomIt = geoms.constBegin();
1355 for ( ; geomIt != geoms.constEnd(); ++geomIt )
1356 {
1357 if ( *geomIt )
1358 {
1359 if ( GEOSisEmpty_r( geosinit()->ctxt, *geomIt ) )
1360 {
1361 // don't add empty parts to a geos collection, it can cause crashes in GEOS
1362 nNullGeoms++;
1363 nNotNullGeoms--;
1364 GEOSGeom_destroy_r( geosinit()->ctxt, *geomIt );
1365 }
1366 else
1367 {
1368 geomarr[i] = *geomIt;
1369 ++i;
1370 }
1371 }
1372 }
1373 geos::unique_ptr geom;
1374
1375 try
1376 {
1377 geom.reset( GEOSGeom_createCollection_r( geosinit()->ctxt, typeId, geomarr, nNotNullGeoms ) );
1378 }
1379 catch ( GEOSException & )
1380 {
1381 }
1382
1383 delete [] geomarr;
1384
1385 return geom;
1386}
1387
1388std::unique_ptr<QgsAbstractGeometry> QgsGeos::fromGeos( const GEOSGeometry *geos )
1389{
1390 if ( !geos )
1391 {
1392 return nullptr;
1393 }
1394
1395 int nCoordDims = GEOSGeom_getCoordinateDimension_r( geosinit()->ctxt, geos );
1396 int nDims = GEOSGeom_getDimensions_r( geosinit()->ctxt, geos );
1397 bool hasZ = ( nCoordDims == 3 );
1398 bool hasM = ( ( nDims - nCoordDims ) == 1 );
1399
1400 switch ( GEOSGeomTypeId_r( geosinit()->ctxt, geos ) )
1401 {
1402 case GEOS_POINT: // a point
1403 {
1404 if ( GEOSisEmpty_r( geosinit()->ctxt, geos ) )
1405 return nullptr;
1406
1407 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, geos );
1408 unsigned int nPoints = 0;
1409 GEOSCoordSeq_getSize_r( geosinit()->ctxt, cs, &nPoints );
1410 return nPoints > 0 ? std::unique_ptr<QgsAbstractGeometry>( coordSeqPoint( cs, 0, hasZ, hasM ).clone() ) : nullptr;
1411 }
1412 case GEOS_LINESTRING:
1413 {
1414 return sequenceToLinestring( geos, hasZ, hasM );
1415 }
1416 case GEOS_POLYGON:
1417 {
1418 return fromGeosPolygon( geos );
1419 }
1420 case GEOS_MULTIPOINT:
1421 {
1422 std::unique_ptr< QgsMultiPoint > multiPoint( new QgsMultiPoint() );
1423 int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1424 multiPoint->reserve( nParts );
1425 for ( int i = 0; i < nParts; ++i )
1426 {
1427 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ) );
1428 if ( cs )
1429 {
1430 unsigned int nPoints = 0;
1431 GEOSCoordSeq_getSize_r( geosinit()->ctxt, cs, &nPoints );
1432 if ( nPoints > 0 )
1433 multiPoint->addGeometry( coordSeqPoint( cs, 0, hasZ, hasM ).clone() );
1434 }
1435 }
1436 return std::move( multiPoint );
1437 }
1438 case GEOS_MULTILINESTRING:
1439 {
1440 std::unique_ptr< QgsMultiLineString > multiLineString( new QgsMultiLineString() );
1441 int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1442 multiLineString->reserve( nParts );
1443 for ( int i = 0; i < nParts; ++i )
1444 {
1445 std::unique_ptr< QgsLineString >line( sequenceToLinestring( GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ), hasZ, hasM ) );
1446 if ( line )
1447 {
1448 multiLineString->addGeometry( line.release() );
1449 }
1450 }
1451 return std::move( multiLineString );
1452 }
1453 case GEOS_MULTIPOLYGON:
1454 {
1455 std::unique_ptr< QgsMultiPolygon > multiPolygon( new QgsMultiPolygon() );
1456
1457 int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1458 multiPolygon->reserve( nParts );
1459 for ( int i = 0; i < nParts; ++i )
1460 {
1461 std::unique_ptr< QgsPolygon > poly = fromGeosPolygon( GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ) );
1462 if ( poly )
1463 {
1464 multiPolygon->addGeometry( poly.release() );
1465 }
1466 }
1467 return std::move( multiPolygon );
1468 }
1469 case GEOS_GEOMETRYCOLLECTION:
1470 {
1471 std::unique_ptr< QgsGeometryCollection > geomCollection( new QgsGeometryCollection() );
1472 int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1473 geomCollection->reserve( nParts );
1474 for ( int i = 0; i < nParts; ++i )
1475 {
1476 std::unique_ptr< QgsAbstractGeometry > geom( fromGeos( GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ) ) );
1477 if ( geom )
1478 {
1479 geomCollection->addGeometry( geom.release() );
1480 }
1481 }
1482 return std::move( geomCollection );
1483 }
1484 }
1485 return nullptr;
1486}
1487
1488std::unique_ptr<QgsPolygon> QgsGeos::fromGeosPolygon( const GEOSGeometry *geos )
1489{
1490 if ( GEOSGeomTypeId_r( geosinit()->ctxt, geos ) != GEOS_POLYGON )
1491 {
1492 return nullptr;
1493 }
1494
1495 int nCoordDims = GEOSGeom_getCoordinateDimension_r( geosinit()->ctxt, geos );
1496 int nDims = GEOSGeom_getDimensions_r( geosinit()->ctxt, geos );
1497 bool hasZ = ( nCoordDims == 3 );
1498 bool hasM = ( ( nDims - nCoordDims ) == 1 );
1499
1500 std::unique_ptr< QgsPolygon > polygon( new QgsPolygon() );
1501
1502 const GEOSGeometry *ring = GEOSGetExteriorRing_r( geosinit()->ctxt, geos );
1503 if ( ring )
1504 {
1505 polygon->setExteriorRing( sequenceToLinestring( ring, hasZ, hasM ).release() );
1506 }
1507
1508 QVector<QgsCurve *> interiorRings;
1509 const int ringCount = GEOSGetNumInteriorRings_r( geosinit()->ctxt, geos );
1510 interiorRings.reserve( ringCount );
1511 for ( int i = 0; i < ringCount; ++i )
1512 {
1513 ring = GEOSGetInteriorRingN_r( geosinit()->ctxt, geos, i );
1514 if ( ring )
1515 {
1516 interiorRings.push_back( sequenceToLinestring( ring, hasZ, hasM ).release() );
1517 }
1518 }
1519 polygon->setInteriorRings( interiorRings );
1520
1521 return polygon;
1522}
1523
1524std::unique_ptr<QgsLineString> QgsGeos::sequenceToLinestring( const GEOSGeometry *geos, bool hasZ, bool hasM )
1525{
1526 const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, geos );
1527
1528 unsigned int nPoints;
1529 GEOSCoordSeq_getSize_r( geosinit()->ctxt, cs, &nPoints );
1530
1531 QVector< double > xOut( nPoints );
1532 QVector< double > yOut( nPoints );
1533 QVector< double > zOut;
1534 if ( hasZ )
1535 zOut.resize( nPoints );
1536 QVector< double > mOut;
1537 if ( hasM )
1538 mOut.resize( nPoints );
1539
1540 double *x = xOut.data();
1541 double *y = yOut.data();
1542 double *z = zOut.data();
1543 double *m = mOut.data();
1544
1545#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
1546 GEOSCoordSeq_copyToArrays_r( geosinit()->ctxt, cs, x, y, hasZ ? z : nullptr, hasM ? m : nullptr );
1547#else
1548 for ( unsigned int i = 0; i < nPoints; ++i )
1549 {
1550 if ( hasZ )
1551 GEOSCoordSeq_getXYZ_r( geosinit()->ctxt, cs, i, x++, y++, z++ );
1552 else
1553 GEOSCoordSeq_getXY_r( geosinit()->ctxt, cs, i, x++, y++ );
1554 if ( hasM )
1555 {
1556 GEOSCoordSeq_getOrdinate_r( geosinit()->ctxt, cs, i, 3, m++ );
1557 }
1558 }
1559#endif
1560 std::unique_ptr< QgsLineString > line( new QgsLineString( xOut, yOut, zOut, mOut ) );
1561 return line;
1562}
1563
1564int QgsGeos::numberOfGeometries( GEOSGeometry *g )
1565{
1566 if ( !g )
1567 return 0;
1568
1569 int geometryType = GEOSGeomTypeId_r( geosinit()->ctxt, g );
1570 if ( geometryType == GEOS_POINT || geometryType == GEOS_LINESTRING || geometryType == GEOS_LINEARRING
1571 || geometryType == GEOS_POLYGON )
1572 return 1;
1573
1574 //calling GEOSGetNumGeometries is save for multi types and collections also in geos2
1575 return GEOSGetNumGeometries_r( geosinit()->ctxt, g );
1576}
1577
1578QgsPoint QgsGeos::coordSeqPoint( const GEOSCoordSequence *cs, int i, bool hasZ, bool hasM )
1579{
1580 if ( !cs )
1581 {
1582 return QgsPoint();
1583 }
1584
1585 double x, y;
1586 double z = 0;
1587 double m = 0;
1588 if ( hasZ )
1589 GEOSCoordSeq_getXYZ_r( geosinit()->ctxt, cs, i, &x, &y, &z );
1590 else
1591 GEOSCoordSeq_getXY_r( geosinit()->ctxt, cs, i, &x, &y );
1592 if ( hasM )
1593 {
1594 GEOSCoordSeq_getOrdinate_r( geosinit()->ctxt, cs, i, 3, &m );
1595 }
1596
1598 if ( hasZ && hasM )
1599 {
1601 }
1602 else if ( hasZ )
1603 {
1605 }
1606 else if ( hasM )
1607 {
1609 }
1610 return QgsPoint( t, x, y, z, m );
1611}
1612
1614{
1615 if ( !geom )
1616 return nullptr;
1617
1618 int coordDims = 2;
1619 if ( geom->is3D() )
1620 {
1621 ++coordDims;
1622 }
1623 if ( geom->isMeasure() )
1624 {
1625 ++coordDims;
1626 }
1627
1629 {
1630 int geosType = GEOS_GEOMETRYCOLLECTION;
1631
1633 {
1634 switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
1635 {
1637 geosType = GEOS_MULTIPOINT;
1638 break;
1639
1641 geosType = GEOS_MULTILINESTRING;
1642 break;
1643
1645 geosType = GEOS_MULTIPOLYGON;
1646 break;
1647
1650 return nullptr;
1651 }
1652 }
1653
1654
1655 const QgsGeometryCollection *c = qgsgeometry_cast<const QgsGeometryCollection *>( geom );
1656
1657 if ( !c )
1658 return nullptr;
1659
1660 QVector< GEOSGeometry * > geomVector( c->numGeometries() );
1661 for ( int i = 0; i < c->numGeometries(); ++i )
1662 {
1663 geomVector[i] = asGeos( c->geometryN( i ), precision ).release();
1664 }
1665 return createGeosCollection( geosType, geomVector );
1666 }
1667 else
1668 {
1669 switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
1670 {
1672 return createGeosPoint( static_cast<const QgsPoint *>( geom ), coordDims, precision );
1673
1675 return createGeosLinestring( static_cast<const QgsLineString *>( geom ), precision );
1676
1678 return createGeosPolygon( static_cast<const QgsPolygon *>( geom ), precision );
1679
1682 return nullptr;
1683 }
1684 }
1685 return nullptr;
1686}
1687
1688std::unique_ptr<QgsAbstractGeometry> QgsGeos::overlay( const QgsAbstractGeometry *geom, Overlay op, QString *errorMsg, const QgsGeometryParameters &parameters ) const
1689{
1690 if ( !mGeos || !geom )
1691 {
1692 return nullptr;
1693 }
1694
1695 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
1696 if ( !geosGeom )
1697 {
1698 return nullptr;
1699 }
1700
1701 const double gridSize = parameters.gridSize();
1702
1703 try
1704 {
1705 geos::unique_ptr opGeom;
1706 switch ( op )
1707 {
1708 case OverlayIntersection:
1709 if ( gridSize > 0 )
1710 {
1711 opGeom.reset( GEOSIntersectionPrec_r( geosinit()->ctxt, mGeos.get(), geosGeom.get(), gridSize ) );
1712 }
1713 else
1714 {
1715 opGeom.reset( GEOSIntersection_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1716 }
1717 break;
1718
1719 case OverlayDifference:
1720 if ( gridSize > 0 )
1721 {
1722 opGeom.reset( GEOSDifferencePrec_r( geosinit()->ctxt, mGeos.get(), geosGeom.get(), gridSize ) );
1723 }
1724 else
1725 {
1726 opGeom.reset( GEOSDifference_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1727 }
1728 break;
1729
1730 case OverlayUnion:
1731 {
1732 geos::unique_ptr unionGeometry;
1733 if ( gridSize > 0 )
1734 {
1735 unionGeometry.reset( GEOSUnionPrec_r( geosinit()->ctxt, mGeos.get(), geosGeom.get(), gridSize ) );
1736 }
1737 else
1738 {
1739 unionGeometry.reset( GEOSUnion_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1740 }
1741
1742 if ( unionGeometry && GEOSGeomTypeId_r( geosinit()->ctxt, unionGeometry.get() ) == GEOS_MULTILINESTRING )
1743 {
1744 geos::unique_ptr mergedLines( GEOSLineMerge_r( geosinit()->ctxt, unionGeometry.get() ) );
1745 if ( mergedLines )
1746 {
1747 unionGeometry = std::move( mergedLines );
1748 }
1749 }
1750
1751 opGeom = std::move( unionGeometry );
1752 }
1753 break;
1754
1755 case OverlaySymDifference:
1756 if ( gridSize > 0 )
1757 {
1758 opGeom.reset( GEOSSymDifferencePrec_r( geosinit()->ctxt, mGeos.get(), geosGeom.get(), gridSize ) );
1759 }
1760 else
1761 {
1762 opGeom.reset( GEOSSymDifference_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1763 }
1764 break;
1765 }
1766 return fromGeos( opGeom.get() );
1767 }
1768 catch ( GEOSException &e )
1769 {
1770 logError( QStringLiteral( "GEOS" ), e.what() );
1771 if ( errorMsg )
1772 {
1773 *errorMsg = e.what();
1774 }
1775 return nullptr;
1776 }
1777}
1778
1779bool QgsGeos::relation( const QgsAbstractGeometry *geom, Relation r, QString *errorMsg ) const
1780{
1781 if ( !mGeos || !geom )
1782 {
1783 return false;
1784 }
1785
1786 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
1787 if ( !geosGeom )
1788 {
1789 return false;
1790 }
1791
1792 bool result = false;
1793 try
1794 {
1795 if ( mGeosPrepared ) //use faster version with prepared geometry
1796 {
1797 switch ( r )
1798 {
1799 case RelationIntersects:
1800 result = ( GEOSPreparedIntersects_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1801 break;
1802 case RelationTouches:
1803 result = ( GEOSPreparedTouches_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1804 break;
1805 case RelationCrosses:
1806 result = ( GEOSPreparedCrosses_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1807 break;
1808 case RelationWithin:
1809 result = ( GEOSPreparedWithin_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1810 break;
1811 case RelationContains:
1812 result = ( GEOSPreparedContains_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1813 break;
1814 case RelationDisjoint:
1815 result = ( GEOSPreparedDisjoint_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1816 break;
1817 case RelationOverlaps:
1818 result = ( GEOSPreparedOverlaps_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1819 break;
1820 }
1821 return result;
1822 }
1823
1824 switch ( r )
1825 {
1826 case RelationIntersects:
1827 result = ( GEOSIntersects_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1828 break;
1829 case RelationTouches:
1830 result = ( GEOSTouches_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1831 break;
1832 case RelationCrosses:
1833 result = ( GEOSCrosses_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1834 break;
1835 case RelationWithin:
1836 result = ( GEOSWithin_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1837 break;
1838 case RelationContains:
1839 result = ( GEOSContains_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1840 break;
1841 case RelationDisjoint:
1842 result = ( GEOSDisjoint_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1843 break;
1844 case RelationOverlaps:
1845 result = ( GEOSOverlaps_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1846 break;
1847 }
1848 }
1849 catch ( GEOSException &e )
1850 {
1851 logError( QStringLiteral( "GEOS" ), e.what() );
1852 if ( errorMsg )
1853 {
1854 *errorMsg = e.what();
1855 }
1856 return false;
1857 }
1858
1859 return result;
1860}
1861
1862QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, QString *errorMsg ) const
1863{
1864 if ( !mGeos )
1865 {
1866 return nullptr;
1867 }
1868
1870 try
1871 {
1872 geos.reset( GEOSBuffer_r( geosinit()->ctxt, mGeos.get(), distance, segments ) );
1873 }
1874 CATCH_GEOS_WITH_ERRMSG( nullptr )
1875 return fromGeos( geos.get() ).release();
1876}
1877
1878QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, Qgis::EndCapStyle endCapStyle, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg ) const
1879{
1880 if ( !mGeos )
1881 {
1882 return nullptr;
1883 }
1884
1886 try
1887 {
1888 geos.reset( GEOSBufferWithStyle_r( geosinit()->ctxt, mGeos.get(), distance, segments, static_cast< int >( endCapStyle ), static_cast< int >( joinStyle ), miterLimit ) );
1889 }
1890 CATCH_GEOS_WITH_ERRMSG( nullptr )
1891 return fromGeos( geos.get() ).release();
1892}
1893
1894QgsAbstractGeometry *QgsGeos::simplify( double tolerance, QString *errorMsg ) const
1895{
1896 if ( !mGeos )
1897 {
1898 return nullptr;
1899 }
1901 try
1902 {
1903 geos.reset( GEOSTopologyPreserveSimplify_r( geosinit()->ctxt, mGeos.get(), tolerance ) );
1904 }
1905 CATCH_GEOS_WITH_ERRMSG( nullptr )
1906 return fromGeos( geos.get() ).release();
1907}
1908
1909QgsAbstractGeometry *QgsGeos::interpolate( double distance, QString *errorMsg ) const
1910{
1911 if ( !mGeos )
1912 {
1913 return nullptr;
1914 }
1916 try
1917 {
1918 geos.reset( GEOSInterpolate_r( geosinit()->ctxt, mGeos.get(), distance ) );
1919 }
1920 CATCH_GEOS_WITH_ERRMSG( nullptr )
1921 return fromGeos( geos.get() ).release();
1922}
1923
1924QgsPoint *QgsGeos::centroid( QString *errorMsg ) const
1925{
1926 if ( !mGeos )
1927 {
1928 return nullptr;
1929 }
1930
1932 double x;
1933 double y;
1934
1935 try
1936 {
1937 geos.reset( GEOSGetCentroid_r( geosinit()->ctxt, mGeos.get() ) );
1938
1939 if ( !geos )
1940 return nullptr;
1941
1942 GEOSGeomGetX_r( geosinit()->ctxt, geos.get(), &x );
1943 GEOSGeomGetY_r( geosinit()->ctxt, geos.get(), &y );
1944 }
1945 CATCH_GEOS_WITH_ERRMSG( nullptr )
1946
1947 return new QgsPoint( x, y );
1948}
1949
1950QgsAbstractGeometry *QgsGeos::envelope( QString *errorMsg ) const
1951{
1952 if ( !mGeos )
1953 {
1954 return nullptr;
1955 }
1957 try
1958 {
1959 geos.reset( GEOSEnvelope_r( geosinit()->ctxt, mGeos.get() ) );
1960 }
1961 CATCH_GEOS_WITH_ERRMSG( nullptr )
1962 return fromGeos( geos.get() ).release();
1963}
1964
1965QgsPoint *QgsGeos::pointOnSurface( QString *errorMsg ) const
1966{
1967 if ( !mGeos )
1968 {
1969 return nullptr;
1970 }
1971
1972 double x;
1973 double y;
1974
1976 try
1977 {
1978 geos.reset( GEOSPointOnSurface_r( geosinit()->ctxt, mGeos.get() ) );
1979
1980 if ( !geos || GEOSisEmpty_r( geosinit()->ctxt, geos.get() ) != 0 )
1981 {
1982 return nullptr;
1983 }
1984
1985 GEOSGeomGetX_r( geosinit()->ctxt, geos.get(), &x );
1986 GEOSGeomGetY_r( geosinit()->ctxt, geos.get(), &y );
1987 }
1988 CATCH_GEOS_WITH_ERRMSG( nullptr )
1989
1990 return new QgsPoint( x, y );
1991}
1992
1993QgsAbstractGeometry *QgsGeos::convexHull( QString *errorMsg ) const
1994{
1995 if ( !mGeos )
1996 {
1997 return nullptr;
1998 }
1999
2000 try
2001 {
2002 geos::unique_ptr cHull( GEOSConvexHull_r( geosinit()->ctxt, mGeos.get() ) );
2003 std::unique_ptr< QgsAbstractGeometry > cHullGeom = fromGeos( cHull.get() );
2004 return cHullGeom.release();
2005 }
2006 CATCH_GEOS_WITH_ERRMSG( nullptr )
2007}
2008
2009QgsAbstractGeometry *QgsGeos::concaveHull( double targetPercent, bool allowHoles, QString *errorMsg ) const
2010{
2011#if GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR<11
2012 ( void )allowHoles;
2013 ( void )targetPercent;
2014 ( void )errorMsg;
2015 throw QgsNotSupportedException( QObject::tr( "Calculating concaveHull requires a QGIS build based on GEOS 3.11 or later" ) );
2016#else
2017 if ( !mGeos )
2018 {
2019 return nullptr;
2020 }
2021
2022 try
2023 {
2024 geos::unique_ptr concaveHull( GEOSConcaveHull_r( geosinit()->ctxt, mGeos.get(), targetPercent, allowHoles ) );
2025 std::unique_ptr< QgsAbstractGeometry > concaveHullGeom = fromGeos( concaveHull.get() );
2026 return concaveHullGeom.release();
2027 }
2028 CATCH_GEOS_WITH_ERRMSG( nullptr )
2029#endif
2030}
2031
2032bool QgsGeos::isValid( QString *errorMsg, const bool allowSelfTouchingHoles, QgsGeometry *errorLoc ) const
2033{
2034 if ( !mGeos )
2035 {
2036 return false;
2037 }
2038
2039 try
2040 {
2041 GEOSGeometry *g1 = nullptr;
2042 char *r = nullptr;
2043 char res = GEOSisValidDetail_r( geosinit()->ctxt, mGeos.get(), allowSelfTouchingHoles ? GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE : 0, &r, &g1 );
2044 const bool invalid = res != 1;
2045
2046 QString error;
2047 if ( r )
2048 {
2049 error = QString( r );
2050 GEOSFree_r( geosinit()->ctxt, r );
2051 }
2052
2053 if ( invalid && errorMsg )
2054 {
2055 static QgsStringMap translatedErrors;
2056
2057 if ( translatedErrors.empty() )
2058 {
2059 // Copied from https://git.osgeo.org/gitea/geos/geos/src/branch/master/src/operation/valid/TopologyValidationError.cpp
2060 translatedErrors.insert( QStringLiteral( "topology validation error" ), QObject::tr( "Topology validation error", "GEOS Error" ) );
2061 translatedErrors.insert( QStringLiteral( "repeated point" ), QObject::tr( "Repeated point", "GEOS Error" ) );
2062 translatedErrors.insert( QStringLiteral( "hole lies outside shell" ), QObject::tr( "Hole lies outside shell", "GEOS Error" ) );
2063 translatedErrors.insert( QStringLiteral( "holes are nested" ), QObject::tr( "Holes are nested", "GEOS Error" ) );
2064 translatedErrors.insert( QStringLiteral( "interior is disconnected" ), QObject::tr( "Interior is disconnected", "GEOS Error" ) );
2065 translatedErrors.insert( QStringLiteral( "self-intersection" ), QObject::tr( "Self-intersection", "GEOS Error" ) );
2066 translatedErrors.insert( QStringLiteral( "ring self-intersection" ), QObject::tr( "Ring self-intersection", "GEOS Error" ) );
2067 translatedErrors.insert( QStringLiteral( "nested shells" ), QObject::tr( "Nested shells", "GEOS Error" ) );
2068 translatedErrors.insert( QStringLiteral( "duplicate rings" ), QObject::tr( "Duplicate rings", "GEOS Error" ) );
2069 translatedErrors.insert( QStringLiteral( "too few points in geometry component" ), QObject::tr( "Too few points in geometry component", "GEOS Error" ) );
2070 translatedErrors.insert( QStringLiteral( "invalid coordinate" ), QObject::tr( "Invalid coordinate", "GEOS Error" ) );
2071 translatedErrors.insert( QStringLiteral( "ring is not closed" ), QObject::tr( "Ring is not closed", "GEOS Error" ) );
2072 }
2073
2074 *errorMsg = translatedErrors.value( error.toLower(), error );
2075
2076 if ( g1 && errorLoc )
2077 {
2078 *errorLoc = geometryFromGeos( g1 );
2079 }
2080 else if ( g1 )
2081 {
2082 GEOSGeom_destroy_r( geosinit()->ctxt, g1 );
2083 }
2084 }
2085 return !invalid;
2086 }
2087 CATCH_GEOS_WITH_ERRMSG( false )
2088}
2089
2090bool QgsGeos::isEqual( const QgsAbstractGeometry *geom, QString *errorMsg ) const
2091{
2092 if ( !mGeos || !geom )
2093 {
2094 return false;
2095 }
2096
2097 try
2098 {
2099 geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
2100 if ( !geosGeom )
2101 {
2102 return false;
2103 }
2104 bool equal = GEOSEquals_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() );
2105 return equal;
2106 }
2107 CATCH_GEOS_WITH_ERRMSG( false )
2108}
2109
2110bool QgsGeos::isEmpty( QString *errorMsg ) const
2111{
2112 if ( !mGeos )
2113 {
2114 return false;
2115 }
2116
2117 try
2118 {
2119 return GEOSisEmpty_r( geosinit()->ctxt, mGeos.get() );
2120 }
2121 CATCH_GEOS_WITH_ERRMSG( false )
2122}
2123
2124bool QgsGeos::isSimple( QString *errorMsg ) const
2125{
2126 if ( !mGeos )
2127 {
2128 return false;
2129 }
2130
2131 try
2132 {
2133 return GEOSisSimple_r( geosinit()->ctxt, mGeos.get() );
2134 }
2135 CATCH_GEOS_WITH_ERRMSG( false )
2136}
2137
2138GEOSCoordSequence *QgsGeos::createCoordinateSequence( const QgsCurve *curve, double precision, bool forceClose )
2139{
2140 GEOSContextHandle_t ctxt = geosinit()->ctxt;
2141
2142 std::unique_ptr< QgsLineString > segmentized;
2143 const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( curve );
2144
2145 if ( !line )
2146 {
2147 segmentized.reset( curve->curveToLine() );
2148 line = segmentized.get();
2149 }
2150
2151 if ( !line )
2152 {
2153 return nullptr;
2154 }
2155 GEOSCoordSequence *coordSeq = nullptr;
2156
2157 const int numPoints = line->numPoints();
2158
2159 const bool hasZ = line->is3D();
2160
2161#if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=10 )
2162 if ( qgsDoubleNear( precision, 0 ) )
2163 {
2164 if ( !forceClose || ( line->pointN( 0 ) == line->pointN( numPoints - 1 ) ) )
2165 {
2166 // use optimised method if we don't have to force close an open ring
2167 try
2168 {
2169 coordSeq = GEOSCoordSeq_copyFromArrays_r( ctxt, line->xData(), line->yData(), line->zData(), nullptr, numPoints );
2170 if ( !coordSeq )
2171 {
2172 QgsDebugMsg( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for %1 points" ).arg( numPoints ) );
2173 return nullptr;
2174 }
2175 }
2176 CATCH_GEOS( nullptr )
2177 }
2178 else
2179 {
2180 QVector< double > x = line->xVector();
2181 if ( numPoints > 0 )
2182 x.append( x.at( 0 ) );
2183 QVector< double > y = line->yVector();
2184 if ( numPoints > 0 )
2185 y.append( y.at( 0 ) );
2186 QVector< double > z = line->zVector();
2187 if ( hasZ && numPoints > 0 )
2188 z.append( z.at( 0 ) );
2189 try
2190 {
2191 coordSeq = GEOSCoordSeq_copyFromArrays_r( ctxt, x.constData(), y.constData(), !hasZ ? nullptr : z.constData(), nullptr, numPoints + 1 );
2192 if ( !coordSeq )
2193 {
2194 QgsDebugMsg( QStringLiteral( "GEOS Exception: Could not create closed coordinate sequence for %1 points" ).arg( numPoints + 1 ) );
2195 return nullptr;
2196 }
2197 }
2198 CATCH_GEOS( nullptr )
2199 }
2200 return coordSeq;
2201 }
2202#endif
2203
2204 int coordDims = 2;
2205 const bool hasM = false; //line->isMeasure(); //disabled until geos supports m-coordinates
2206
2207 if ( hasZ )
2208 {
2209 ++coordDims;
2210 }
2211 if ( hasM )
2212 {
2213 ++coordDims;
2214 }
2215
2216 int numOutPoints = numPoints;
2217 if ( forceClose && ( line->pointN( 0 ) != line->pointN( numPoints - 1 ) ) )
2218 {
2219 ++numOutPoints;
2220 }
2221
2222 try
2223 {
2224 coordSeq = GEOSCoordSeq_create_r( ctxt, numOutPoints, coordDims );
2225 if ( !coordSeq )
2226 {
2227 QgsDebugMsg( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for %1 points in %2 dimensions" ).arg( numPoints ).arg( coordDims ) );
2228 return nullptr;
2229 }
2230
2231 const double *xData = line->xData();
2232 const double *yData = line->yData();
2233 const double *zData = hasZ ? line->zData() : nullptr;
2234 const double *mData = hasM ? line->mData() : nullptr;
2235
2236 if ( precision > 0. )
2237 {
2238 for ( int i = 0; i < numOutPoints; ++i )
2239 {
2240 if ( i >= numPoints )
2241 {
2242 // start reading back from start of line
2243 xData = line->xData();
2244 yData = line->yData();
2245 zData = hasZ ? line->zData() : nullptr;
2246 mData = hasM ? line->mData() : nullptr;
2247 }
2248 if ( hasZ )
2249 {
2250 GEOSCoordSeq_setXYZ_r( ctxt, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision, std::round( *zData++ / precision ) * precision );
2251 }
2252 else
2253 {
2254 GEOSCoordSeq_setXY_r( ctxt, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision );
2255 }
2256 if ( hasM )
2257 {
2258 GEOSCoordSeq_setOrdinate_r( ctxt, coordSeq, i, 3, *mData++ );
2259 }
2260 }
2261 }
2262 else
2263 {
2264 for ( int i = 0; i < numOutPoints; ++i )
2265 {
2266 if ( i >= numPoints )
2267 {
2268 // start reading back from start of line
2269 xData = line->xData();
2270 yData = line->yData();
2271 zData = hasZ ? line->zData() : nullptr;
2272 mData = hasM ? line->mData() : nullptr;
2273 }
2274 if ( hasZ )
2275 {
2276 GEOSCoordSeq_setXYZ_r( ctxt, coordSeq, i, *xData++, *yData++, *zData++ );
2277 }
2278 else
2279 {
2280 GEOSCoordSeq_setXY_r( ctxt, coordSeq, i, *xData++, *yData++ );
2281 }
2282 if ( hasM )
2283 {
2284 GEOSCoordSeq_setOrdinate_r( ctxt, coordSeq, i, 3, *mData++ );
2285 }
2286 }
2287 }
2288 }
2289 CATCH_GEOS( nullptr )
2290
2291 return coordSeq;
2292}
2293
2294geos::unique_ptr QgsGeos::createGeosPoint( const QgsAbstractGeometry *point, int coordDims, double precision )
2295{
2296 const QgsPoint *pt = qgsgeometry_cast<const QgsPoint *>( point );
2297 if ( !pt )
2298 return nullptr;
2299
2300 return createGeosPointXY( pt->x(), pt->y(), pt->is3D(), pt->z(), pt->isMeasure(), pt->m(), coordDims, precision );
2301}
2302
2303geos::unique_ptr QgsGeos::createGeosPointXY( double x, double y, bool hasZ, double z, bool hasM, double m, int coordDims, double precision )
2304{
2305 Q_UNUSED( hasM )
2306 Q_UNUSED( m )
2307
2308 geos::unique_ptr geosPoint;
2309 try
2310 {
2311 if ( coordDims == 2 )
2312 {
2313 // optimised constructor
2314 if ( precision > 0. )
2315 geosPoint.reset( GEOSGeom_createPointFromXY_r( geosinit()->ctxt, std::round( x / precision ) * precision, std::round( y / precision ) * precision ) );
2316 else
2317 geosPoint.reset( GEOSGeom_createPointFromXY_r( geosinit()->ctxt, x, y ) );
2318 return geosPoint;
2319 }
2320
2321 GEOSCoordSequence *coordSeq = GEOSCoordSeq_create_r( geosinit()->ctxt, 1, coordDims );
2322 if ( !coordSeq )
2323 {
2324 QgsDebugMsg( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for point with %1 dimensions" ).arg( coordDims ) );
2325 return nullptr;
2326 }
2327 if ( precision > 0. )
2328 {
2329 GEOSCoordSeq_setX_r( geosinit()->ctxt, coordSeq, 0, std::round( x / precision ) * precision );
2330 GEOSCoordSeq_setY_r( geosinit()->ctxt, coordSeq, 0, std::round( y / precision ) * precision );
2331 if ( hasZ )
2332 {
2333 GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, 0, 2, std::round( z / precision ) * precision );
2334 }
2335 }
2336 else
2337 {
2338 GEOSCoordSeq_setX_r( geosinit()->ctxt, coordSeq, 0, x );
2339 GEOSCoordSeq_setY_r( geosinit()->ctxt, coordSeq, 0, y );
2340 if ( hasZ )
2341 {
2342 GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, 0, 2, z );
2343 }
2344 }
2345#if 0 //disabled until geos supports m-coordinates
2346 if ( hasM )
2347 {
2348 GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, 0, 3, m );
2349 }
2350#endif
2351 geosPoint.reset( GEOSGeom_createPoint_r( geosinit()->ctxt, coordSeq ) );
2352 }
2353 CATCH_GEOS( nullptr )
2354 return geosPoint;
2355}
2356
2357geos::unique_ptr QgsGeos::createGeosLinestring( const QgsAbstractGeometry *curve, double precision )
2358{
2359 const QgsCurve *c = qgsgeometry_cast<const QgsCurve *>( curve );
2360 if ( !c )
2361 return nullptr;
2362
2363 GEOSCoordSequence *coordSeq = createCoordinateSequence( c, precision );
2364 if ( !coordSeq )
2365 return nullptr;
2366
2367 geos::unique_ptr geosGeom;
2368 try
2369 {
2370 geosGeom.reset( GEOSGeom_createLineString_r( geosinit()->ctxt, coordSeq ) );
2371 }
2372 CATCH_GEOS( nullptr )
2373 return geosGeom;
2374}
2375
2376geos::unique_ptr QgsGeos::createGeosPolygon( const QgsAbstractGeometry *poly, double precision )
2377{
2378 const QgsCurvePolygon *polygon = qgsgeometry_cast<const QgsCurvePolygon *>( poly );
2379 if ( !polygon )
2380 return nullptr;
2381
2382 const QgsCurve *exteriorRing = polygon->exteriorRing();
2383 if ( !exteriorRing )
2384 {
2385 return nullptr;
2386 }
2387
2388 geos::unique_ptr geosPolygon;
2389 try
2390 {
2391 geos::unique_ptr exteriorRingGeos( GEOSGeom_createLinearRing_r( geosinit()->ctxt, createCoordinateSequence( exteriorRing, precision, true ) ) );
2392
2393 int nHoles = polygon->numInteriorRings();
2394 GEOSGeometry **holes = nullptr;
2395 if ( nHoles > 0 )
2396 {
2397 holes = new GEOSGeometry*[ nHoles ];
2398 }
2399
2400 for ( int i = 0; i < nHoles; ++i )
2401 {
2402 const QgsCurve *interiorRing = polygon->interiorRing( i );
2403 holes[i] = GEOSGeom_createLinearRing_r( geosinit()->ctxt, createCoordinateSequence( interiorRing, precision, true ) );
2404 }
2405 geosPolygon.reset( GEOSGeom_createPolygon_r( geosinit()->ctxt, exteriorRingGeos.release(), holes, nHoles ) );
2406 delete[] holes;
2407 }
2408 CATCH_GEOS( nullptr )
2409
2410 return geosPolygon;
2411}
2412
2413QgsAbstractGeometry *QgsGeos::offsetCurve( double distance, int segments, Qgis::JoinStyle joinStyle, double miterLimit, QString *errorMsg ) const
2414{
2415 if ( !mGeos )
2416 return nullptr;
2417
2418 geos::unique_ptr offset;
2419 try
2420 {
2421 // Force quadrant segments to be at least 8, see
2422 // https://github.com/qgis/QGIS/issues/53165#issuecomment-1563470832
2423 if ( segments < 8 ) segments = 8;
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:72
BufferSide
Side of line to buffer.
Definition qgis.h:1008
@ Right
Buffer to right of line.
GeometryOperationResult
Success or failure of a geometry operation.
Definition qgis.h:955
@ 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:1033
EndCapStyle
End cap styles for buffers.
Definition qgis.h:1020
MakeValidMethod
Algorithms to use when repairing invalid geometries.
Definition qgis.h:1046
@ 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...
Abstract base class for all geometries.
bool isMeasure() const
Returns true if the geometry contains m values.
virtual QgsRectangle boundingBox() const =0
Returns the minimal bounding box for the geometry.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
QgsWkbTypes::Type wkbType() const
Returns the WKB type of the geometry.
virtual bool isEmpty() const
Returns true if the geometry is empty.
virtual int dimension() const =0
Returns the inherent dimension of the geometry.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
Curve polygon geometry type.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Abstract base class for curved geometry type.
Definition qgscurve.h: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.
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(QgsWkbTypes::Type type)
Returns a new geometry collection matching a specified WKB type.
Encapsulates parameters under which a geometry operation is performed.
double gridSize() const
Returns the grid size which will be used to snap vertices of a geometry.
A geometry is the spatial representation of a feature.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
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:2032
std::unique_ptr< QgsAbstractGeometry > subdivide(int maxNodes, QString *errorMsg=nullptr, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const
Subdivides the geometry.
Definition qgsgeos.cpp:413
QgsAbstractGeometry * intersection(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const override
Calculate the intersection of this and geom.
Definition qgsgeos.cpp:280
double hausdorffDistance(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const
Returns the Hausdorff distance between this geometry and geom.
Definition qgsgeos.cpp:637
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0)
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:220
QgsAbstractGeometry * buffer(double distance, int segments, QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:1862
QgsAbstractGeometry * concaveHull(double targetPercent, bool allowHoles=false, QString *errorMsg=nullptr) const
Returns a possibly concave geometry that encloses the input geometry.
Definition qgsgeos.cpp:2009
std::unique_ptr< QgsAbstractGeometry > reshapeGeometry(const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg=nullptr) const
Reshapes the geometry using a line.
Definition qgsgeos.cpp: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:1894
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:1488
std::unique_ptr< QgsAbstractGeometry > minimumClearanceLine(QString *errorMsg=nullptr) const
Returns a LineString whose endpoints define the minimum clearance of a geometry.
Definition qgsgeos.cpp:2527
QgsAbstractGeometry * envelope(QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:1950
QString relate(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Returns the Dimensional Extended 9 Intersection Model (DE-9IM) representation of the relationship bet...
Definition qgsgeos.cpp:764
bool crosses(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom crosses this.
Definition qgsgeos.cpp:739
QgsAbstractGeometry * convexHull(QString *errorMsg=nullptr) const override
Calculate the convex hull of this.
Definition qgsgeos.cpp:1993
double distance(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Calculates the distance between this and geom.
Definition qgsgeos.cpp:507
std::unique_ptr< QgsAbstractGeometry > minimumWidth(QString *errorMsg=nullptr) const
Returns a linestring geometry which represents the minimum diameter of the geometry.
Definition qgsgeos.cpp:2493
bool isSimple(QString *errorMsg=nullptr) const override
Determines whether the geometry is simple (according to OGC definition).
Definition qgsgeos.cpp:2124
QgsAbstractGeometry * combine(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const override
Calculate the combination of this and geom.
Definition qgsgeos.cpp:433
bool within(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom is within this.
Definition qgsgeos.cpp:744
void prepareGeometry() override
Prepares the geometry, so that subsequent calls to spatial relation methods are much faster.
Definition qgsgeos.cpp:252
static std::unique_ptr< QgsAbstractGeometry > fromGeos(const GEOSGeometry *geos)
Create a geometry from a GEOSGeometry.
Definition qgsgeos.cpp:1388
QgsAbstractGeometry * interpolate(double distance, QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:1909
bool distanceWithin(const QgsAbstractGeometry *geom, double maxdistance, QString *errorMsg=nullptr) const override
Checks if geom is within maxdistance distance from this geometry.
Definition qgsgeos.cpp:565
bool isEqual(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if this is equal to geom.
Definition qgsgeos.cpp:2090
bool contains(double x, double y, QString *errorMsg=nullptr) const
Returns true if the geometry contains the point at (x, y).
Definition qgsgeos.cpp:608
QgsGeometry mergeLines(QString *errorMsg=nullptr) const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
Definition qgsgeos.cpp: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:2413
static GEOSContextHandle_t getGEOSHandler()
Definition qgsgeos.cpp:3450
QgsPoint * centroid(QString *errorMsg=nullptr) const override
Calculates the centroid of this.
Definition qgsgeos.cpp:1924
double lineLocatePoint(const QgsPoint &point, QString *errorMsg=nullptr) const
Returns a distance representing the location along this linestring of the closest point on this lines...
Definition qgsgeos.cpp:2814
bool isEmpty(QString *errorMsg=nullptr) const override
Definition qgsgeos.cpp:2110
static Qgis::GeometryOperationResult addPart(QgsGeometry &geometry, GEOSGeometry *newPart)
Adds a new island polygon to a multipolygon feature.
Definition qgsgeos.cpp:230
double frechetDistance(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const
Returns the Fréchet distance between this geometry and geom, restricted to discrete points for both g...
Definition qgsgeos.cpp:683
QgsGeometry voronoiDiagram(const QgsAbstractGeometry *extent=nullptr, double tolerance=0.0, bool edgesOnly=false, QString *errorMsg=nullptr) const
Creates a Voronoi diagram for the nodes contained within the geometry.
Definition qgsgeos.cpp: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:1578
QgsPoint * pointOnSurface(QString *errorMsg=nullptr) const override
Calculate a point that is guaranteed to be on the surface of this.
Definition qgsgeos.cpp:1965
static QgsGeometry geometryFromGeos(GEOSGeometry *geos)
Creates a new QgsGeometry object, feeding in a geometry in GEOS format.
Definition qgsgeos.cpp:150
Line string geometry type, with support for z-dimension and m-values.
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
QVector< double > xVector() const
Returns the x vertex values as a vector.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
int numPoints() const override
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
QVector< double > yVector() const
Returns the y vertex values as a vector.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
QVector< double > zVector() const
Returns the z vertex values as a vector.
double closestSegment(const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf=nullptr, double epsilon=4 *std::numeric_limits< double >::epsilon()) const override
Searches for the closest segment of the geometry to a given point.
const double * mData() const
Returns a const pointer to the m vertex data, or nullptr if the linestring does not have m values.
void addVertex(const QgsPoint &pt)
Adds a new vertex to the end of the line string.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
Multi curve geometry collection.
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Multi line string geometry collection.
Multi point geometry collection.
Multi polygon geometry collection.
Custom exception class which is raised when an operation is not supported.
A class to represent a 2D point.
Definition qgspointxy.h:59
double y
Definition qgspointxy.h:63
double x
Definition qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double z
Definition qgspoint.h:54
double x
Definition qgspoint.h:52
double distance(double x, double y) const
Returns the Cartesian 2D distance between this point and a specified x, y coordinate.
Definition qgspoint.h:343
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.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
void setYMinimum(double y)
Set the minimum y value.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
void setXMinimum(double x)
Set the minimum x value.
double width() const
Returns the width of the rectangle.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
bool isEmpty() const
Returns true if the rectangle is empty.
double height() const
Returns the height of the rectangle.
static GeometryType geometryType(Type type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Type
The WKB type describes the number of dimensions a geometry has.
Definition qgswkbtypes.h:70
static Type flatType(Type type)
Returns the flat type for a WKB type.
static bool isMultiType(Type type)
Returns true if the WKB type is a multi type.
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:2527
QMap< QString, QString > QgsStringMap
Definition qgis.h:3022
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.