QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgsgeos.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsgeos.cpp
3  -------------------------------------------------------------------
4 Date : 22 Sept 2014
5 Copyright : (C) 2014 by Marco Hugentobler
6 email : 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"
18 #include "qgsgeometrycollection.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"
27 #include "qgsgeometryeditutils.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 
51 static 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 
83 static void printGEOSNotice( const char *fmt, ... )
84 {
85 #if defined(QGISDEBUG)
86  va_list ap;
87  char buffer[1024];
88 
89  va_start( ap, fmt );
90  vsnprintf( buffer, sizeof buffer, fmt, ap );
91  va_end( ap );
92 #else
93  Q_UNUSED( fmt )
94 #endif
95 }
96 
97 class 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 
116 Q_GLOBAL_STATIC( GEOSInit, geosinit )
117 
118 void geos::GeosDeleter::operator()( GEOSGeometry *geom )
119 {
120  GEOSGeom_destroy_r( geosinit()->ctxt, geom );
121 }
122 
123 void geos::GeosDeleter::operator()( const GEOSPreparedGeometry *geom )
124 {
125  GEOSPreparedGeom_destroy_r( geosinit()->ctxt, geom );
126 }
127 
128 void geos::GeosDeleter::operator()( GEOSBufferParams *params )
129 {
130  GEOSBufferParams_destroy_r( geosinit()->ctxt, params );
131 }
132 
133 void geos::GeosDeleter::operator()( GEOSCoordSequence *sequence )
134 {
135  GEOSCoordSeq_destroy_r( geosinit()->ctxt, sequence );
136 }
137 
138 
140 
141 
143  : QgsGeometryEngine( geometry )
144  , mGeos( nullptr )
145  , mPrecision( precision )
146 {
147  cacheGeos();
148 }
149 
151 {
153  GEOSGeom_destroy_r( QgsGeos::getGEOSHandler(), geos );
154  return g;
155 }
156 
158 {
159  QgsGeometry g( QgsGeos::fromGeos( geos.get() ) );
160  return g;
161 }
162 
164 {
165  if ( geometry.isNull() )
166  {
167  return nullptr;
168  }
169 
170  return asGeos( geometry.constGet(), precision );
171 }
172 
173 QgsGeometry::OperationResult QgsGeos::addPart( QgsGeometry &geometry, GEOSGeometry *newPart )
174 {
175  if ( geometry.isNull() )
176  {
178  }
179  if ( !newPart )
180  {
182  }
183 
184  std::unique_ptr< QgsAbstractGeometry > geom = fromGeos( newPart );
185  return QgsGeometryEditUtils::addPart( geometry.get(), std::move( geom ) );
186 }
187 
189 {
190  mGeos.reset();
191  mGeosPrepared.reset();
192  cacheGeos();
193 }
194 
196 {
197  mGeosPrepared.reset();
198  if ( mGeos )
199  {
200  mGeosPrepared.reset( GEOSPrepare_r( geosinit()->ctxt, mGeos.get() ) );
201  }
202 }
203 
204 void QgsGeos::cacheGeos() const
205 {
206  if ( !mGeometry )
207  {
208  return;
209  }
210 
211  mGeos = asGeos( mGeometry, mPrecision );
212 }
213 
214 QgsAbstractGeometry *QgsGeos::intersection( const QgsAbstractGeometry *geom, QString *errorMsg ) const
215 {
216  return overlay( geom, OverlayIntersection, errorMsg ).release();
217 }
218 
219 QgsAbstractGeometry *QgsGeos::difference( const QgsAbstractGeometry *geom, QString *errorMsg ) const
220 {
221  return overlay( geom, OverlayDifference, errorMsg ).release();
222 }
223 
224 std::unique_ptr<QgsAbstractGeometry> QgsGeos::clip( const QgsRectangle &rect, QString *errorMsg ) const
225 {
226  if ( !mGeos || rect.isNull() || rect.isEmpty() )
227  {
228  return nullptr;
229  }
230 
231  try
232  {
233  geos::unique_ptr opGeom( GEOSClipByRect_r( geosinit()->ctxt, mGeos.get(), rect.xMinimum(), rect.yMinimum(), rect.xMaximum(), rect.yMaximum() ) );
234  return fromGeos( opGeom.get() );
235  }
236  catch ( GEOSException &e )
237  {
238  logError( QStringLiteral( "GEOS" ), e.what() );
239  if ( errorMsg )
240  {
241  *errorMsg = e.what();
242  }
243  return nullptr;
244  }
245 }
246 
247 
248 
249 
250 void QgsGeos::subdivideRecursive( const GEOSGeometry *currentPart, int maxNodes, int depth, QgsGeometryCollection *parts, const QgsRectangle &clipRect ) const
251 {
252  int partType = GEOSGeomTypeId_r( geosinit()->ctxt, currentPart );
253  if ( qgsDoubleNear( clipRect.width(), 0.0 ) && qgsDoubleNear( clipRect.height(), 0.0 ) )
254  {
255  if ( partType == GEOS_POINT )
256  {
257  parts->addGeometry( fromGeos( currentPart ).release() );
258  return;
259  }
260  else
261  {
262  return;
263  }
264  }
265 
266  if ( partType == GEOS_MULTILINESTRING || partType == GEOS_MULTIPOLYGON || partType == GEOS_GEOMETRYCOLLECTION )
267  {
268  int partCount = GEOSGetNumGeometries_r( geosinit()->ctxt, currentPart );
269  for ( int i = 0; i < partCount; ++i )
270  {
271  subdivideRecursive( GEOSGetGeometryN_r( geosinit()->ctxt, currentPart, i ), maxNodes, depth, parts, clipRect );
272  }
273  return;
274  }
275 
276  if ( depth > 50 )
277  {
278  parts->addGeometry( fromGeos( currentPart ).release() );
279  return;
280  }
281 
282  int vertexCount = GEOSGetNumCoordinates_r( geosinit()->ctxt, currentPart );
283  if ( vertexCount == 0 )
284  {
285  return;
286  }
287  else if ( vertexCount < maxNodes )
288  {
289  parts->addGeometry( fromGeos( currentPart ).release() );
290  return;
291  }
292 
293  // chop clipping rect in half by longest side
294  double width = clipRect.width();
295  double height = clipRect.height();
296  QgsRectangle halfClipRect1 = clipRect;
297  QgsRectangle halfClipRect2 = clipRect;
298  if ( width > height )
299  {
300  halfClipRect1.setXMaximum( clipRect.xMinimum() + width / 2.0 );
301  halfClipRect2.setXMinimum( halfClipRect1.xMaximum() );
302  }
303  else
304  {
305  halfClipRect1.setYMaximum( clipRect.yMinimum() + height / 2.0 );
306  halfClipRect2.setYMinimum( halfClipRect1.yMaximum() );
307  }
308 
309  if ( height <= 0 )
310  {
311  halfClipRect1.setYMinimum( halfClipRect1.yMinimum() - std::numeric_limits<double>::epsilon() );
312  halfClipRect2.setYMinimum( halfClipRect2.yMinimum() - std::numeric_limits<double>::epsilon() );
313  halfClipRect1.setYMaximum( halfClipRect1.yMaximum() + std::numeric_limits<double>::epsilon() );
314  halfClipRect2.setYMaximum( halfClipRect2.yMaximum() + std::numeric_limits<double>::epsilon() );
315  }
316  if ( width <= 0 )
317  {
318  halfClipRect1.setXMinimum( halfClipRect1.xMinimum() - std::numeric_limits<double>::epsilon() );
319  halfClipRect2.setXMinimum( halfClipRect2.xMinimum() - std::numeric_limits<double>::epsilon() );
320  halfClipRect1.setXMaximum( halfClipRect1.xMaximum() + std::numeric_limits<double>::epsilon() );
321  halfClipRect2.setXMaximum( halfClipRect2.xMaximum() + std::numeric_limits<double>::epsilon() );
322  }
323 
324  geos::unique_ptr clipPart1( GEOSClipByRect_r( geosinit()->ctxt, currentPart, halfClipRect1.xMinimum(), halfClipRect1.yMinimum(), halfClipRect1.xMaximum(), halfClipRect1.yMaximum() ) );
325  geos::unique_ptr clipPart2( GEOSClipByRect_r( geosinit()->ctxt, currentPart, halfClipRect2.xMinimum(), halfClipRect2.yMinimum(), halfClipRect2.xMaximum(), halfClipRect2.yMaximum() ) );
326 
327  ++depth;
328 
329  if ( clipPart1 )
330  {
331  subdivideRecursive( clipPart1.get(), maxNodes, depth, parts, halfClipRect1 );
332  }
333  if ( clipPart2 )
334  {
335  subdivideRecursive( clipPart2.get(), maxNodes, depth, parts, halfClipRect2 );
336  }
337 }
338 
339 std::unique_ptr<QgsAbstractGeometry> QgsGeos::subdivide( int maxNodes, QString *errorMsg ) const
340 {
341  if ( !mGeos )
342  {
343  return nullptr;
344  }
345 
346  // minimum allowed max is 8
347  maxNodes = std::max( maxNodes, 8 );
348 
349  std::unique_ptr< QgsGeometryCollection > parts = QgsGeometryFactory::createCollectionOfType( mGeometry->wkbType() );
350  try
351  {
352  subdivideRecursive( mGeos.get(), maxNodes, 0, parts.get(), mGeometry->boundingBox() );
353  }
354  CATCH_GEOS_WITH_ERRMSG( nullptr )
355 
356  return std::move( parts );
357 }
358 
359 QgsAbstractGeometry *QgsGeos::combine( const QgsAbstractGeometry *geom, QString *errorMsg ) const
360 {
361  return overlay( geom, OverlayUnion, errorMsg ).release();
362 }
363 
364 QgsAbstractGeometry *QgsGeos::combine( const QVector<QgsAbstractGeometry *> &geomList, QString *errorMsg ) const
365 {
366  QVector< GEOSGeometry * > geosGeometries;
367  geosGeometries.reserve( geomList.size() );
368  for ( const QgsAbstractGeometry *g : geomList )
369  {
370  if ( !g )
371  continue;
372 
373  geosGeometries << asGeos( g, mPrecision ).release();
374  }
375 
376  geos::unique_ptr geomUnion;
377  try
378  {
379  geos::unique_ptr geomCollection = createGeosCollection( GEOS_GEOMETRYCOLLECTION, geosGeometries );
380  geomUnion.reset( GEOSUnaryUnion_r( geosinit()->ctxt, geomCollection.get() ) );
381  }
382  CATCH_GEOS_WITH_ERRMSG( nullptr )
383 
384  std::unique_ptr< QgsAbstractGeometry > result = fromGeos( geomUnion.get() );
385  return result.release();
386 }
387 
388 QgsAbstractGeometry *QgsGeos::combine( const QVector<QgsGeometry> &geomList, QString *errorMsg ) const
389 {
390  QVector< GEOSGeometry * > geosGeometries;
391  geosGeometries.reserve( geomList.size() );
392  for ( const QgsGeometry &g : geomList )
393  {
394  if ( g.isNull() )
395  continue;
396 
397  geosGeometries << asGeos( g.constGet(), mPrecision ).release();
398  }
399 
400  geos::unique_ptr geomUnion;
401  try
402  {
403  geos::unique_ptr geomCollection = createGeosCollection( GEOS_GEOMETRYCOLLECTION, geosGeometries );
404  geomUnion.reset( GEOSUnaryUnion_r( geosinit()->ctxt, geomCollection.get() ) );
405  }
406  CATCH_GEOS_WITH_ERRMSG( nullptr )
407 
408  std::unique_ptr< QgsAbstractGeometry > result = fromGeos( geomUnion.get() );
409  return result.release();
410 }
411 
412 QgsAbstractGeometry *QgsGeos::symDifference( const QgsAbstractGeometry *geom, QString *errorMsg ) const
413 {
414  return overlay( geom, OverlaySymDifference, errorMsg ).release();
415 }
416 
417 double QgsGeos::distance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
418 {
419  double distance = -1.0;
420  if ( !mGeos )
421  {
422  return distance;
423  }
424 
425  geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
426  if ( !otherGeosGeom )
427  {
428  return distance;
429  }
430 
431  try
432  {
433 #if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=9 )
434  if ( mGeosPrepared )
435  {
436  GEOSPreparedDistance_r( geosinit()->ctxt, mGeosPrepared.get(), otherGeosGeom.get(), &distance );
437  }
438  else
439  {
440  GEOSDistance_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), &distance );
441  }
442 #else
443  GEOSDistance_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), &distance );
444 #endif
445  }
446  CATCH_GEOS_WITH_ERRMSG( -1.0 )
447 
448  return distance;
449 }
450 
451 double QgsGeos::hausdorffDistance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
452 {
453  double distance = -1.0;
454  if ( !mGeos )
455  {
456  return distance;
457  }
458 
459  geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
460  if ( !otherGeosGeom )
461  {
462  return distance;
463  }
464 
465  try
466  {
467  GEOSHausdorffDistance_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), &distance );
468  }
469  CATCH_GEOS_WITH_ERRMSG( -1.0 )
470 
471  return distance;
472 }
473 
474 double QgsGeos::hausdorffDistanceDensify( const QgsAbstractGeometry *geom, double densifyFraction, QString *errorMsg ) const
475 {
476  double distance = -1.0;
477  if ( !mGeos )
478  {
479  return distance;
480  }
481 
482  geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
483  if ( !otherGeosGeom )
484  {
485  return distance;
486  }
487 
488  try
489  {
490  GEOSHausdorffDistanceDensify_r( geosinit()->ctxt, mGeos.get(), otherGeosGeom.get(), densifyFraction, &distance );
491  }
492  CATCH_GEOS_WITH_ERRMSG( -1.0 )
493 
494  return distance;
495 }
496 
497 bool QgsGeos::intersects( const QgsAbstractGeometry *geom, QString *errorMsg ) const
498 {
499  return relation( geom, RelationIntersects, errorMsg );
500 }
501 
502 bool QgsGeos::touches( const QgsAbstractGeometry *geom, QString *errorMsg ) const
503 {
504  return relation( geom, RelationTouches, errorMsg );
505 }
506 
507 bool QgsGeos::crosses( const QgsAbstractGeometry *geom, QString *errorMsg ) const
508 {
509  return relation( geom, RelationCrosses, errorMsg );
510 }
511 
512 bool QgsGeos::within( const QgsAbstractGeometry *geom, QString *errorMsg ) const
513 {
514  return relation( geom, RelationWithin, errorMsg );
515 }
516 
517 bool QgsGeos::overlaps( const QgsAbstractGeometry *geom, QString *errorMsg ) const
518 {
519  return relation( geom, RelationOverlaps, errorMsg );
520 }
521 
522 bool QgsGeos::contains( const QgsAbstractGeometry *geom, QString *errorMsg ) const
523 {
524  return relation( geom, RelationContains, errorMsg );
525 }
526 
527 bool QgsGeos::disjoint( const QgsAbstractGeometry *geom, QString *errorMsg ) const
528 {
529  return relation( geom, RelationDisjoint, errorMsg );
530 }
531 
532 QString QgsGeos::relate( const QgsAbstractGeometry *geom, QString *errorMsg ) const
533 {
534  if ( !mGeos )
535  {
536  return QString();
537  }
538 
539  geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
540  if ( !geosGeom )
541  {
542  return QString();
543  }
544 
545  QString result;
546  try
547  {
548  char *r = GEOSRelate_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() );
549  if ( r )
550  {
551  result = QString( r );
552  GEOSFree_r( geosinit()->ctxt, r );
553  }
554  }
555  catch ( GEOSException &e )
556  {
557  logError( QStringLiteral( "GEOS" ), e.what() );
558  if ( errorMsg )
559  {
560  *errorMsg = e.what();
561  }
562  }
563 
564  return result;
565 }
566 
567 bool QgsGeos::relatePattern( const QgsAbstractGeometry *geom, const QString &pattern, QString *errorMsg ) const
568 {
569  if ( !mGeos || !geom )
570  {
571  return false;
572  }
573 
574  geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
575  if ( !geosGeom )
576  {
577  return false;
578  }
579 
580  bool result = false;
581  try
582  {
583  result = ( GEOSRelatePattern_r( geosinit()->ctxt, mGeos.get(), geosGeom.get(), pattern.toLocal8Bit().constData() ) == 1 );
584  }
585  catch ( GEOSException &e )
586  {
587  logError( QStringLiteral( "GEOS" ), e.what() );
588  if ( errorMsg )
589  {
590  *errorMsg = e.what();
591  }
592  }
593 
594  return result;
595 }
596 
597 double QgsGeos::area( QString *errorMsg ) const
598 {
599  double area = -1.0;
600  if ( !mGeos )
601  {
602  return area;
603  }
604 
605  try
606  {
607  if ( GEOSArea_r( geosinit()->ctxt, mGeos.get(), &area ) != 1 )
608  return -1.0;
609  }
610  CATCH_GEOS_WITH_ERRMSG( -1.0 )
611  return area;
612 }
613 
614 double QgsGeos::length( QString *errorMsg ) const
615 {
616  double length = -1.0;
617  if ( !mGeos )
618  {
619  return length;
620  }
621  try
622  {
623  if ( GEOSLength_r( geosinit()->ctxt, mGeos.get(), &length ) != 1 )
624  return -1.0;
625  }
626  CATCH_GEOS_WITH_ERRMSG( -1.0 )
627  return length;
628 }
629 
631  QVector<QgsGeometry> &newGeometries,
632  bool topological,
633  QgsPointSequence &topologyTestPoints,
634  QString *errorMsg, bool skipIntersectionCheck ) const
635 {
636 
637  EngineOperationResult returnCode = Success;
638  if ( !mGeos || !mGeometry )
639  {
640  return InvalidBaseGeometry;
641  }
642 
643  //return if this type is point/multipoint
644  if ( mGeometry->dimension() == 0 )
645  {
646  return SplitCannotSplitPoint; //cannot split points
647  }
648 
649  if ( !GEOSisValid_r( geosinit()->ctxt, mGeos.get() ) )
650  return InvalidBaseGeometry;
651 
652  //make sure splitLine is valid
653  if ( ( mGeometry->dimension() == 1 && splitLine.numPoints() < 1 ) ||
654  ( mGeometry->dimension() == 2 && splitLine.numPoints() < 2 ) )
655  return InvalidInput;
656 
657  newGeometries.clear();
658  geos::unique_ptr splitLineGeos;
659 
660  try
661  {
662  if ( splitLine.numPoints() > 1 )
663  {
664  splitLineGeos = createGeosLinestring( &splitLine, mPrecision );
665  }
666  else if ( splitLine.numPoints() == 1 )
667  {
668  splitLineGeos = createGeosPointXY( splitLine.xAt( 0 ), splitLine.yAt( 0 ), false, 0, false, 0, 2, mPrecision );
669  }
670  else
671  {
672  return InvalidInput;
673  }
674 
675  if ( !GEOSisValid_r( geosinit()->ctxt, splitLineGeos.get() ) || !GEOSisSimple_r( geosinit()->ctxt, splitLineGeos.get() ) )
676  {
677  return InvalidInput;
678  }
679 
680  if ( topological )
681  {
682  //find out candidate points for topological corrections
683  if ( !topologicalTestPointsSplit( splitLineGeos.get(), topologyTestPoints ) )
684  {
685  return InvalidInput; // TODO: is it really an invalid input?
686  }
687  }
688 
689  //call split function depending on geometry type
690  if ( mGeometry->dimension() == 1 )
691  {
692  returnCode = splitLinearGeometry( splitLineGeos.get(), newGeometries, skipIntersectionCheck );
693  }
694  else if ( mGeometry->dimension() == 2 )
695  {
696  returnCode = splitPolygonGeometry( splitLineGeos.get(), newGeometries, skipIntersectionCheck );
697  }
698  else
699  {
700  return InvalidInput;
701  }
702  }
704 
705  return returnCode;
706 }
707 
708 
709 
710 bool QgsGeos::topologicalTestPointsSplit( const GEOSGeometry *splitLine, QgsPointSequence &testPoints, QString *errorMsg ) const
711 {
712  //Find out the intersection points between splitLineGeos and this geometry.
713  //These points need to be tested for topological correctness by the calling function
714  //if topological editing is enabled
715 
716  if ( !mGeos )
717  {
718  return false;
719  }
720 
721  try
722  {
723  testPoints.clear();
724  geos::unique_ptr intersectionGeom( GEOSIntersection_r( geosinit()->ctxt, mGeos.get(), splitLine ) );
725  if ( !intersectionGeom )
726  return false;
727 
728  bool simple = false;
729  int nIntersectGeoms = 1;
730  if ( GEOSGeomTypeId_r( geosinit()->ctxt, intersectionGeom.get() ) == GEOS_LINESTRING
731  || GEOSGeomTypeId_r( geosinit()->ctxt, intersectionGeom.get() ) == GEOS_POINT )
732  simple = true;
733 
734  if ( !simple )
735  nIntersectGeoms = GEOSGetNumGeometries_r( geosinit()->ctxt, intersectionGeom.get() );
736 
737  for ( int i = 0; i < nIntersectGeoms; ++i )
738  {
739  const GEOSGeometry *currentIntersectGeom = nullptr;
740  if ( simple )
741  currentIntersectGeom = intersectionGeom.get();
742  else
743  currentIntersectGeom = GEOSGetGeometryN_r( geosinit()->ctxt, intersectionGeom.get(), i );
744 
745  const GEOSCoordSequence *lineSequence = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, currentIntersectGeom );
746  unsigned int sequenceSize = 0;
747  double x, y;
748  if ( GEOSCoordSeq_getSize_r( geosinit()->ctxt, lineSequence, &sequenceSize ) != 0 )
749  {
750  for ( unsigned int i = 0; i < sequenceSize; ++i )
751  {
752  if ( GEOSCoordSeq_getX_r( geosinit()->ctxt, lineSequence, i, &x ) != 0 )
753  {
754  if ( GEOSCoordSeq_getY_r( geosinit()->ctxt, lineSequence, i, &y ) != 0 )
755  {
756  testPoints.push_back( QgsPoint( x, y ) );
757  }
758  }
759  }
760  }
761  }
762  }
763  CATCH_GEOS_WITH_ERRMSG( true )
764 
765  return true;
766 }
767 
768 geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) const
769 {
770  int type = GEOSGeomTypeId_r( geosinit()->ctxt, mGeos.get() );
771 
772  std::unique_ptr< QgsMultiCurve > multiCurve;
773  if ( type == GEOS_MULTILINESTRING )
774  {
775  multiCurve.reset( qgsgeometry_cast<QgsMultiCurve *>( mGeometry->clone() ) );
776  }
777  else if ( type == GEOS_LINESTRING )
778  {
779  multiCurve.reset( new QgsMultiCurve() );
780  multiCurve->addGeometry( mGeometry->clone() );
781  }
782  else
783  {
784  return nullptr;
785  }
786 
787  if ( !multiCurve )
788  {
789  return nullptr;
790  }
791 
792 
793  std::unique_ptr< QgsAbstractGeometry > splitGeom( fromGeos( GEOSsplitPoint ) );
794  QgsPoint *splitPoint = qgsgeometry_cast<QgsPoint *>( splitGeom.get() );
795  if ( !splitPoint )
796  {
797  return nullptr;
798  }
799 
800  QgsMultiCurve lines;
801 
802  //For each part
803  for ( int i = 0; i < multiCurve->numGeometries(); ++i )
804  {
805  const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( multiCurve->geometryN( i ) );
806  if ( line )
807  {
808  //For each segment
809  QgsLineString newLine;
810  newLine.addVertex( line->pointN( 0 ) );
811  int nVertices = line->numPoints();
812  for ( int j = 1; j < ( nVertices - 1 ); ++j )
813  {
814  QgsPoint currentPoint = line->pointN( j );
815  newLine.addVertex( currentPoint );
816  if ( currentPoint == *splitPoint )
817  {
818  lines.addGeometry( newLine.clone() );
819  newLine = QgsLineString();
820  newLine.addVertex( currentPoint );
821  }
822  }
823  newLine.addVertex( line->pointN( nVertices - 1 ) );
824  lines.addGeometry( newLine.clone() );
825  }
826  }
827 
828  return asGeos( &lines, mPrecision );
829 }
830 
831 QgsGeometryEngine::EngineOperationResult QgsGeos::splitLinearGeometry( GEOSGeometry *splitLine, QVector<QgsGeometry> &newGeometries, bool skipIntersectionCheck ) const
832 {
833  if ( !splitLine )
834  return InvalidInput;
835 
836  if ( !mGeos )
837  return InvalidBaseGeometry;
838 
839  //first test if linestring intersects geometry. If not, return straight away
840  if ( !skipIntersectionCheck && !GEOSIntersects_r( geosinit()->ctxt, splitLine, mGeos.get() ) )
841  return NothingHappened;
842 
843  //check that split line has no linear intersection
844  int linearIntersect = GEOSRelatePattern_r( geosinit()->ctxt, mGeos.get(), splitLine, "1********" );
845  if ( linearIntersect > 0 )
846  return InvalidInput;
847 
848  int splitGeomType = GEOSGeomTypeId_r( geosinit()->ctxt, splitLine );
849 
850  geos::unique_ptr splitGeom;
851  if ( splitGeomType == GEOS_POINT )
852  {
853  splitGeom = linePointDifference( splitLine );
854  }
855  else
856  {
857  splitGeom.reset( GEOSDifference_r( geosinit()->ctxt, mGeos.get(), splitLine ) );
858  }
859  QVector<GEOSGeometry *> lineGeoms;
860 
861  int splitType = GEOSGeomTypeId_r( geosinit()->ctxt, splitGeom.get() );
862  if ( splitType == GEOS_MULTILINESTRING )
863  {
864  int nGeoms = GEOSGetNumGeometries_r( geosinit()->ctxt, splitGeom.get() );
865  lineGeoms.reserve( nGeoms );
866  for ( int i = 0; i < nGeoms; ++i )
867  lineGeoms << GEOSGeom_clone_r( geosinit()->ctxt, GEOSGetGeometryN_r( geosinit()->ctxt, splitGeom.get(), i ) );
868 
869  }
870  else
871  {
872  lineGeoms << GEOSGeom_clone_r( geosinit()->ctxt, splitGeom.get() );
873  }
874 
875  mergeGeometriesMultiTypeSplit( lineGeoms );
876 
877  for ( int i = 0; i < lineGeoms.size(); ++i )
878  {
879  newGeometries << QgsGeometry( fromGeos( lineGeoms[i] ) );
880  GEOSGeom_destroy_r( geosinit()->ctxt, lineGeoms[i] );
881  }
882 
883  return Success;
884 }
885 
886 QgsGeometryEngine::EngineOperationResult QgsGeos::splitPolygonGeometry( GEOSGeometry *splitLine, QVector<QgsGeometry> &newGeometries, bool skipIntersectionCheck ) const
887 {
888  if ( !splitLine )
889  return InvalidInput;
890 
891  if ( !mGeos )
892  return InvalidBaseGeometry;
893 
894  // we will need prepared geometry for intersection tests
895  const_cast<QgsGeos *>( this )->prepareGeometry();
896  if ( !mGeosPrepared )
897  return EngineError;
898 
899  //first test if linestring intersects geometry. If not, return straight away
900  if ( !skipIntersectionCheck && !GEOSPreparedIntersects_r( geosinit()->ctxt, mGeosPrepared.get(), splitLine ) )
901  return NothingHappened;
902 
903  //first union all the polygon rings together (to get them noded, see JTS developer guide)
904  geos::unique_ptr nodedGeometry = nodeGeometries( splitLine, mGeos.get() );
905  if ( !nodedGeometry )
906  return NodedGeometryError; //an error occurred during noding
907 
908  const GEOSGeometry *noded = nodedGeometry.get();
909  geos::unique_ptr polygons( GEOSPolygonize_r( geosinit()->ctxt, &noded, 1 ) );
910  if ( !polygons || numberOfGeometries( polygons.get() ) == 0 )
911  {
912  return InvalidBaseGeometry;
913  }
914 
915  //test every polygon is contained in original geometry
916  //include in result if yes
917  QVector<GEOSGeometry *> testedGeometries;
918 
919  // test whether the polygon parts returned from polygonize algorithm actually
920  // belong to the source polygon geometry (if the source polygon contains some holes,
921  // those would be also returned by polygonize and we need to skip them)
922  for ( int i = 0; i < numberOfGeometries( polygons.get() ); i++ )
923  {
924  const GEOSGeometry *polygon = GEOSGetGeometryN_r( geosinit()->ctxt, polygons.get(), i );
925 
926  geos::unique_ptr pointOnSurface( GEOSPointOnSurface_r( geosinit()->ctxt, polygon ) );
927  if ( pointOnSurface && GEOSPreparedIntersects_r( geosinit()->ctxt, mGeosPrepared.get(), pointOnSurface.get() ) )
928  testedGeometries << GEOSGeom_clone_r( geosinit()->ctxt, polygon );
929  }
930 
931  int nGeometriesThis = numberOfGeometries( mGeos.get() ); //original number of geometries
932  if ( testedGeometries.empty() || testedGeometries.size() == nGeometriesThis )
933  {
934  //no split done, preserve original geometry
935  for ( int i = 0; i < testedGeometries.size(); ++i )
936  {
937  GEOSGeom_destroy_r( geosinit()->ctxt, testedGeometries[i] );
938  }
939  return NothingHappened;
940  }
941 
942  // For multi-part geometries, try to identify parts that have been unchanged and try to merge them back
943  // to a single multi-part geometry. For example, if there's a multi-polygon with three parts, but only
944  // one part is being split, this function makes sure that the other two parts will be kept in a multi-part
945  // geometry rather than being separated into two single-part geometries.
946  mergeGeometriesMultiTypeSplit( testedGeometries );
947 
948  int i;
949  for ( i = 0; i < testedGeometries.size() && GEOSisValid_r( geosinit()->ctxt, testedGeometries[i] ); ++i )
950  ;
951 
952  if ( i < testedGeometries.size() )
953  {
954  for ( i = 0; i < testedGeometries.size(); ++i )
955  GEOSGeom_destroy_r( geosinit()->ctxt, testedGeometries[i] );
956 
957  return InvalidBaseGeometry;
958  }
959 
960  for ( i = 0; i < testedGeometries.size(); ++i )
961  {
962  newGeometries << QgsGeometry( fromGeos( testedGeometries[i] ) );
963  GEOSGeom_destroy_r( geosinit()->ctxt, testedGeometries[i] );
964  }
965 
966  return Success;
967 }
968 
969 geos::unique_ptr QgsGeos::nodeGeometries( const GEOSGeometry *splitLine, const GEOSGeometry *geom )
970 {
971  if ( !splitLine || !geom )
972  return nullptr;
973 
974  geos::unique_ptr geometryBoundary;
975  if ( GEOSGeomTypeId_r( geosinit()->ctxt, geom ) == GEOS_POLYGON || GEOSGeomTypeId_r( geosinit()->ctxt, geom ) == GEOS_MULTIPOLYGON )
976  geometryBoundary.reset( GEOSBoundary_r( geosinit()->ctxt, geom ) );
977  else
978  geometryBoundary.reset( GEOSGeom_clone_r( geosinit()->ctxt, geom ) );
979 
980  geos::unique_ptr splitLineClone( GEOSGeom_clone_r( geosinit()->ctxt, splitLine ) );
981  geos::unique_ptr unionGeometry( GEOSUnion_r( geosinit()->ctxt, splitLineClone.get(), geometryBoundary.get() ) );
982 
983  return unionGeometry;
984 }
985 
986 int QgsGeos::mergeGeometriesMultiTypeSplit( QVector<GEOSGeometry *> &splitResult ) const
987 {
988  if ( !mGeos )
989  return 1;
990 
991  //convert mGeos to geometry collection
992  int type = GEOSGeomTypeId_r( geosinit()->ctxt, mGeos.get() );
993  if ( type != GEOS_GEOMETRYCOLLECTION &&
994  type != GEOS_MULTILINESTRING &&
995  type != GEOS_MULTIPOLYGON &&
996  type != GEOS_MULTIPOINT )
997  return 0;
998 
999  QVector<GEOSGeometry *> copyList = splitResult;
1000  splitResult.clear();
1001 
1002  //collect all the geometries that belong to the initial multifeature
1003  QVector<GEOSGeometry *> unionGeom;
1004 
1005  for ( int i = 0; i < copyList.size(); ++i )
1006  {
1007  //is this geometry a part of the original multitype?
1008  bool isPart = false;
1009  for ( int j = 0; j < GEOSGetNumGeometries_r( geosinit()->ctxt, mGeos.get() ); j++ )
1010  {
1011  if ( GEOSEquals_r( geosinit()->ctxt, copyList[i], GEOSGetGeometryN_r( geosinit()->ctxt, mGeos.get(), j ) ) )
1012  {
1013  isPart = true;
1014  break;
1015  }
1016  }
1017 
1018  if ( isPart )
1019  {
1020  unionGeom << copyList[i];
1021  }
1022  else
1023  {
1024  QVector<GEOSGeometry *> geomVector;
1025  geomVector << copyList[i];
1026 
1027  if ( type == GEOS_MULTILINESTRING )
1028  splitResult << createGeosCollection( GEOS_MULTILINESTRING, geomVector ).release();
1029  else if ( type == GEOS_MULTIPOLYGON )
1030  splitResult << createGeosCollection( GEOS_MULTIPOLYGON, geomVector ).release();
1031  else
1032  GEOSGeom_destroy_r( geosinit()->ctxt, copyList[i] );
1033  }
1034  }
1035 
1036  //make multifeature out of unionGeom
1037  if ( !unionGeom.isEmpty() )
1038  {
1039  if ( type == GEOS_MULTILINESTRING )
1040  splitResult << createGeosCollection( GEOS_MULTILINESTRING, unionGeom ).release();
1041  else if ( type == GEOS_MULTIPOLYGON )
1042  splitResult << createGeosCollection( GEOS_MULTIPOLYGON, unionGeom ).release();
1043  }
1044  else
1045  {
1046  unionGeom.clear();
1047  }
1048 
1049  return 0;
1050 }
1051 
1052 geos::unique_ptr QgsGeos::createGeosCollection( int typeId, const QVector<GEOSGeometry *> &geoms )
1053 {
1054  int nNullGeoms = geoms.count( nullptr );
1055  int nNotNullGeoms = geoms.size() - nNullGeoms;
1056 
1057  GEOSGeometry **geomarr = new GEOSGeometry*[ nNotNullGeoms ];
1058  if ( !geomarr )
1059  {
1060  return nullptr;
1061  }
1062 
1063  int i = 0;
1064  QVector<GEOSGeometry *>::const_iterator geomIt = geoms.constBegin();
1065  for ( ; geomIt != geoms.constEnd(); ++geomIt )
1066  {
1067  if ( *geomIt )
1068  {
1069  if ( GEOSisEmpty_r( geosinit()->ctxt, *geomIt ) )
1070  {
1071  // don't add empty parts to a geos collection, it can cause crashes in GEOS
1072  nNullGeoms++;
1073  nNotNullGeoms--;
1074  GEOSGeom_destroy_r( geosinit()->ctxt, *geomIt );
1075  }
1076  else
1077  {
1078  geomarr[i] = *geomIt;
1079  ++i;
1080  }
1081  }
1082  }
1083  geos::unique_ptr geom;
1084 
1085  try
1086  {
1087  geom.reset( GEOSGeom_createCollection_r( geosinit()->ctxt, typeId, geomarr, nNotNullGeoms ) );
1088  }
1089  catch ( GEOSException & )
1090  {
1091  }
1092 
1093  delete [] geomarr;
1094 
1095  return geom;
1096 }
1097 
1098 std::unique_ptr<QgsAbstractGeometry> QgsGeos::fromGeos( const GEOSGeometry *geos )
1099 {
1100  if ( !geos )
1101  {
1102  return nullptr;
1103  }
1104 
1105  int nCoordDims = GEOSGeom_getCoordinateDimension_r( geosinit()->ctxt, geos );
1106  int nDims = GEOSGeom_getDimensions_r( geosinit()->ctxt, geos );
1107  bool hasZ = ( nCoordDims == 3 );
1108  bool hasM = ( ( nDims - nCoordDims ) == 1 );
1109 
1110  switch ( GEOSGeomTypeId_r( geosinit()->ctxt, geos ) )
1111  {
1112  case GEOS_POINT: // a point
1113  {
1114  if ( GEOSisEmpty_r( geosinit()->ctxt, geos ) )
1115  return nullptr;
1116 
1117  const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, geos );
1118  unsigned int nPoints = 0;
1119  GEOSCoordSeq_getSize_r( geosinit()->ctxt, cs, &nPoints );
1120  return nPoints > 0 ? std::unique_ptr<QgsAbstractGeometry>( coordSeqPoint( cs, 0, hasZ, hasM ).clone() ) : qgis::make_unique< QgsPoint >();
1121  }
1122  case GEOS_LINESTRING:
1123  {
1124  return sequenceToLinestring( geos, hasZ, hasM );
1125  }
1126  case GEOS_POLYGON:
1127  {
1128  return fromGeosPolygon( geos );
1129  }
1130  case GEOS_MULTIPOINT:
1131  {
1132  std::unique_ptr< QgsMultiPoint > multiPoint( new QgsMultiPoint() );
1133  int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1134  multiPoint->reserve( nParts );
1135  for ( int i = 0; i < nParts; ++i )
1136  {
1137  const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ) );
1138  if ( cs )
1139  {
1140  unsigned int nPoints = 0;
1141  GEOSCoordSeq_getSize_r( geosinit()->ctxt, cs, &nPoints );
1142  if ( nPoints > 0 )
1143  multiPoint->addGeometry( coordSeqPoint( cs, 0, hasZ, hasM ).clone() );
1144  }
1145  }
1146  return std::move( multiPoint );
1147  }
1148  case GEOS_MULTILINESTRING:
1149  {
1150  std::unique_ptr< QgsMultiLineString > multiLineString( new QgsMultiLineString() );
1151  int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1152  multiLineString->reserve( nParts );
1153  for ( int i = 0; i < nParts; ++i )
1154  {
1155  std::unique_ptr< QgsLineString >line( sequenceToLinestring( GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ), hasZ, hasM ) );
1156  if ( line )
1157  {
1158  multiLineString->addGeometry( line.release() );
1159  }
1160  }
1161  return std::move( multiLineString );
1162  }
1163  case GEOS_MULTIPOLYGON:
1164  {
1165  std::unique_ptr< QgsMultiPolygon > multiPolygon( new QgsMultiPolygon() );
1166 
1167  int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1168  multiPolygon->reserve( nParts );
1169  for ( int i = 0; i < nParts; ++i )
1170  {
1171  std::unique_ptr< QgsPolygon > poly = fromGeosPolygon( GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ) );
1172  if ( poly )
1173  {
1174  multiPolygon->addGeometry( poly.release() );
1175  }
1176  }
1177  return std::move( multiPolygon );
1178  }
1179  case GEOS_GEOMETRYCOLLECTION:
1180  {
1181  std::unique_ptr< QgsGeometryCollection > geomCollection( new QgsGeometryCollection() );
1182  int nParts = GEOSGetNumGeometries_r( geosinit()->ctxt, geos );
1183  geomCollection->reserve( nParts );
1184  for ( int i = 0; i < nParts; ++i )
1185  {
1186  std::unique_ptr< QgsAbstractGeometry > geom( fromGeos( GEOSGetGeometryN_r( geosinit()->ctxt, geos, i ) ) );
1187  if ( geom )
1188  {
1189  geomCollection->addGeometry( geom.release() );
1190  }
1191  }
1192  return std::move( geomCollection );
1193  }
1194  }
1195  return nullptr;
1196 }
1197 
1198 std::unique_ptr<QgsPolygon> QgsGeos::fromGeosPolygon( const GEOSGeometry *geos )
1199 {
1200  if ( GEOSGeomTypeId_r( geosinit()->ctxt, geos ) != GEOS_POLYGON )
1201  {
1202  return nullptr;
1203  }
1204 
1205  int nCoordDims = GEOSGeom_getCoordinateDimension_r( geosinit()->ctxt, geos );
1206  int nDims = GEOSGeom_getDimensions_r( geosinit()->ctxt, geos );
1207  bool hasZ = ( nCoordDims == 3 );
1208  bool hasM = ( ( nDims - nCoordDims ) == 1 );
1209 
1210  std::unique_ptr< QgsPolygon > polygon( new QgsPolygon() );
1211 
1212  const GEOSGeometry *ring = GEOSGetExteriorRing_r( geosinit()->ctxt, geos );
1213  if ( ring )
1214  {
1215  polygon->setExteriorRing( sequenceToLinestring( ring, hasZ, hasM ).release() );
1216  }
1217 
1218  QVector<QgsCurve *> interiorRings;
1219  const int ringCount = GEOSGetNumInteriorRings_r( geosinit()->ctxt, geos );
1220  interiorRings.reserve( ringCount );
1221  for ( int i = 0; i < ringCount; ++i )
1222  {
1223  ring = GEOSGetInteriorRingN_r( geosinit()->ctxt, geos, i );
1224  if ( ring )
1225  {
1226  interiorRings.push_back( sequenceToLinestring( ring, hasZ, hasM ).release() );
1227  }
1228  }
1229  polygon->setInteriorRings( interiorRings );
1230 
1231  return polygon;
1232 }
1233 
1234 std::unique_ptr<QgsLineString> QgsGeos::sequenceToLinestring( const GEOSGeometry *geos, bool hasZ, bool hasM )
1235 {
1236  const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, geos );
1237  unsigned int nPoints;
1238  GEOSCoordSeq_getSize_r( geosinit()->ctxt, cs, &nPoints );
1239  QVector< double > xOut( nPoints );
1240  QVector< double > yOut( nPoints );
1241  QVector< double > zOut;
1242  if ( hasZ )
1243  zOut.resize( nPoints );
1244  QVector< double > mOut;
1245  if ( hasM )
1246  mOut.resize( nPoints );
1247  double *x = xOut.data();
1248  double *y = yOut.data();
1249  double *z = zOut.data();
1250  double *m = mOut.data();
1251  for ( unsigned int i = 0; i < nPoints; ++i )
1252  {
1253 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
1254  if ( hasZ )
1255  GEOSCoordSeq_getXYZ_r( geosinit()->ctxt, cs, i, x++, y++, z++ );
1256  else
1257  GEOSCoordSeq_getXY_r( geosinit()->ctxt, cs, i, x++, y++ );
1258 #else
1259  GEOSCoordSeq_getX_r( geosinit()->ctxt, cs, i, x++ );
1260  GEOSCoordSeq_getY_r( geosinit()->ctxt, cs, i, y++ );
1261  if ( hasZ )
1262  {
1263  GEOSCoordSeq_getZ_r( geosinit()->ctxt, cs, i, z++ );
1264  }
1265 #endif
1266  if ( hasM )
1267  {
1268  GEOSCoordSeq_getOrdinate_r( geosinit()->ctxt, cs, i, 3, m++ );
1269  }
1270  }
1271  std::unique_ptr< QgsLineString > line( new QgsLineString( xOut, yOut, zOut, mOut ) );
1272  return line;
1273 }
1274 
1275 int QgsGeos::numberOfGeometries( GEOSGeometry *g )
1276 {
1277  if ( !g )
1278  return 0;
1279 
1280  int geometryType = GEOSGeomTypeId_r( geosinit()->ctxt, g );
1281  if ( geometryType == GEOS_POINT || geometryType == GEOS_LINESTRING || geometryType == GEOS_LINEARRING
1282  || geometryType == GEOS_POLYGON )
1283  return 1;
1284 
1285  //calling GEOSGetNumGeometries is save for multi types and collections also in geos2
1286  return GEOSGetNumGeometries_r( geosinit()->ctxt, g );
1287 }
1288 
1289 QgsPoint QgsGeos::coordSeqPoint( const GEOSCoordSequence *cs, int i, bool hasZ, bool hasM )
1290 {
1291  if ( !cs )
1292  {
1293  return QgsPoint();
1294  }
1295 
1296  double x, y;
1297  double z = 0;
1298  double m = 0;
1299 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
1300  if ( hasZ )
1301  GEOSCoordSeq_getXYZ_r( geosinit()->ctxt, cs, i, &x, &y, &z );
1302  else
1303  GEOSCoordSeq_getXY_r( geosinit()->ctxt, cs, i, &x, &y );
1304 #else
1305  GEOSCoordSeq_getX_r( geosinit()->ctxt, cs, i, &x );
1306  GEOSCoordSeq_getY_r( geosinit()->ctxt, cs, i, &y );
1307  if ( hasZ )
1308  {
1309  GEOSCoordSeq_getZ_r( geosinit()->ctxt, cs, i, &z );
1310  }
1311 #endif
1312  if ( hasM )
1313  {
1314  GEOSCoordSeq_getOrdinate_r( geosinit()->ctxt, cs, i, 3, &m );
1315  }
1316 
1318  if ( hasZ && hasM )
1319  {
1321  }
1322  else if ( hasZ )
1323  {
1324  t = QgsWkbTypes::PointZ;
1325  }
1326  else if ( hasM )
1327  {
1328  t = QgsWkbTypes::PointM;
1329  }
1330  return QgsPoint( t, x, y, z, m );
1331 }
1332 
1334 {
1335  if ( !geom )
1336  return nullptr;
1337 
1338  int coordDims = 2;
1339  if ( geom->is3D() )
1340  {
1341  ++coordDims;
1342  }
1343  if ( geom->isMeasure() )
1344  {
1345  ++coordDims;
1346  }
1347 
1349  {
1350  int geosType = GEOS_GEOMETRYCOLLECTION;
1351 
1353  {
1354  switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
1355  {
1357  geosType = GEOS_MULTIPOINT;
1358  break;
1359 
1361  geosType = GEOS_MULTILINESTRING;
1362  break;
1363 
1365  geosType = GEOS_MULTIPOLYGON;
1366  break;
1367 
1370  return nullptr;
1371  }
1372  }
1373 
1374 
1375  const QgsGeometryCollection *c = qgsgeometry_cast<const QgsGeometryCollection *>( geom );
1376 
1377  if ( !c )
1378  return nullptr;
1379 
1380  QVector< GEOSGeometry * > geomVector( c->numGeometries() );
1381  for ( int i = 0; i < c->numGeometries(); ++i )
1382  {
1383  geomVector[i] = asGeos( c->geometryN( i ), precision ).release();
1384  }
1385  return createGeosCollection( geosType, geomVector );
1386  }
1387  else
1388  {
1389  switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
1390  {
1392  return createGeosPoint( static_cast<const QgsPoint *>( geom ), coordDims, precision );
1393 
1395  return createGeosLinestring( static_cast<const QgsLineString *>( geom ), precision );
1396 
1398  return createGeosPolygon( static_cast<const QgsPolygon *>( geom ), precision );
1399 
1402  return nullptr;
1403  }
1404  }
1405  return nullptr;
1406 }
1407 
1408 std::unique_ptr<QgsAbstractGeometry> QgsGeos::overlay( const QgsAbstractGeometry *geom, Overlay op, QString *errorMsg ) const
1409 {
1410  if ( !mGeos || !geom )
1411  {
1412  return nullptr;
1413  }
1414 
1415  geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
1416  if ( !geosGeom )
1417  {
1418  return nullptr;
1419  }
1420 
1421  try
1422  {
1423  geos::unique_ptr opGeom;
1424  switch ( op )
1425  {
1426  case OverlayIntersection:
1427  opGeom.reset( GEOSIntersection_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1428  break;
1429 
1430  case OverlayDifference:
1431  opGeom.reset( GEOSDifference_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1432  break;
1433 
1434  case OverlayUnion:
1435  {
1436  geos::unique_ptr unionGeometry( GEOSUnion_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1437 
1438  if ( unionGeometry && GEOSGeomTypeId_r( geosinit()->ctxt, unionGeometry.get() ) == GEOS_MULTILINESTRING )
1439  {
1440  geos::unique_ptr mergedLines( GEOSLineMerge_r( geosinit()->ctxt, unionGeometry.get() ) );
1441  if ( mergedLines )
1442  {
1443  unionGeometry = std::move( mergedLines );
1444  }
1445  }
1446 
1447  opGeom = std::move( unionGeometry );
1448  }
1449  break;
1450 
1451  case OverlaySymDifference:
1452  opGeom.reset( GEOSSymDifference_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) );
1453  break;
1454  }
1455  return fromGeos( opGeom.get() );
1456  }
1457  catch ( GEOSException &e )
1458  {
1459  logError( QStringLiteral( "GEOS" ), e.what() );
1460  if ( errorMsg )
1461  {
1462  *errorMsg = e.what();
1463  }
1464  return nullptr;
1465  }
1466 }
1467 
1468 bool QgsGeos::relation( const QgsAbstractGeometry *geom, Relation r, QString *errorMsg ) const
1469 {
1470  if ( !mGeos || !geom )
1471  {
1472  return false;
1473  }
1474 
1475  geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
1476  if ( !geosGeom )
1477  {
1478  return false;
1479  }
1480 
1481  bool result = false;
1482  try
1483  {
1484  if ( mGeosPrepared ) //use faster version with prepared geometry
1485  {
1486  switch ( r )
1487  {
1488  case RelationIntersects:
1489  result = ( GEOSPreparedIntersects_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1490  break;
1491  case RelationTouches:
1492  result = ( GEOSPreparedTouches_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1493  break;
1494  case RelationCrosses:
1495  result = ( GEOSPreparedCrosses_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1496  break;
1497  case RelationWithin:
1498  result = ( GEOSPreparedWithin_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1499  break;
1500  case RelationContains:
1501  result = ( GEOSPreparedContains_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1502  break;
1503  case RelationDisjoint:
1504  result = ( GEOSPreparedDisjoint_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1505  break;
1506  case RelationOverlaps:
1507  result = ( GEOSPreparedOverlaps_r( geosinit()->ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
1508  break;
1509  }
1510  return result;
1511  }
1512 
1513  switch ( r )
1514  {
1515  case RelationIntersects:
1516  result = ( GEOSIntersects_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1517  break;
1518  case RelationTouches:
1519  result = ( GEOSTouches_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1520  break;
1521  case RelationCrosses:
1522  result = ( GEOSCrosses_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1523  break;
1524  case RelationWithin:
1525  result = ( GEOSWithin_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1526  break;
1527  case RelationContains:
1528  result = ( GEOSContains_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1529  break;
1530  case RelationDisjoint:
1531  result = ( GEOSDisjoint_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1532  break;
1533  case RelationOverlaps:
1534  result = ( GEOSOverlaps_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() ) == 1 );
1535  break;
1536  }
1537  }
1538  catch ( GEOSException &e )
1539  {
1540  logError( QStringLiteral( "GEOS" ), e.what() );
1541  if ( errorMsg )
1542  {
1543  *errorMsg = e.what();
1544  }
1545  return false;
1546  }
1547 
1548  return result;
1549 }
1550 
1551 QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, QString *errorMsg ) const
1552 {
1553  if ( !mGeos )
1554  {
1555  return nullptr;
1556  }
1557 
1559  try
1560  {
1561  geos.reset( GEOSBuffer_r( geosinit()->ctxt, mGeos.get(), distance, segments ) );
1562  }
1563  CATCH_GEOS_WITH_ERRMSG( nullptr )
1564  return fromGeos( geos.get() ).release();
1565 }
1566 
1567 QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, int endCapStyle, int joinStyle, double miterLimit, QString *errorMsg ) const
1568 {
1569  if ( !mGeos )
1570  {
1571  return nullptr;
1572  }
1573 
1575  try
1576  {
1577  geos.reset( GEOSBufferWithStyle_r( geosinit()->ctxt, mGeos.get(), distance, segments, endCapStyle, joinStyle, miterLimit ) );
1578  }
1579  CATCH_GEOS_WITH_ERRMSG( nullptr )
1580  return fromGeos( geos.get() ).release();
1581 }
1582 
1583 QgsAbstractGeometry *QgsGeos::simplify( double tolerance, QString *errorMsg ) const
1584 {
1585  if ( !mGeos )
1586  {
1587  return nullptr;
1588  }
1590  try
1591  {
1592  geos.reset( GEOSTopologyPreserveSimplify_r( geosinit()->ctxt, mGeos.get(), tolerance ) );
1593  }
1594  CATCH_GEOS_WITH_ERRMSG( nullptr )
1595  return fromGeos( geos.get() ).release();
1596 }
1597 
1598 QgsAbstractGeometry *QgsGeos::interpolate( double distance, QString *errorMsg ) const
1599 {
1600  if ( !mGeos )
1601  {
1602  return nullptr;
1603  }
1605  try
1606  {
1607  geos.reset( GEOSInterpolate_r( geosinit()->ctxt, mGeos.get(), distance ) );
1608  }
1609  CATCH_GEOS_WITH_ERRMSG( nullptr )
1610  return fromGeos( geos.get() ).release();
1611 }
1612 
1613 QgsPoint *QgsGeos::centroid( QString *errorMsg ) const
1614 {
1615  if ( !mGeos )
1616  {
1617  return nullptr;
1618  }
1619 
1621  double x;
1622  double y;
1623 
1624  try
1625  {
1626  geos.reset( GEOSGetCentroid_r( geosinit()->ctxt, mGeos.get() ) );
1627 
1628  if ( !geos )
1629  return nullptr;
1630 
1631  GEOSGeomGetX_r( geosinit()->ctxt, geos.get(), &x );
1632  GEOSGeomGetY_r( geosinit()->ctxt, geos.get(), &y );
1633  }
1634  CATCH_GEOS_WITH_ERRMSG( nullptr )
1635 
1636  return new QgsPoint( x, y );
1637 }
1638 
1639 QgsAbstractGeometry *QgsGeos::envelope( QString *errorMsg ) const
1640 {
1641  if ( !mGeos )
1642  {
1643  return nullptr;
1644  }
1646  try
1647  {
1648  geos.reset( GEOSEnvelope_r( geosinit()->ctxt, mGeos.get() ) );
1649  }
1650  CATCH_GEOS_WITH_ERRMSG( nullptr )
1651  return fromGeos( geos.get() ).release();
1652 }
1653 
1654 QgsPoint *QgsGeos::pointOnSurface( QString *errorMsg ) const
1655 {
1656  if ( !mGeos )
1657  {
1658  return nullptr;
1659  }
1660 
1661  double x;
1662  double y;
1663 
1665  try
1666  {
1667  geos.reset( GEOSPointOnSurface_r( geosinit()->ctxt, mGeos.get() ) );
1668 
1669  if ( !geos || GEOSisEmpty_r( geosinit()->ctxt, geos.get() ) != 0 )
1670  {
1671  return nullptr;
1672  }
1673 
1674  GEOSGeomGetX_r( geosinit()->ctxt, geos.get(), &x );
1675  GEOSGeomGetY_r( geosinit()->ctxt, geos.get(), &y );
1676  }
1677  CATCH_GEOS_WITH_ERRMSG( nullptr )
1678 
1679  return new QgsPoint( x, y );
1680 }
1681 
1682 QgsAbstractGeometry *QgsGeos::convexHull( QString *errorMsg ) const
1683 {
1684  if ( !mGeos )
1685  {
1686  return nullptr;
1687  }
1688 
1689  try
1690  {
1691  geos::unique_ptr cHull( GEOSConvexHull_r( geosinit()->ctxt, mGeos.get() ) );
1692  std::unique_ptr< QgsAbstractGeometry > cHullGeom = fromGeos( cHull.get() );
1693  return cHullGeom.release();
1694  }
1695  CATCH_GEOS_WITH_ERRMSG( nullptr )
1696 }
1697 
1698 bool QgsGeos::isValid( QString *errorMsg, const bool allowSelfTouchingHoles, QgsGeometry *errorLoc ) const
1699 {
1700  if ( !mGeos )
1701  {
1702  return false;
1703  }
1704 
1705  try
1706  {
1707  GEOSGeometry *g1 = nullptr;
1708  char *r = nullptr;
1709  char res = GEOSisValidDetail_r( geosinit()->ctxt, mGeos.get(), allowSelfTouchingHoles ? GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE : 0, &r, &g1 );
1710  const bool invalid = res != 1;
1711 
1712  QString error;
1713  if ( r )
1714  {
1715  error = QString( r );
1716  GEOSFree_r( geosinit()->ctxt, r );
1717  }
1718 
1719  if ( invalid && errorMsg )
1720  {
1721  static QgsStringMap translatedErrors;
1722 
1723  if ( translatedErrors.empty() )
1724  {
1725  // Copied from https://git.osgeo.org/gitea/geos/geos/src/branch/master/src/operation/valid/TopologyValidationError.cpp
1726  translatedErrors.insert( QStringLiteral( "topology validation error" ), QObject::tr( "Topology validation error", "GEOS Error" ) );
1727  translatedErrors.insert( QStringLiteral( "repeated point" ), QObject::tr( "Repeated point", "GEOS Error" ) );
1728  translatedErrors.insert( QStringLiteral( "hole lies outside shell" ), QObject::tr( "Hole lies outside shell", "GEOS Error" ) );
1729  translatedErrors.insert( QStringLiteral( "holes are nested" ), QObject::tr( "Holes are nested", "GEOS Error" ) );
1730  translatedErrors.insert( QStringLiteral( "interior is disconnected" ), QObject::tr( "Interior is disconnected", "GEOS Error" ) );
1731  translatedErrors.insert( QStringLiteral( "self-intersection" ), QObject::tr( "Self-intersection", "GEOS Error" ) );
1732  translatedErrors.insert( QStringLiteral( "ring self-intersection" ), QObject::tr( "Ring self-intersection", "GEOS Error" ) );
1733  translatedErrors.insert( QStringLiteral( "nested shells" ), QObject::tr( "Nested shells", "GEOS Error" ) );
1734  translatedErrors.insert( QStringLiteral( "duplicate rings" ), QObject::tr( "Duplicate rings", "GEOS Error" ) );
1735  translatedErrors.insert( QStringLiteral( "too few points in geometry component" ), QObject::tr( "Too few points in geometry component", "GEOS Error" ) );
1736  translatedErrors.insert( QStringLiteral( "invalid coordinate" ), QObject::tr( "Invalid coordinate", "GEOS Error" ) );
1737  translatedErrors.insert( QStringLiteral( "ring is not closed" ), QObject::tr( "Ring is not closed", "GEOS Error" ) );
1738  }
1739 
1740  *errorMsg = translatedErrors.value( error.toLower(), error );
1741 
1742  if ( g1 && errorLoc )
1743  {
1744  *errorLoc = geometryFromGeos( g1 );
1745  }
1746  else if ( g1 )
1747  {
1748  GEOSGeom_destroy_r( geosinit()->ctxt, g1 );
1749  }
1750  }
1751  return !invalid;
1752  }
1753  CATCH_GEOS_WITH_ERRMSG( false )
1754 }
1755 
1756 bool QgsGeos::isEqual( const QgsAbstractGeometry *geom, QString *errorMsg ) const
1757 {
1758  if ( !mGeos || !geom )
1759  {
1760  return false;
1761  }
1762 
1763  try
1764  {
1765  geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
1766  if ( !geosGeom )
1767  {
1768  return false;
1769  }
1770  bool equal = GEOSEquals_r( geosinit()->ctxt, mGeos.get(), geosGeom.get() );
1771  return equal;
1772  }
1773  CATCH_GEOS_WITH_ERRMSG( false )
1774 }
1775 
1776 bool QgsGeos::isEmpty( QString *errorMsg ) const
1777 {
1778  if ( !mGeos )
1779  {
1780  return false;
1781  }
1782 
1783  try
1784  {
1785  return GEOSisEmpty_r( geosinit()->ctxt, mGeos.get() );
1786  }
1787  CATCH_GEOS_WITH_ERRMSG( false )
1788 }
1789 
1790 bool QgsGeos::isSimple( QString *errorMsg ) const
1791 {
1792  if ( !mGeos )
1793  {
1794  return false;
1795  }
1796 
1797  try
1798  {
1799  return GEOSisSimple_r( geosinit()->ctxt, mGeos.get() );
1800  }
1801  CATCH_GEOS_WITH_ERRMSG( false )
1802 }
1803 
1804 GEOSCoordSequence *QgsGeos::createCoordinateSequence( const QgsCurve *curve, double precision, bool forceClose )
1805 {
1806  std::unique_ptr< QgsLineString > segmentized;
1807  const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( curve );
1808 
1809  if ( !line )
1810  {
1811  segmentized.reset( curve->curveToLine() );
1812  line = segmentized.get();
1813  }
1814 
1815  if ( !line )
1816  {
1817  return nullptr;
1818  }
1819 
1820  bool hasZ = line->is3D();
1821  bool hasM = false; //line->isMeasure(); //disabled until geos supports m-coordinates
1822  int coordDims = 2;
1823  if ( hasZ )
1824  {
1825  ++coordDims;
1826  }
1827  if ( hasM )
1828  {
1829  ++coordDims;
1830  }
1831 
1832  int numPoints = line->numPoints();
1833 
1834  int numOutPoints = numPoints;
1835  if ( forceClose && ( line->pointN( 0 ) != line->pointN( numPoints - 1 ) ) )
1836  {
1837  ++numOutPoints;
1838  }
1839 
1840  GEOSCoordSequence *coordSeq = nullptr;
1841  try
1842  {
1843  coordSeq = GEOSCoordSeq_create_r( geosinit()->ctxt, numOutPoints, coordDims );
1844  if ( !coordSeq )
1845  {
1846  QgsDebugMsg( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for %1 points in %2 dimensions" ).arg( numPoints ).arg( coordDims ) );
1847  return nullptr;
1848  }
1849 
1850  const double *xData = line->xData();
1851  const double *yData = line->yData();
1852  const double *zData = hasZ ? line->zData() : nullptr;
1853  const double *mData = hasM ? line->mData() : nullptr;
1854 
1855  if ( precision > 0. )
1856  {
1857  for ( int i = 0; i < numOutPoints; ++i )
1858  {
1859  if ( i >= numPoints )
1860  {
1861  // start reading back from start of line
1862  xData = line->xData();
1863  yData = line->yData();
1864  zData = hasZ ? line->zData() : nullptr;
1865  mData = hasM ? line->mData() : nullptr;
1866  }
1867 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
1868  if ( hasZ )
1869  {
1870  GEOSCoordSeq_setXYZ_r( geosinit()->ctxt, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision, std::round( *zData++ / precision ) * precision );
1871  }
1872  else
1873  {
1874  GEOSCoordSeq_setXY_r( geosinit()->ctxt, coordSeq, i, std::round( *xData++ / precision ) * precision, std::round( *yData++ / precision ) * precision );
1875  }
1876 #else
1877  GEOSCoordSeq_setX_r( geosinit()->ctxt, coordSeq, i, std::round( *xData++ / precision ) * precision );
1878  GEOSCoordSeq_setY_r( geosinit()->ctxt, coordSeq, i, std::round( *yData++ / precision ) * precision );
1879  if ( hasZ )
1880  {
1881  GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, i, 2, std::round( *zData++ / precision ) * precision );
1882  }
1883 #endif
1884  if ( hasM )
1885  {
1886  GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, i, 3, line->mAt( *mData++ ) );
1887  }
1888  }
1889  }
1890  else
1891  {
1892  for ( int i = 0; i < numOutPoints; ++i )
1893  {
1894  if ( i >= numPoints )
1895  {
1896  // start reading back from start of line
1897  xData = line->xData();
1898  yData = line->yData();
1899  zData = hasZ ? line->zData() : nullptr;
1900  mData = hasM ? line->mData() : nullptr;
1901  }
1902 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
1903  if ( hasZ )
1904  {
1905  GEOSCoordSeq_setXYZ_r( geosinit()->ctxt, coordSeq, i, *xData++, *yData++, *zData++ );
1906  }
1907  else
1908  {
1909  GEOSCoordSeq_setXY_r( geosinit()->ctxt, coordSeq, i, *xData++, *yData++ );
1910  }
1911 #else
1912  GEOSCoordSeq_setX_r( geosinit()->ctxt, coordSeq, i, *xData++ );
1913  GEOSCoordSeq_setY_r( geosinit()->ctxt, coordSeq, i, *yData++ );
1914  if ( hasZ )
1915  {
1916  GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, i, 2, *zData++ );
1917  }
1918 #endif
1919  if ( hasM )
1920  {
1921  GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, i, 3, *mData++ );
1922  }
1923  }
1924  }
1925  }
1926  CATCH_GEOS( nullptr )
1927 
1928  return coordSeq;
1929 }
1930 
1931 geos::unique_ptr QgsGeos::createGeosPoint( const QgsAbstractGeometry *point, int coordDims, double precision )
1932 {
1933  const QgsPoint *pt = qgsgeometry_cast<const QgsPoint *>( point );
1934  if ( !pt )
1935  return nullptr;
1936 
1937  return createGeosPointXY( pt->x(), pt->y(), pt->is3D(), pt->z(), pt->isMeasure(), pt->m(), coordDims, precision );
1938 }
1939 
1940 geos::unique_ptr QgsGeos::createGeosPointXY( double x, double y, bool hasZ, double z, bool hasM, double m, int coordDims, double precision )
1941 {
1942  Q_UNUSED( hasM )
1943  Q_UNUSED( m )
1944 
1945  geos::unique_ptr geosPoint;
1946  try
1947  {
1948 #if GEOS_VERSION_MAJOR>3 || GEOS_VERSION_MINOR>=8
1949  if ( coordDims == 2 )
1950  {
1951  // optimised constructor
1952  if ( precision > 0. )
1953  geosPoint.reset( GEOSGeom_createPointFromXY_r( geosinit()->ctxt, std::round( x / precision ) * precision, std::round( y / precision ) * precision ) );
1954  else
1955  geosPoint.reset( GEOSGeom_createPointFromXY_r( geosinit()->ctxt, x, y ) );
1956  return geosPoint;
1957  }
1958 #endif
1959 
1960  GEOSCoordSequence *coordSeq = GEOSCoordSeq_create_r( geosinit()->ctxt, 1, coordDims );
1961  if ( !coordSeq )
1962  {
1963  QgsDebugMsg( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for point with %1 dimensions" ).arg( coordDims ) );
1964  return nullptr;
1965  }
1966  if ( precision > 0. )
1967  {
1968  GEOSCoordSeq_setX_r( geosinit()->ctxt, coordSeq, 0, std::round( x / precision ) * precision );
1969  GEOSCoordSeq_setY_r( geosinit()->ctxt, coordSeq, 0, std::round( y / precision ) * precision );
1970  if ( hasZ )
1971  {
1972  GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, 0, 2, std::round( z / precision ) * precision );
1973  }
1974  }
1975  else
1976  {
1977  GEOSCoordSeq_setX_r( geosinit()->ctxt, coordSeq, 0, x );
1978  GEOSCoordSeq_setY_r( geosinit()->ctxt, coordSeq, 0, y );
1979  if ( hasZ )
1980  {
1981  GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, 0, 2, z );
1982  }
1983  }
1984 #if 0 //disabled until geos supports m-coordinates
1985  if ( hasM )
1986  {
1987  GEOSCoordSeq_setOrdinate_r( geosinit()->ctxt, coordSeq, 0, 3, m );
1988  }
1989 #endif
1990  geosPoint.reset( GEOSGeom_createPoint_r( geosinit()->ctxt, coordSeq ) );
1991  }
1992  CATCH_GEOS( nullptr )
1993  return geosPoint;
1994 }
1995 
1996 geos::unique_ptr QgsGeos::createGeosLinestring( const QgsAbstractGeometry *curve, double precision )
1997 {
1998  const QgsCurve *c = qgsgeometry_cast<const QgsCurve *>( curve );
1999  if ( !c )
2000  return nullptr;
2001 
2002  GEOSCoordSequence *coordSeq = createCoordinateSequence( c, precision );
2003  if ( !coordSeq )
2004  return nullptr;
2005 
2006  geos::unique_ptr geosGeom;
2007  try
2008  {
2009  geosGeom.reset( GEOSGeom_createLineString_r( geosinit()->ctxt, coordSeq ) );
2010  }
2011  CATCH_GEOS( nullptr )
2012  return geosGeom;
2013 }
2014 
2015 geos::unique_ptr QgsGeos::createGeosPolygon( const QgsAbstractGeometry *poly, double precision )
2016 {
2017  const QgsCurvePolygon *polygon = qgsgeometry_cast<const QgsCurvePolygon *>( poly );
2018  if ( !polygon )
2019  return nullptr;
2020 
2021  const QgsCurve *exteriorRing = polygon->exteriorRing();
2022  if ( !exteriorRing )
2023  {
2024  return nullptr;
2025  }
2026 
2027  geos::unique_ptr geosPolygon;
2028  try
2029  {
2030  geos::unique_ptr exteriorRingGeos( GEOSGeom_createLinearRing_r( geosinit()->ctxt, createCoordinateSequence( exteriorRing, precision, true ) ) );
2031 
2032  int nHoles = polygon->numInteriorRings();
2033  GEOSGeometry **holes = nullptr;
2034  if ( nHoles > 0 )
2035  {
2036  holes = new GEOSGeometry*[ nHoles ];
2037  }
2038 
2039  for ( int i = 0; i < nHoles; ++i )
2040  {
2041  const QgsCurve *interiorRing = polygon->interiorRing( i );
2042  holes[i] = GEOSGeom_createLinearRing_r( geosinit()->ctxt, createCoordinateSequence( interiorRing, precision, true ) );
2043  }
2044  geosPolygon.reset( GEOSGeom_createPolygon_r( geosinit()->ctxt, exteriorRingGeos.release(), holes, nHoles ) );
2045  delete[] holes;
2046  }
2047  CATCH_GEOS( nullptr )
2048 
2049  return geosPolygon;
2050 }
2051 
2052 QgsAbstractGeometry *QgsGeos::offsetCurve( double distance, int segments, int joinStyle, double miterLimit, QString *errorMsg ) const
2053 {
2054  if ( !mGeos )
2055  return nullptr;
2056 
2057  geos::unique_ptr offset;
2058  try
2059  {
2060  offset.reset( GEOSOffsetCurve_r( geosinit()->ctxt, mGeos.get(), distance, segments, joinStyle, miterLimit ) );
2061  }
2062  CATCH_GEOS_WITH_ERRMSG( nullptr )
2063  std::unique_ptr< QgsAbstractGeometry > offsetGeom = fromGeos( offset.get() );
2064  return offsetGeom.release();
2065 }
2066 
2067 std::unique_ptr<QgsAbstractGeometry> QgsGeos::singleSidedBuffer( double distance, int segments, int side, int joinStyle, double miterLimit, QString *errorMsg ) const
2068 {
2069  if ( !mGeos )
2070  {
2071  return nullptr;
2072  }
2073 
2075  try
2076  {
2077  geos::buffer_params_unique_ptr bp( GEOSBufferParams_create_r( geosinit()->ctxt ) );
2078  GEOSBufferParams_setSingleSided_r( geosinit()->ctxt, bp.get(), 1 );
2079  GEOSBufferParams_setQuadrantSegments_r( geosinit()->ctxt, bp.get(), segments );
2080  GEOSBufferParams_setJoinStyle_r( geosinit()->ctxt, bp.get(), joinStyle );
2081  GEOSBufferParams_setMitreLimit_r( geosinit()->ctxt, bp.get(), miterLimit ); //#spellok
2082 
2083  if ( side == 1 )
2084  {
2085  distance = -distance;
2086  }
2087  geos.reset( GEOSBufferWithParams_r( geosinit()->ctxt, mGeos.get(), bp.get(), distance ) );
2088  }
2089  CATCH_GEOS_WITH_ERRMSG( nullptr )
2090  return fromGeos( geos.get() );
2091 }
2092 
2093 std::unique_ptr<QgsAbstractGeometry> QgsGeos::reshapeGeometry( const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg ) const
2094 {
2095  if ( !mGeos || mGeometry->dimension() == 0 )
2096  {
2097  if ( errorCode ) { *errorCode = InvalidBaseGeometry; }
2098  return nullptr;
2099  }
2100 
2101  if ( reshapeWithLine.numPoints() < 2 )
2102  {
2103  if ( errorCode ) { *errorCode = InvalidInput; }
2104  return nullptr;
2105  }
2106 
2107  geos::unique_ptr reshapeLineGeos = createGeosLinestring( &reshapeWithLine, mPrecision );
2108 
2109  //single or multi?
2110  int numGeoms = GEOSGetNumGeometries_r( geosinit()->ctxt, mGeos.get() );
2111  if ( numGeoms == -1 )
2112  {
2113  if ( errorCode )
2114  {
2115  *errorCode = InvalidBaseGeometry;
2116  }
2117  return nullptr;
2118  }
2119 
2120  bool isMultiGeom = false;
2121  int geosTypeId = GEOSGeomTypeId_r( geosinit()->ctxt, mGeos.get() );
2122  if ( geosTypeId == GEOS_MULTILINESTRING || geosTypeId == GEOS_MULTIPOLYGON )
2123  isMultiGeom = true;
2124 
2125  bool isLine = ( mGeometry->dimension() == 1 );
2126 
2127  if ( !isMultiGeom )
2128  {
2129  geos::unique_ptr reshapedGeometry;
2130  if ( isLine )
2131  {
2132  reshapedGeometry = reshapeLine( mGeos.get(), reshapeLineGeos.get(), mPrecision );
2133  }
2134  else
2135  {
2136  reshapedGeometry = reshapePolygon( mGeos.get(), reshapeLineGeos.get(), mPrecision );
2137  }
2138 
2139  if ( errorCode )
2140  *errorCode = Success;
2141  std::unique_ptr< QgsAbstractGeometry > reshapeResult = fromGeos( reshapedGeometry.get() );
2142  return reshapeResult;
2143  }
2144  else
2145  {
2146  try
2147  {
2148  //call reshape for each geometry part and replace mGeos with new geometry if reshape took place
2149  bool reshapeTookPlace = false;
2150 
2151  geos::unique_ptr currentReshapeGeometry;
2152  GEOSGeometry **newGeoms = new GEOSGeometry*[numGeoms];
2153 
2154  for ( int i = 0; i < numGeoms; ++i )
2155  {
2156  if ( isLine )
2157  currentReshapeGeometry = reshapeLine( GEOSGetGeometryN_r( geosinit()->ctxt, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
2158  else
2159  currentReshapeGeometry = reshapePolygon( GEOSGetGeometryN_r( geosinit()->ctxt, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
2160 
2161  if ( currentReshapeGeometry )
2162  {
2163  newGeoms[i] = currentReshapeGeometry.release();
2164  reshapeTookPlace = true;
2165  }
2166  else
2167  {
2168  newGeoms[i] = GEOSGeom_clone_r( geosinit()->ctxt, GEOSGetGeometryN_r( geosinit()->ctxt, mGeos.get(), i ) );
2169  }
2170  }
2171 
2172  geos::unique_ptr newMultiGeom;
2173  if ( isLine )
2174  {
2175  newMultiGeom.reset( GEOSGeom_createCollection_r( geosinit()->ctxt, GEOS_MULTILINESTRING, newGeoms, numGeoms ) );
2176  }
2177  else //multipolygon
2178  {
2179  newMultiGeom.reset( GEOSGeom_createCollection_r( geosinit()->ctxt, GEOS_MULTIPOLYGON, newGeoms, numGeoms ) );
2180  }
2181 
2182  delete[] newGeoms;
2183  if ( !newMultiGeom )
2184  {
2185  if ( errorCode ) { *errorCode = EngineError; }
2186  return nullptr;
2187  }
2188 
2189  if ( reshapeTookPlace )
2190  {
2191  if ( errorCode )
2192  *errorCode = Success;
2193  std::unique_ptr< QgsAbstractGeometry > reshapedMultiGeom = fromGeos( newMultiGeom.get() );
2194  return reshapedMultiGeom;
2195  }
2196  else
2197  {
2198  if ( errorCode )
2199  {
2200  *errorCode = NothingHappened;
2201  }
2202  return nullptr;
2203  }
2204  }
2205  CATCH_GEOS_WITH_ERRMSG( nullptr )
2206  }
2207 }
2208 
2209 QgsGeometry QgsGeos::mergeLines( QString *errorMsg ) const
2210 {
2211  if ( !mGeos )
2212  {
2213  return QgsGeometry();
2214  }
2215 
2216  if ( GEOSGeomTypeId_r( geosinit()->ctxt, mGeos.get() ) != GEOS_MULTILINESTRING )
2217  return QgsGeometry();
2218 
2220  try
2221  {
2222  geos.reset( GEOSLineMerge_r( geosinit()->ctxt, mGeos.get() ) );
2223  }
2225  return QgsGeometry( fromGeos( geos.get() ) );
2226 }
2227 
2228 QgsGeometry QgsGeos::closestPoint( const QgsGeometry &other, QString *errorMsg ) const
2229 {
2230  if ( !mGeos || isEmpty() || other.isNull() )
2231  {
2232  return QgsGeometry();
2233  }
2234 
2235  geos::unique_ptr otherGeom( asGeos( other.constGet(), mPrecision ) );
2236  if ( !otherGeom )
2237  {
2238  return QgsGeometry();
2239  }
2240 
2241  double nx = 0.0;
2242  double ny = 0.0;
2243  try
2244  {
2245 #if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=9 )
2246  geos::coord_sequence_unique_ptr nearestCoord;
2247  if ( mGeosPrepared ) // use faster version with prepared geometry
2248  {
2249  nearestCoord.reset( GEOSPreparedNearestPoints_r( geosinit()->ctxt, mGeosPrepared.get(), otherGeom.get() ) );
2250  }
2251  else
2252  {
2253  nearestCoord.reset( GEOSNearestPoints_r( geosinit()->ctxt, mGeos.get(), otherGeom.get() ) );
2254  }
2255 #else
2256  geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( geosinit()->ctxt, mGeos.get(), otherGeom.get() ) );
2257 #endif
2258 
2259  ( void )GEOSCoordSeq_getX_r( geosinit()->ctxt, nearestCoord.get(), 0, &nx );
2260  ( void )GEOSCoordSeq_getY_r( geosinit()->ctxt, nearestCoord.get(), 0, &ny );
2261  }
2262  catch ( GEOSException &e )
2263  {
2264  logError( QStringLiteral( "GEOS" ), e.what() );
2265  if ( errorMsg )
2266  {
2267  *errorMsg = e.what();
2268  }
2269  return QgsGeometry();
2270  }
2271 
2272  return QgsGeometry( new QgsPoint( nx, ny ) );
2273 }
2274 
2275 QgsGeometry QgsGeos::shortestLine( const QgsGeometry &other, QString *errorMsg ) const
2276 {
2277  if ( !mGeos || other.isNull() )
2278  {
2279  return QgsGeometry();
2280  }
2281 
2282  geos::unique_ptr otherGeom( asGeos( other.constGet(), mPrecision ) );
2283  if ( !otherGeom )
2284  {
2285  return QgsGeometry();
2286  }
2287 
2288  double nx1 = 0.0;
2289  double ny1 = 0.0;
2290  double nx2 = 0.0;
2291  double ny2 = 0.0;
2292  try
2293  {
2294  geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( geosinit()->ctxt, mGeos.get(), otherGeom.get() ) );
2295 
2296  ( void )GEOSCoordSeq_getX_r( geosinit()->ctxt, nearestCoord.get(), 0, &nx1 );
2297  ( void )GEOSCoordSeq_getY_r( geosinit()->ctxt, nearestCoord.get(), 0, &ny1 );
2298  ( void )GEOSCoordSeq_getX_r( geosinit()->ctxt, nearestCoord.get(), 1, &nx2 );
2299  ( void )GEOSCoordSeq_getY_r( geosinit()->ctxt, nearestCoord.get(), 1, &ny2 );
2300  }
2301  catch ( GEOSException &e )
2302  {
2303  logError( QStringLiteral( "GEOS" ), e.what() );
2304  if ( errorMsg )
2305  {
2306  *errorMsg = e.what();
2307  }
2308  return QgsGeometry();
2309  }
2310 
2311  QgsLineString *line = new QgsLineString();
2312  line->addVertex( QgsPoint( nx1, ny1 ) );
2313  line->addVertex( QgsPoint( nx2, ny2 ) );
2314  return QgsGeometry( line );
2315 }
2316 
2317 double QgsGeos::lineLocatePoint( const QgsPoint &point, QString *errorMsg ) const
2318 {
2319  if ( !mGeos )
2320  {
2321  return -1;
2322  }
2323 
2324  geos::unique_ptr otherGeom( asGeos( &point, mPrecision ) );
2325  if ( !otherGeom )
2326  {
2327  return -1;
2328  }
2329 
2330  double distance = -1;
2331  try
2332  {
2333  distance = GEOSProject_r( geosinit()->ctxt, mGeos.get(), otherGeom.get() );
2334  }
2335  catch ( GEOSException &e )
2336  {
2337  logError( QStringLiteral( "GEOS" ), e.what() );
2338  if ( errorMsg )
2339  {
2340  *errorMsg = e.what();
2341  }
2342  return -1;
2343  }
2344 
2345  return distance;
2346 }
2347 
2348 QgsGeometry QgsGeos::polygonize( const QVector<const QgsAbstractGeometry *> &geometries, QString *errorMsg )
2349 {
2350  GEOSGeometry **const lineGeosGeometries = new GEOSGeometry*[ geometries.size()];
2351  int validLines = 0;
2352  for ( const QgsAbstractGeometry *g : geometries )
2353  {
2354  geos::unique_ptr l = asGeos( g );
2355  if ( l )
2356  {
2357  lineGeosGeometries[validLines] = l.release();
2358  validLines++;
2359  }
2360  }
2361 
2362  try
2363  {
2364  geos::unique_ptr result( GEOSPolygonize_r( geosinit()->ctxt, lineGeosGeometries, validLines ) );
2365  for ( int i = 0; i < validLines; ++i )
2366  {
2367  GEOSGeom_destroy_r( geosinit()->ctxt, lineGeosGeometries[i] );
2368  }
2369  delete[] lineGeosGeometries;
2370  return QgsGeometry( fromGeos( result.get() ) );
2371  }
2372  catch ( GEOSException &e )
2373  {
2374  if ( errorMsg )
2375  {
2376  *errorMsg = e.what();
2377  }
2378  for ( int i = 0; i < validLines; ++i )
2379  {
2380  GEOSGeom_destroy_r( geosinit()->ctxt, lineGeosGeometries[i] );
2381  }
2382  delete[] lineGeosGeometries;
2383  return QgsGeometry();
2384  }
2385 }
2386 
2387 QgsGeometry QgsGeos::voronoiDiagram( const QgsAbstractGeometry *extent, double tolerance, bool edgesOnly, QString *errorMsg ) const
2388 {
2389  if ( !mGeos )
2390  {
2391  return QgsGeometry();
2392  }
2393 
2394  geos::unique_ptr extentGeosGeom;
2395  if ( extent )
2396  {
2397  extentGeosGeom = asGeos( extent, mPrecision );
2398  if ( !extentGeosGeom )
2399  {
2400  return QgsGeometry();
2401  }
2402  }
2403 
2405  try
2406  {
2407  geos.reset( GEOSVoronoiDiagram_r( geosinit()->ctxt, mGeos.get(), extentGeosGeom.get(), tolerance, edgesOnly ) );
2408 
2409  if ( !geos || GEOSisEmpty_r( geosinit()->ctxt, geos.get() ) != 0 )
2410  {
2411  return QgsGeometry();
2412  }
2413 
2414  return QgsGeometry( fromGeos( geos.get() ) );
2415  }
2417 }
2418 
2419 QgsGeometry QgsGeos::delaunayTriangulation( double tolerance, bool edgesOnly, QString *errorMsg ) const
2420 {
2421  if ( !mGeos )
2422  {
2423  return QgsGeometry();
2424  }
2425 
2427  try
2428  {
2429  geos.reset( GEOSDelaunayTriangulation_r( geosinit()->ctxt, mGeos.get(), tolerance, edgesOnly ) );
2430 
2431  if ( !geos || GEOSisEmpty_r( geosinit()->ctxt, geos.get() ) != 0 )
2432  {
2433  return QgsGeometry();
2434  }
2435 
2436  return QgsGeometry( fromGeos( geos.get() ) );
2437  }
2439 }
2440 
2441 
2443 static bool _linestringEndpoints( const GEOSGeometry *linestring, double &x1, double &y1, double &x2, double &y2 )
2444 {
2445  const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, linestring );
2446  if ( !coordSeq )
2447  return false;
2448 
2449  unsigned int coordSeqSize;
2450  if ( GEOSCoordSeq_getSize_r( geosinit()->ctxt, coordSeq, &coordSeqSize ) == 0 )
2451  return false;
2452 
2453  if ( coordSeqSize < 2 )
2454  return false;
2455 
2456  GEOSCoordSeq_getX_r( geosinit()->ctxt, coordSeq, 0, &x1 );
2457  GEOSCoordSeq_getY_r( geosinit()->ctxt, coordSeq, 0, &y1 );
2458  GEOSCoordSeq_getX_r( geosinit()->ctxt, coordSeq, coordSeqSize - 1, &x2 );
2459  GEOSCoordSeq_getY_r( geosinit()->ctxt, coordSeq, coordSeqSize - 1, &y2 );
2460  return true;
2461 }
2462 
2463 
2465 static geos::unique_ptr _mergeLinestrings( const GEOSGeometry *line1, const GEOSGeometry *line2, const QgsPointXY &intersectionPoint )
2466 {
2467  double x1, y1, x2, y2;
2468  if ( !_linestringEndpoints( line1, x1, y1, x2, y2 ) )
2469  return nullptr;
2470 
2471  double rx1, ry1, rx2, ry2;
2472  if ( !_linestringEndpoints( line2, rx1, ry1, rx2, ry2 ) )
2473  return nullptr;
2474 
2475  bool intersectionAtOrigLineEndpoint =
2476  ( intersectionPoint.x() == x1 && intersectionPoint.y() == y1 ) !=
2477  ( intersectionPoint.x() == x2 && intersectionPoint.y() == y2 );
2478  bool intersectionAtReshapeLineEndpoint =
2479  ( intersectionPoint.x() == rx1 && intersectionPoint.y() == ry1 ) ||
2480  ( intersectionPoint.x() == rx2 && intersectionPoint.y() == ry2 );
2481 
2482  // the intersection must be at the begin/end of both lines
2483  if ( intersectionAtOrigLineEndpoint && intersectionAtReshapeLineEndpoint )
2484  {
2485  geos::unique_ptr g1( GEOSGeom_clone_r( geosinit()->ctxt, line1 ) );
2486  geos::unique_ptr g2( GEOSGeom_clone_r( geosinit()->ctxt, line2 ) );
2487  GEOSGeometry *geoms[2] = { g1.release(), g2.release() };
2488  geos::unique_ptr multiGeom( GEOSGeom_createCollection_r( geosinit()->ctxt, GEOS_MULTILINESTRING, geoms, 2 ) );
2489  geos::unique_ptr res( GEOSLineMerge_r( geosinit()->ctxt, multiGeom.get() ) );
2490  return res;
2491  }
2492  else
2493  return nullptr;
2494 }
2495 
2496 
2497 geos::unique_ptr QgsGeos::reshapeLine( const GEOSGeometry *line, const GEOSGeometry *reshapeLineGeos, double precision )
2498 {
2499  if ( !line || !reshapeLineGeos )
2500  return nullptr;
2501 
2502  bool atLeastTwoIntersections = false;
2503  bool oneIntersection = false;
2504  QgsPointXY oneIntersectionPoint;
2505 
2506  try
2507  {
2508  //make sure there are at least two intersection between line and reshape geometry
2509  geos::unique_ptr intersectGeom( GEOSIntersection_r( geosinit()->ctxt, line, reshapeLineGeos ) );
2510  if ( intersectGeom )
2511  {
2512  const int geomType = GEOSGeomTypeId_r( geosinit()->ctxt, intersectGeom.get() );
2513  atLeastTwoIntersections = ( geomType == GEOS_MULTIPOINT && GEOSGetNumGeometries_r( geosinit()->ctxt, intersectGeom.get() ) > 1 )
2514  || ( geomType == GEOS_GEOMETRYCOLLECTION && GEOSGetNumGeometries_r( geosinit()->ctxt, intersectGeom.get() ) > 0 ) // a collection implies at least two points!
2515  || ( geomType == GEOS_MULTILINESTRING && GEOSGetNumGeometries_r( geosinit()->ctxt, intersectGeom.get() ) > 0 );
2516  // one point is enough when extending line at its endpoint
2517  if ( GEOSGeomTypeId_r( geosinit()->ctxt, intersectGeom.get() ) == GEOS_POINT )
2518  {
2519  const GEOSCoordSequence *intersectionCoordSeq = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, intersectGeom.get() );
2520  double xi, yi;
2521  GEOSCoordSeq_getX_r( geosinit()->ctxt, intersectionCoordSeq, 0, &xi );
2522  GEOSCoordSeq_getY_r( geosinit()->ctxt, intersectionCoordSeq, 0, &yi );
2523  oneIntersection = true;
2524  oneIntersectionPoint = QgsPointXY( xi, yi );
2525  }
2526  }
2527  }
2528  catch ( GEOSException & )
2529  {
2530  atLeastTwoIntersections = false;
2531  }
2532 
2533  // special case when extending line at its endpoint
2534  if ( oneIntersection )
2535  return _mergeLinestrings( line, reshapeLineGeos, oneIntersectionPoint );
2536 
2537  if ( !atLeastTwoIntersections )
2538  return nullptr;
2539 
2540  //begin and end point of original line
2541  double x1, y1, x2, y2;
2542  if ( !_linestringEndpoints( line, x1, y1, x2, y2 ) )
2543  return nullptr;
2544 
2545  geos::unique_ptr beginLineVertex = createGeosPointXY( x1, y1, false, 0, false, 0, 2, precision );
2546  geos::unique_ptr endLineVertex = createGeosPointXY( x2, y2, false, 0, false, 0, 2, precision );
2547 
2548  bool isRing = false;
2549  if ( GEOSGeomTypeId_r( geosinit()->ctxt, line ) == GEOS_LINEARRING
2550  || GEOSEquals_r( geosinit()->ctxt, beginLineVertex.get(), endLineVertex.get() ) == 1 )
2551  isRing = true;
2552 
2553  //node line and reshape line
2554  geos::unique_ptr nodedGeometry = nodeGeometries( reshapeLineGeos, line );
2555  if ( !nodedGeometry )
2556  {
2557  return nullptr;
2558  }
2559 
2560  //and merge them together
2561  geos::unique_ptr mergedLines( GEOSLineMerge_r( geosinit()->ctxt, nodedGeometry.get() ) );
2562  if ( !mergedLines )
2563  {
2564  return nullptr;
2565  }
2566 
2567  int numMergedLines = GEOSGetNumGeometries_r( geosinit()->ctxt, mergedLines.get() );
2568  if ( numMergedLines < 2 ) //some special cases. Normally it is >2
2569  {
2570  if ( numMergedLines == 1 ) //reshape line is from begin to endpoint. So we keep the reshapeline
2571  {
2572  geos::unique_ptr result( GEOSGeom_clone_r( geosinit()->ctxt, reshapeLineGeos ) );
2573  return result;
2574  }
2575  else
2576  return nullptr;
2577  }
2578 
2579  QVector<GEOSGeometry *> resultLineParts; //collection with the line segments that will be contained in result
2580  QVector<GEOSGeometry *> probableParts; //parts where we can decide on inclusion only after going through all the candidates
2581 
2582  for ( int i = 0; i < numMergedLines; ++i )
2583  {
2584  const GEOSGeometry *currentGeom = GEOSGetGeometryN_r( geosinit()->ctxt, mergedLines.get(), i );
2585 
2586  // have we already added this part?
2587  bool alreadyAdded = false;
2588  double distance = 0;
2589  double bufferDistance = std::pow( 10.0L, geomDigits( currentGeom ) - 11 );
2590  for ( const GEOSGeometry *other : qgis::as_const( resultLineParts ) )
2591  {
2592  GEOSHausdorffDistance_r( geosinit()->ctxt, currentGeom, other, &distance );
2593  if ( distance < bufferDistance )
2594  {
2595  alreadyAdded = true;
2596  break;
2597  }
2598  }
2599  if ( alreadyAdded )
2600  continue;
2601 
2602  const GEOSCoordSequence *currentCoordSeq = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, currentGeom );
2603  unsigned int currentCoordSeqSize;
2604  GEOSCoordSeq_getSize_r( geosinit()->ctxt, currentCoordSeq, &currentCoordSeqSize );
2605  if ( currentCoordSeqSize < 2 )
2606  continue;
2607 
2608  //get the two endpoints of the current line merge result
2609  double xBegin, xEnd, yBegin, yEnd;
2610  GEOSCoordSeq_getX_r( geosinit()->ctxt, currentCoordSeq, 0, &xBegin );
2611  GEOSCoordSeq_getY_r( geosinit()->ctxt, currentCoordSeq, 0, &yBegin );
2612  GEOSCoordSeq_getX_r( geosinit()->ctxt, currentCoordSeq, currentCoordSeqSize - 1, &xEnd );
2613  GEOSCoordSeq_getY_r( geosinit()->ctxt, currentCoordSeq, currentCoordSeqSize - 1, &yEnd );
2614  geos::unique_ptr beginCurrentGeomVertex = createGeosPointXY( xBegin, yBegin, false, 0, false, 0, 2, precision );
2615  geos::unique_ptr endCurrentGeomVertex = createGeosPointXY( xEnd, yEnd, false, 0, false, 0, 2, precision );
2616 
2617  //check how many endpoints of the line merge result are on the (original) line
2618  int nEndpointsOnOriginalLine = 0;
2619  if ( pointContainedInLine( beginCurrentGeomVertex.get(), line ) == 1 )
2620  nEndpointsOnOriginalLine += 1;
2621 
2622  if ( pointContainedInLine( endCurrentGeomVertex.get(), line ) == 1 )
2623  nEndpointsOnOriginalLine += 1;
2624 
2625  //check how many endpoints equal the endpoints of the original line
2626  int nEndpointsSameAsOriginalLine = 0;
2627  if ( GEOSEquals_r( geosinit()->ctxt, beginCurrentGeomVertex.get(), beginLineVertex.get() ) == 1
2628  || GEOSEquals_r( geosinit()->ctxt, beginCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
2629  nEndpointsSameAsOriginalLine += 1;
2630 
2631  if ( GEOSEquals_r( geosinit()->ctxt, endCurrentGeomVertex.get(), beginLineVertex.get() ) == 1
2632  || GEOSEquals_r( geosinit()->ctxt, endCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
2633  nEndpointsSameAsOriginalLine += 1;
2634 
2635  //check if the current geometry overlaps the original geometry (GEOSOverlap does not seem to work with linestrings)
2636  bool currentGeomOverlapsOriginalGeom = false;
2637  bool currentGeomOverlapsReshapeLine = false;
2638  if ( lineContainedInLine( currentGeom, line ) == 1 )
2639  currentGeomOverlapsOriginalGeom = true;
2640 
2641  if ( lineContainedInLine( currentGeom, reshapeLineGeos ) == 1 )
2642  currentGeomOverlapsReshapeLine = true;
2643 
2644  //logic to decide if this part belongs to the result
2645  if ( !isRing && nEndpointsSameAsOriginalLine == 1 && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
2646  {
2647  resultLineParts.push_back( GEOSGeom_clone_r( geosinit()->ctxt, currentGeom ) );
2648  }
2649  //for closed rings, we take one segment from the candidate list
2650  else if ( isRing && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
2651  {
2652  probableParts.push_back( GEOSGeom_clone_r( geosinit()->ctxt, currentGeom ) );
2653  }
2654  else if ( nEndpointsOnOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
2655  {
2656  resultLineParts.push_back( GEOSGeom_clone_r( geosinit()->ctxt, currentGeom ) );
2657  }
2658  else if ( nEndpointsSameAsOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
2659  {
2660  resultLineParts.push_back( GEOSGeom_clone_r( geosinit()->ctxt, currentGeom ) );
2661  }
2662  else if ( currentGeomOverlapsOriginalGeom && currentGeomOverlapsReshapeLine )
2663  {
2664  resultLineParts.push_back( GEOSGeom_clone_r( geosinit()->ctxt, currentGeom ) );
2665  }
2666  }
2667 
2668  //add the longest segment from the probable list for rings (only used for polygon rings)
2669  if ( isRing && !probableParts.isEmpty() )
2670  {
2671  geos::unique_ptr maxGeom; //the longest geometry in the probabla list
2672  GEOSGeometry *currentGeom = nullptr;
2673  double maxLength = -std::numeric_limits<double>::max();
2674  double currentLength = 0;
2675  for ( int i = 0; i < probableParts.size(); ++i )
2676  {
2677  currentGeom = probableParts.at( i );
2678  GEOSLength_r( geosinit()->ctxt, currentGeom, &currentLength );
2679  if ( currentLength > maxLength )
2680  {
2681  maxLength = currentLength;
2682  maxGeom.reset( currentGeom );
2683  }
2684  else
2685  {
2686  GEOSGeom_destroy_r( geosinit()->ctxt, currentGeom );
2687  }
2688  }
2689  resultLineParts.push_back( maxGeom.release() );
2690  }
2691 
2692  geos::unique_ptr result;
2693  if ( resultLineParts.empty() )
2694  return nullptr;
2695 
2696  if ( resultLineParts.size() == 1 ) //the whole result was reshaped
2697  {
2698  result.reset( resultLineParts[0] );
2699  }
2700  else //>1
2701  {
2702  GEOSGeometry **lineArray = new GEOSGeometry*[resultLineParts.size()];
2703  for ( int i = 0; i < resultLineParts.size(); ++i )
2704  {
2705  lineArray[i] = resultLineParts[i];
2706  }
2707 
2708  //create multiline from resultLineParts
2709  geos::unique_ptr multiLineGeom( GEOSGeom_createCollection_r( geosinit()->ctxt, GEOS_MULTILINESTRING, lineArray, resultLineParts.size() ) );
2710  delete [] lineArray;
2711 
2712  //then do a linemerge with the newly combined partstrings
2713  result.reset( GEOSLineMerge_r( geosinit()->ctxt, multiLineGeom.get() ) );
2714  }
2715 
2716  //now test if the result is a linestring. Otherwise something went wrong
2717  if ( GEOSGeomTypeId_r( geosinit()->ctxt, result.get() ) != GEOS_LINESTRING )
2718  {
2719  return nullptr;
2720  }
2721 
2722  return result;
2723 }
2724 
2725 geos::unique_ptr QgsGeos::reshapePolygon( const GEOSGeometry *polygon, const GEOSGeometry *reshapeLineGeos, double precision )
2726 {
2727  //go through outer shell and all inner rings and check if there is exactly one intersection of a ring and the reshape line
2728  int nIntersections = 0;
2729  int lastIntersectingRing = -2;
2730  const GEOSGeometry *lastIntersectingGeom = nullptr;
2731 
2732  int nRings = GEOSGetNumInteriorRings_r( geosinit()->ctxt, polygon );
2733  if ( nRings < 0 )
2734  return nullptr;
2735 
2736  //does outer ring intersect?
2737  const GEOSGeometry *outerRing = GEOSGetExteriorRing_r( geosinit()->ctxt, polygon );
2738  if ( GEOSIntersects_r( geosinit()->ctxt, outerRing, reshapeLineGeos ) == 1 )
2739  {
2740  ++nIntersections;
2741  lastIntersectingRing = -1;
2742  lastIntersectingGeom = outerRing;
2743  }
2744 
2745  //do inner rings intersect?
2746  const GEOSGeometry **innerRings = new const GEOSGeometry*[nRings];
2747 
2748  try
2749  {
2750  for ( int i = 0; i < nRings; ++i )
2751  {
2752  innerRings[i] = GEOSGetInteriorRingN_r( geosinit()->ctxt, polygon, i );
2753  if ( GEOSIntersects_r( geosinit()->ctxt, innerRings[i], reshapeLineGeos ) == 1 )
2754  {
2755  ++nIntersections;
2756  lastIntersectingRing = i;
2757  lastIntersectingGeom = innerRings[i];
2758  }
2759  }
2760  }
2761  catch ( GEOSException & )
2762  {
2763  nIntersections = 0;
2764  }
2765 
2766  if ( nIntersections != 1 ) //reshape line is only allowed to intersect one ring
2767  {
2768  delete [] innerRings;
2769  return nullptr;
2770  }
2771 
2772  //we have one intersecting ring, let's try to reshape it
2773  geos::unique_ptr reshapeResult = reshapeLine( lastIntersectingGeom, reshapeLineGeos, precision );
2774  if ( !reshapeResult )
2775  {
2776  delete [] innerRings;
2777  return nullptr;
2778  }
2779 
2780  //if reshaping took place, we need to reassemble the polygon and its rings
2781  GEOSGeometry *newRing = nullptr;
2782  const GEOSCoordSequence *reshapeSequence = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, reshapeResult.get() );
2783  GEOSCoordSequence *newCoordSequence = GEOSCoordSeq_clone_r( geosinit()->ctxt, reshapeSequence );
2784 
2785  reshapeResult.reset();
2786 
2787  newRing = GEOSGeom_createLinearRing_r( geosinit()->ctxt, newCoordSequence );
2788  if ( !newRing )
2789  {
2790  delete [] innerRings;
2791  return nullptr;
2792  }
2793 
2794  GEOSGeometry *newOuterRing = nullptr;
2795  if ( lastIntersectingRing == -1 )
2796  newOuterRing = newRing;
2797  else
2798  newOuterRing = GEOSGeom_clone_r( geosinit()->ctxt, outerRing );
2799 
2800  //check if all the rings are still inside the outer boundary
2801  QVector<GEOSGeometry *> ringList;
2802  if ( nRings > 0 )
2803  {
2804  GEOSGeometry *outerRingPoly = GEOSGeom_createPolygon_r( geosinit()->ctxt, GEOSGeom_clone_r( geosinit()->ctxt, newOuterRing ), nullptr, 0 );
2805  if ( outerRingPoly )
2806  {
2807  ringList.reserve( nRings );
2808  GEOSGeometry *currentRing = nullptr;
2809  for ( int i = 0; i < nRings; ++i )
2810  {
2811  if ( lastIntersectingRing == i )
2812  currentRing = newRing;
2813  else
2814  currentRing = GEOSGeom_clone_r( geosinit()->ctxt, innerRings[i] );
2815 
2816  //possibly a ring is no longer contained in the result polygon after reshape
2817  if ( GEOSContains_r( geosinit()->ctxt, outerRingPoly, currentRing ) == 1 )
2818  ringList.push_back( currentRing );
2819  else
2820  GEOSGeom_destroy_r( geosinit()->ctxt, currentRing );
2821  }
2822  }
2823  GEOSGeom_destroy_r( geosinit()->ctxt, outerRingPoly );
2824  }
2825 
2826  GEOSGeometry **newInnerRings = new GEOSGeometry*[ringList.size()];
2827  for ( int i = 0; i < ringList.size(); ++i )
2828  newInnerRings[i] = ringList.at( i );
2829 
2830  delete [] innerRings;
2831 
2832  geos::unique_ptr reshapedPolygon( GEOSGeom_createPolygon_r( geosinit()->ctxt, newOuterRing, newInnerRings, ringList.size() ) );
2833  delete[] newInnerRings;
2834 
2835  return reshapedPolygon;
2836 }
2837 
2838 int QgsGeos::lineContainedInLine( const GEOSGeometry *line1, const GEOSGeometry *line2 )
2839 {
2840  if ( !line1 || !line2 )
2841  {
2842  return -1;
2843  }
2844 
2845  double bufferDistance = std::pow( 10.0L, geomDigits( line2 ) - 11 );
2846 
2847  geos::unique_ptr bufferGeom( GEOSBuffer_r( geosinit()->ctxt, line2, bufferDistance, DEFAULT_QUADRANT_SEGMENTS ) );
2848  if ( !bufferGeom )
2849  return -2;
2850 
2851  geos::unique_ptr intersectionGeom( GEOSIntersection_r( geosinit()->ctxt, bufferGeom.get(), line1 ) );
2852 
2853  //compare ratio between line1Length and intersectGeomLength (usually close to 1 if line1 is contained in line2)
2854  double intersectGeomLength;
2855  double line1Length;
2856 
2857  GEOSLength_r( geosinit()->ctxt, intersectionGeom.get(), &intersectGeomLength );
2858  GEOSLength_r( geosinit()->ctxt, line1, &line1Length );
2859 
2860  double intersectRatio = line1Length / intersectGeomLength;
2861  if ( intersectRatio > 0.9 && intersectRatio < 1.1 )
2862  return 1;
2863 
2864  return 0;
2865 }
2866 
2867 int QgsGeos::pointContainedInLine( const GEOSGeometry *point, const GEOSGeometry *line )
2868 {
2869  if ( !point || !line )
2870  return -1;
2871 
2872  double bufferDistance = std::pow( 10.0L, geomDigits( line ) - 11 );
2873 
2874  geos::unique_ptr lineBuffer( GEOSBuffer_r( geosinit()->ctxt, line, bufferDistance, 8 ) );
2875  if ( !lineBuffer )
2876  return -2;
2877 
2878  bool contained = false;
2879  if ( GEOSContains_r( geosinit()->ctxt, lineBuffer.get(), point ) == 1 )
2880  contained = true;
2881 
2882  return contained;
2883 }
2884 
2885 int QgsGeos::geomDigits( const GEOSGeometry *geom )
2886 {
2887  geos::unique_ptr bbox( GEOSEnvelope_r( geosinit()->ctxt, geom ) );
2888  if ( !bbox.get() )
2889  return -1;
2890 
2891  const GEOSGeometry *bBoxRing = GEOSGetExteriorRing_r( geosinit()->ctxt, bbox.get() );
2892  if ( !bBoxRing )
2893  return -1;
2894 
2895  const GEOSCoordSequence *bBoxCoordSeq = GEOSGeom_getCoordSeq_r( geosinit()->ctxt, bBoxRing );
2896 
2897  if ( !bBoxCoordSeq )
2898  return -1;
2899 
2900  unsigned int nCoords = 0;
2901  if ( !GEOSCoordSeq_getSize_r( geosinit()->ctxt, bBoxCoordSeq, &nCoords ) )
2902  return -1;
2903 
2904  int maxDigits = -1;
2905  for ( unsigned int i = 0; i < nCoords - 1; ++i )
2906  {
2907  double t;
2908  GEOSCoordSeq_getX_r( geosinit()->ctxt, bBoxCoordSeq, i, &t );
2909 
2910  int digits;
2911  digits = std::ceil( std::log10( std::fabs( t ) ) );
2912  if ( digits > maxDigits )
2913  maxDigits = digits;
2914 
2915  GEOSCoordSeq_getY_r( geosinit()->ctxt, bBoxCoordSeq, i, &t );
2916  digits = std::ceil( std::log10( std::fabs( t ) ) );
2917  if ( digits > maxDigits )
2918  maxDigits = digits;
2919  }
2920 
2921  return maxDigits;
2922 }
2923 
2924 GEOSContextHandle_t QgsGeos::getGEOSHandler()
2925 {
2926  return geosinit()->ctxt;
2927 }
Abstract base class for all geometries.
bool is3D() const SIP_HOLDGIL
Returns true if the geometry is 3D and contains a z-value.
virtual QgsRectangle boundingBox() const =0
Returns the minimal bounding box for the geometry.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
virtual int dimension() const =0
Returns the inherent dimension of the geometry.
bool isMeasure() const SIP_HOLDGIL
Returns true if the geometry contains m values.
Curve polygon geometry type.
const QgsCurve * interiorRing(int i) const SIP_HOLDGIL
Retrieves an interior ring from the curve polygon.
const QgsCurve * exteriorRing() const SIP_HOLDGIL
Returns the curve polygon's exterior ring.
int numInteriorRings() const SIP_HOLDGIL
Returns the number of interior rings contained with the curve polygon.
Abstract base class for curved geometry type.
Definition: qgscurve.h:36
virtual QgsLineString * curveToLine(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const =0
Returns a new line string geometry corresponding to a segmentized approximation of the curve.
Geometry collection.
virtual bool addGeometry(QgsAbstractGeometry *g)
Adds a geometry and takes ownership. Returns true in case of success.
static QgsGeometry::OperationResult addPart(QgsAbstractGeometry *geometry, std::unique_ptr< QgsAbstractGeometry > part)
Add a part to multi type geometry.
Contains geometry relation and modification algorithms.
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.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Q_GADGET bool isNull
Definition: qgsgeometry.h:126
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
OperationResult
Success or failure of a geometry operation.
Definition: qgsgeometry.h:136
@ InvalidBaseGeometry
The base geometry on which the operation is done is invalid or empty.
Definition: qgsgeometry.h:139
@ AddPartNotMultiGeometry
The source geometry is not multi.
Definition: qgsgeometry.h:147
Does vector analysis using the geos library and handles import, export, exception handling*.
Definition: qgsgeos.h:104
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:2419
QgsAbstractGeometry * intersection(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Calculate the intersection of this and geom.
Definition: qgsgeos.cpp:214
bool touches(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom touches this.
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:474
bool isValid(QString *errorMsg=nullptr, bool allowSelfTouchingHoles=false, QgsGeometry *errorLoc=nullptr) const override
Returns true if the geometry is valid.
Definition: qgsgeos.cpp:1698
double hausdorffDistance(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const
Returns the Hausdorff distance between this geometry and geom.
Definition: qgsgeos.cpp:451
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:163
QgsAbstractGeometry * buffer(double distance, int segments, QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:1551
std::unique_ptr< QgsAbstractGeometry > reshapeGeometry(const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg=nullptr) const
Reshapes the geometry using a line.
Definition: qgsgeos.cpp:2093
QgsAbstractGeometry * combine(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Calculate the combination of this and geom.
Definition: qgsgeos.cpp:359
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:630
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:224
bool contains(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom contains this.
Definition: qgsgeos.cpp:522
bool disjoint(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom is disjoint from this.
Definition: qgsgeos.cpp:527
QgsGeos(const QgsAbstractGeometry *geometry, double precision=0)
GEOS geometry engine constructor.
Definition: qgsgeos.cpp:142
static QgsGeometry::OperationResult addPart(QgsGeometry &geometry, GEOSGeometry *newPart)
Adds a new island polygon to a multipolygon feature.
Definition: qgsgeos.cpp:173
QgsGeometry shortestLine(const QgsGeometry &other, QString *errorMsg=nullptr) const
Returns the shortest line joining this geometry to the other geometry.
Definition: qgsgeos.cpp:2275
QgsAbstractGeometry * offsetCurve(double distance, int segments, int joinStyle, double miterLimit, QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:2052
QgsAbstractGeometry * simplify(double tolerance, QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:1583
QgsGeometry closestPoint(const QgsGeometry &other, QString *errorMsg=nullptr) const
Returns the closest point on the geometry to the other geometry.
Definition: qgsgeos.cpp:2228
static std::unique_ptr< QgsPolygon > fromGeosPolygon(const GEOSGeometry *geos)
Definition: qgsgeos.cpp:1198
QgsAbstractGeometry * envelope(QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:1639
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:532
bool crosses(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom crosses this.
Definition: qgsgeos.cpp:507
QgsAbstractGeometry * convexHull(QString *errorMsg=nullptr) const override
Calculate the convex hull of this.
Definition: qgsgeos.cpp:1682
double distance(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Calculates the distance between this and geom.
Definition: qgsgeos.cpp:417
QgsAbstractGeometry * symDifference(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Calculate the symmetric difference of this and geom.
Definition: qgsgeos.cpp:412
bool isSimple(QString *errorMsg=nullptr) const override
Determines whether the geometry is simple (according to OGC definition).
Definition: qgsgeos.cpp:1790
bool within(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom is within this.
Definition: qgsgeos.cpp:512
void prepareGeometry() override
Prepares the geometry, so that subsequent calls to spatial relation methods are much faster.
Definition: qgsgeos.cpp:195
std::unique_ptr< QgsAbstractGeometry > singleSidedBuffer(double distance, int segments, int side, int joinStyle, double miterLimit, QString *errorMsg=nullptr) const
Returns a single sided buffer for a geometry.
Definition: qgsgeos.cpp:2067
static std::unique_ptr< QgsAbstractGeometry > fromGeos(const GEOSGeometry *geos)
Create a geometry from a GEOSGeometry.
Definition: qgsgeos.cpp:1098
QgsAbstractGeometry * interpolate(double distance, QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:1598
bool isEqual(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if this is equal to geom.
Definition: qgsgeos.cpp:1756
QgsGeometry mergeLines(QString *errorMsg=nullptr) const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
Definition: qgsgeos.cpp:2209
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:2348
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:567
static GEOSContextHandle_t getGEOSHandler()
Definition: qgsgeos.cpp:2924
QgsPoint * centroid(QString *errorMsg=nullptr) const override
Calculates the centroid of this.
Definition: qgsgeos.cpp:1613
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:2317
bool isEmpty(QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:1776
std::unique_ptr< QgsAbstractGeometry > subdivide(int maxNodes, QString *errorMsg=nullptr) const
Subdivides the geometry.
Definition: qgsgeos.cpp:339
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:2387
bool intersects(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom intersects this.
Definition: qgsgeos.cpp:497
void geometryChanged() override
Should be called whenever the geometry associated with the engine has been modified and the engine mu...
Definition: qgsgeos.cpp:188
bool overlaps(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Checks if geom overlaps this.
Definition: qgsgeos.cpp:517
double area(QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:597
double length(QString *errorMsg=nullptr) const override
Definition: qgsgeos.cpp:614
QgsAbstractGeometry * difference(const QgsAbstractGeometry *geom, QString *errorMsg=nullptr) const override
Calculate the difference of this and geom.
Definition: qgsgeos.cpp:219
static QgsPoint coordSeqPoint(const GEOSCoordSequence *cs, int i, bool hasZ, bool hasM)
Definition: qgsgeos.cpp:1289
QgsPoint * pointOnSurface(QString *errorMsg=nullptr) const override
Calculate a point that is guaranteed to be on the surface of this.
Definition: qgsgeos.cpp:1654
static QgsGeometry geometryFromGeos(GEOSGeometry *geos)
Creates a new QgsGeometry object, feeding in a geometry in GEOS format.
Definition: qgsgeos.cpp:150
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
const double * yData() const
Returns a const pointer to the y vertex data.
int numPoints() const override SIP_HOLDGIL
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
const double * mData() const
Returns a const pointer to the m vertex data, or nullptr if the linestring does not have m values.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
double mAt(int index) const
Returns the m value of the specified node in the line string.
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.
const double * xData() const
Returns a const pointer to the x vertex data.
const double * zData() const
Returns a const pointer to the z vertex data, or nullptr if the linestring does not have z values.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
Multi curve geometry collection.
Definition: qgsmulticurve.h:30
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
Multi line string geometry collection.
Multi point geometry collection.
Definition: qgsmultipoint.h:30
Multi polygon geometry collection.
A class to represent a 2D point.
Definition: qgspointxy.h:44
double y
Definition: qgspointxy.h:48
Q_GADGET double x
Definition: qgspointxy.h:47
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:38
Q_GADGET double x
Definition: qgspoint.h:41
double z
Definition: qgspoint.h:43
double m
Definition: qgspoint.h:44
double y
Definition: qgspoint.h:42
Polygon geometry type.
Definition: qgspolygon.h:34
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:140
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:447
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:135
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:130
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:145
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:437
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:938
static bool isMultiType(Type type) SIP_HOLDGIL
Returns true if the WKB type is a multi type.
Definition: qgswkbtypes.h:832
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
@ GeometryCollection
Definition: qgswkbtypes.h:79
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:702
Contains geos related utilities and functions.
Definition: qgsgeos.h:42
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:79
std::unique_ptr< GEOSCoordSequence, GeosDeleter > coord_sequence_unique_ptr
Scoped GEOS coordinate sequence pointer.
Definition: qgsgeos.h:94
std::unique_ptr< GEOSBufferParams, GeosDeleter > buffer_params_unique_ptr
Scoped GEOS buffer params pointer.
Definition: qgsgeos.h:89
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:316
QMap< QString, QString > QgsStringMap
Definition: qgis.h:759
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
int precision
void CORE_EXPORT operator()(GEOSGeometry *geom)
Destroys the GEOS geometry geom, using the static QGIS geos context.