QGIS API Documentation 3.99.0-Master (7d2ca374f2d)
Loading...
Searching...
No Matches
qgsinternalgeometryengine.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsinternalgeometryengine.cpp - QgsInternalGeometryEngine
3
4 ---------------------
5 begin : 13.1.2016
6 copyright : (C) 2016 by Matthias Kuhn
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
18
19#include <functional>
20#include <geos_c.h>
21#include <memory>
22#include <random>
23
24#include "qgscircle.h"
25#include "qgscircularstring.h"
26#include "qgsfeedback.h"
27#include "qgsgeometry.h"
28#include "qgsgeometryengine.h"
29#include "qgsgeometryutils.h"
30#include "qgsgeos.h"
31#include "qgslinesegment.h"
32#include "qgslinestring.h"
33#include "qgsmulticurve.h"
34#include "qgsmultilinestring.h"
35#include "qgsmultipolygon.h"
36#include "qgspolygon.h"
37#include "qgstessellator.h"
38
39#include <QString>
40#include <QTransform>
41#include <queue>
42
43using namespace Qt::StringLiterals;
44
46 : mGeometry( geometry.constGet() )
47{
48
49}
50
52{
53 return mLastError;
54}
55
56
65
71Direction getEdgeDirection( const QgsPoint &p1, const QgsPoint &p2, double maxDev )
72{
73 double dx = p2.x() - p1.x();
74 double dy = p2.y() - p1.y();
75 if ( ( dx == 0.0 ) && ( dy == 0.0 ) )
76 return Direction::None;
77 if ( fabs( dx ) >= fabs( dy ) )
78 {
79 double dev = fabs( dy ) / fabs( dx );
80 if ( dev > maxDev )
81 return Direction::None;
82 return dx > 0.0 ? Direction::Right : Direction::Left;
83 }
84 else
85 {
86 double dev = fabs( dx ) / fabs( dy );
87 if ( dev > maxDev )
88 return Direction::None;
89 return dy > 0.0 ? Direction::Up : Direction::Down;
90 }
91}
92
98std::pair<bool, std::array<Direction, 4>> getEdgeDirections( const QgsPolygon *g, double maxDev )
99{
100 std::pair<bool, std::array<Direction, 4>> ret = { false, { Direction::None, Direction::None, Direction::None, Direction::None } };
101 // The polygon might start in the middle of a side. Hence, we need a fifth
102 // direction to record the beginning of the side when we went around the
103 // polygon.
104 std::array<Direction, 5> dirs;
105
106 int idx = 0;
108 QgsAbstractGeometry::vertex_iterator current = previous;
109 ++current;
111 while ( current != end )
112 {
113 Direction dir = getEdgeDirection( *previous, *current, maxDev );
114 if ( dir == Direction::None )
115 return ret;
116 if ( idx == 0 )
117 {
118 dirs[0] = dir;
119 ++idx;
120 }
121 else if ( dir != dirs[idx - 1] )
122 {
123 if ( idx == 5 )
124 return ret;
125 dirs[idx] = dir;
126 ++idx;
127 }
128 previous = current;
129 ++current;
130 }
131 ret.first = ( idx == 5 ) ? ( dirs[0] == dirs[4] ) : ( idx == 4 );
132 std::copy( dirs.begin(), dirs.begin() + 4, ret.second.begin() );
133 return ret;
134}
135
136bool matchesOrientation( std::array<Direction, 4> dirs, std::array<Direction, 4> oriented )
137{
138 int idx = std::find( oriented.begin(), oriented.end(), dirs[0] ) - oriented.begin();
139 for ( int i = 1; i < 4; ++i )
140 {
141 if ( dirs[i] != oriented[( idx + i ) % 4] )
142 return false;
143 }
144 return true;
145}
146
150bool isClockwise( std::array<Direction, 4> dirs )
151{
152 const std::array<Direction, 4> cwdirs = { Direction::Up, Direction::Right, Direction::Down, Direction::Left };
153 return matchesOrientation( dirs, cwdirs );
154}
155
160bool isCounterClockwise( std::array<Direction, 4> dirs )
161{
162 const std::array<Direction, 4> ccwdirs = { Direction::Right, Direction::Up, Direction::Left, Direction::Down };
163 return matchesOrientation( dirs, ccwdirs );
164}
165
166
167bool QgsInternalGeometryEngine::isAxisParallelRectangle( double maximumDeviation, bool simpleRectanglesOnly ) const
168{
169 if ( QgsWkbTypes::flatType( mGeometry->wkbType() ) != Qgis::WkbType::Polygon )
170 return false;
171
172 const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( mGeometry );
173 if ( !polygon->exteriorRing() || polygon->numInteriorRings() > 0 )
174 return false;
175
176 const int vertexCount = polygon->exteriorRing()->numPoints();
177 if ( vertexCount < 4 )
178 return false;
179 else if ( simpleRectanglesOnly && ( vertexCount != 5 || !polygon->exteriorRing()->isClosed() ) )
180 return false;
181
182 bool found4Dirs;
183 std::array<Direction, 4> dirs;
184 std::tie( found4Dirs, dirs ) = getEdgeDirections( polygon, std::tan( maximumDeviation * M_PI / 180 ) );
185
186 return found4Dirs && ( isCounterClockwise( dirs ) || isClockwise( dirs ) );
187}
188
189/***************************************************************************
190 * This class is considered CRITICAL and any change MUST be accompanied with
191 * full unit tests.
192 * See details in QEP #17
193 ****************************************************************************/
194
196{
197 mLastError.clear();
198 QVector<QgsLineString *> linesToProcess;
199
200 const QgsMultiCurve *multiCurve = qgsgeometry_cast< const QgsMultiCurve * >( mGeometry );
201 if ( multiCurve )
202 {
203 linesToProcess.reserve( multiCurve->partCount() );
204 for ( int i = 0; i < multiCurve->partCount(); ++i )
205 {
206 linesToProcess << static_cast<QgsLineString *>( multiCurve->geometryN( i )->clone() );
207 }
208 }
209
210 const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( mGeometry );
211 if ( curve )
212 {
213 linesToProcess << static_cast<QgsLineString *>( curve->segmentize() );
214 }
215
216 std::unique_ptr<QgsMultiPolygon> multipolygon( linesToProcess.size() > 1 ? new QgsMultiPolygon() : nullptr );
217 QgsPolygon *polygon = nullptr;
218
219 if ( !linesToProcess.empty() )
220 {
221 std::unique_ptr< QgsLineString > secondline;
222 for ( QgsLineString *line : std::as_const( linesToProcess ) )
223 {
224 QTransform transform = QTransform::fromTranslate( x, y );
225
226 secondline.reset( line->reversed() );
227 secondline->transform( transform );
228
229 line->append( secondline.get() );
230 line->addVertex( line->pointN( 0 ) );
231
232 polygon = new QgsPolygon();
233 polygon->setExteriorRing( line );
234
235 if ( multipolygon )
236 multipolygon->addGeometry( polygon );
237 }
238
239 if ( multipolygon )
240 return QgsGeometry( multipolygon.release() );
241 else
242 return QgsGeometry( polygon );
243 }
244
245 return QgsGeometry();
246}
247
248
249
250// polylabel implementation
251// ported from the original JavaScript implementation developed by Vladimir Agafonkin
252// originally licensed under the ISC License
253
255class Cell
256{
257 public:
258 Cell( double x, double y, double h, const QgsPolygon *polygon )
259 : x( x )
260 , y( y )
261 , h( h )
262 , d( polygon->pointDistanceToBoundary( x, y ) )
263 , max( d + h * M_SQRT2 )
264 {}
265
267 double x;
269 double y;
271 double h;
273 double d;
275 double max;
276};
277
278struct GreaterThanByMax
279{
280 bool operator()( const Cell *lhs, const Cell *rhs ) const
281 {
282 return rhs->max > lhs->max;
283 }
284};
285
286Cell *getCentroidCell( const QgsPolygon *polygon )
287{
288 double area = 0;
289 double x = 0;
290 double y = 0;
291
292 const QgsLineString *exterior = static_cast< const QgsLineString *>( polygon->exteriorRing() );
293 int len = exterior->numPoints() - 1; //assume closed
294 for ( int i = 0, j = len - 1; i < len; j = i++ )
295 {
296 double aX = exterior->xAt( i );
297 double aY = exterior->yAt( i );
298 double bX = exterior->xAt( j );
299 double bY = exterior->yAt( j );
300 double f = aX * bY - bX * aY;
301 x += ( aX + bX ) * f;
302 y += ( aY + bY ) * f;
303 area += f * 3;
304 }
305 if ( area == 0 )
306 return new Cell( exterior->xAt( 0 ), exterior->yAt( 0 ), 0, polygon );
307 else
308 return new Cell( x / area, y / area, 0.0, polygon );
309}
310
311QgsPoint surfacePoleOfInaccessibility( const QgsSurface *surface, double precision, double &distanceFromBoundary )
312{
313 std::unique_ptr< QgsPolygon > segmentizedPoly;
314 const QgsPolygon *polygon = qgsgeometry_cast< const QgsPolygon * >( surface );
315 if ( !polygon )
316 {
317 segmentizedPoly.reset( static_cast< QgsPolygon *>( surface->segmentize() ) );
318 polygon = segmentizedPoly.get();
319 }
320
321 // start with the bounding box
322 QgsRectangle bounds = polygon->boundingBox();
323
324 // initial parameters
325 double cellSize = std::min( bounds.width(), bounds.height() );
326
327 if ( qgsDoubleNear( cellSize, 0.0 ) )
328 return QgsPoint( bounds.xMinimum(), bounds.yMinimum() );
329
330 double h = cellSize / 2.0;
331 std::priority_queue< Cell *, std::vector<Cell *>, GreaterThanByMax > cellQueue;
332
333 // cover polygon with initial cells
334 for ( double x = bounds.xMinimum(); x < bounds.xMaximum(); x += cellSize )
335 {
336 for ( double y = bounds.yMinimum(); y < bounds.yMaximum(); y += cellSize )
337 {
338 cellQueue.push( new Cell( x + h, y + h, h, polygon ) );
339 }
340 }
341
342 // take centroid as the first best guess
343 std::unique_ptr< Cell > bestCell( getCentroidCell( polygon ) );
344
345 // special case for rectangular polygons
346 std::unique_ptr< Cell > bboxCell( new Cell( bounds.xMinimum() + bounds.width() / 2.0,
347 bounds.yMinimum() + bounds.height() / 2.0,
348 0, polygon ) );
349 if ( bboxCell->d > bestCell->d )
350 {
351 bestCell = std::move( bboxCell );
352 }
353
354 while ( !cellQueue.empty() )
355 {
356 // pick the most promising cell from the queue
357 std::unique_ptr< Cell > cell( cellQueue.top() );
358 cellQueue.pop();
359 Cell *currentCell = cell.get();
360
361 // update the best cell if we found a better one
362 if ( currentCell->d > bestCell->d )
363 {
364 bestCell = std::move( cell );
365 }
366
367 // do not drill down further if there's no chance of a better solution
368 if ( currentCell->max - bestCell->d <= precision )
369 continue;
370
371 // split the cell into four cells
372 h = currentCell->h / 2.0;
373 cellQueue.push( new Cell( currentCell->x - h, currentCell->y - h, h, polygon ) );
374 cellQueue.push( new Cell( currentCell->x + h, currentCell->y - h, h, polygon ) );
375 cellQueue.push( new Cell( currentCell->x - h, currentCell->y + h, h, polygon ) );
376 cellQueue.push( new Cell( currentCell->x + h, currentCell->y + h, h, polygon ) );
377 }
378
379 distanceFromBoundary = bestCell->d;
380
381 return QgsPoint( bestCell->x, bestCell->y );
382}
383
385
386QgsGeometry QgsInternalGeometryEngine::poleOfInaccessibility( double precision, double *distanceFromBoundary ) const
387{
388 mLastError.clear();
389 if ( distanceFromBoundary )
390 *distanceFromBoundary = std::numeric_limits<double>::max();
391
392 if ( !mGeometry || mGeometry->isEmpty() )
393 return QgsGeometry();
394
395 if ( precision <= 0 )
396 return QgsGeometry();
397
399 {
400 int numGeom = gc->numGeometries();
401 double maxDist = 0;
402 QgsPoint bestPoint;
403 for ( int i = 0; i < numGeom; ++i )
404 {
405 const QgsSurface *surface = qgsgeometry_cast< const QgsSurface * >( gc->geometryN( i ) );
406 if ( !surface )
407 continue;
408
409 double dist = std::numeric_limits<double>::max();
410 QgsPoint p = surfacePoleOfInaccessibility( surface, precision, dist );
411 if ( dist > maxDist )
412 {
413 maxDist = dist;
414 bestPoint = p;
415 }
416 }
417
418 if ( bestPoint.isEmpty() )
419 return QgsGeometry();
420
421 if ( distanceFromBoundary )
422 *distanceFromBoundary = maxDist;
423 return QgsGeometry( new QgsPoint( bestPoint ) );
424 }
425 else
426 {
427 const QgsSurface *surface = qgsgeometry_cast< const QgsSurface * >( mGeometry );
428 if ( !surface )
429 return QgsGeometry();
430
431 double dist = std::numeric_limits<double>::max();
432 QgsPoint p = surfacePoleOfInaccessibility( surface, precision, dist );
433 if ( p.isEmpty() )
434 return QgsGeometry();
435
436 if ( distanceFromBoundary )
437 *distanceFromBoundary = dist;
438 return QgsGeometry( new QgsPoint( p ) );
439 }
440}
441
442
443// helpers for orthogonalize
444// adapted from original code in potlatch/id osm editor
445
446bool dotProductWithinAngleTolerance( double dotProduct, double lowerThreshold, double upperThreshold )
447{
448 return lowerThreshold > std::fabs( dotProduct ) || std::fabs( dotProduct ) > upperThreshold;
449}
450
451double normalizedDotProduct( const QgsPoint &a, const QgsPoint &b, const QgsPoint &c )
452{
453 QgsVector p = a - b;
454 QgsVector q = c - b;
455
456 if ( p.length() > 0 )
457 p = p.normalized();
458 if ( q.length() > 0 )
459 q = q.normalized();
460
461 return p * q;
462}
463
464double squareness( QgsLineString *ring, double lowerThreshold, double upperThreshold )
465{
466 double sum = 0.0;
467
468 bool isClosed = ring->isClosed();
469 int numPoints = ring->numPoints();
470 QgsPoint a;
471 QgsPoint b;
472 QgsPoint c;
473
474 for ( int i = 0; i < numPoints; ++i )
475 {
476 if ( !isClosed && i == numPoints - 1 )
477 break;
478 else if ( !isClosed && i == 0 )
479 {
480 b = ring->pointN( 0 );
481 c = ring->pointN( 1 );
482 }
483 else
484 {
485 if ( i == 0 )
486 {
487 a = ring->pointN( numPoints - 1 );
488 b = ring->pointN( 0 );
489 }
490 if ( i == numPoints - 1 )
491 c = ring->pointN( 0 );
492 else
493 c = ring->pointN( i + 1 );
494
495 double dotProduct = normalizedDotProduct( a, b, c );
496 if ( !dotProductWithinAngleTolerance( dotProduct, lowerThreshold, upperThreshold ) )
497 continue;
498
499 sum += 2.0 * std::min( std::fabs( dotProduct - 1.0 ), std::min( std::fabs( dotProduct ), std::fabs( dotProduct + 1 ) ) );
500 }
501 a = b;
502 b = c;
503 }
504
505 return sum;
506}
507
508QgsVector calcMotion( const QgsPoint &a, const QgsPoint &b, const QgsPoint &c,
509 double lowerThreshold, double upperThreshold )
510{
511 QgsVector p = a - b;
512 QgsVector q = c - b;
513
514 if ( qgsDoubleNear( p.length(), 0.0 ) || qgsDoubleNear( q.length(), 0.0 ) )
515 return QgsVector( 0, 0 );
516
517 // 2.0 is a magic number from the original JOSM source code
518 double scale = 2.0 * std::min( p.length(), q.length() );
519
520 p = p.normalized();
521 q = q.normalized();
522 double dotProduct = p * q;
523
524 if ( !dotProductWithinAngleTolerance( dotProduct, lowerThreshold, upperThreshold ) )
525 {
526 return QgsVector( 0, 0 );
527 }
528
529 // wonderful nasty hack which has survived through JOSM -> id -> QGIS
530 // to deal with almost-straight segments (angle is closer to 180 than to 90/270).
531 if ( dotProduct < -M_SQRT1_2 )
532 dotProduct += 1.0;
533
534 QgsVector new_v = p + q;
535 if ( qgsDoubleNear( new_v.length(), 0.0 ) )
536 {
537 return QgsVector( 0, 0 );
538 }
539 // 0.1 magic number from JOSM implementation - think this is to limit each iterative step
540 return new_v.normalized() * ( 0.1 * dotProduct * scale );
541}
542
543QgsLineString *doOrthogonalize( QgsLineString *ring, int iterations, double tolerance, double lowerThreshold, double upperThreshold )
544{
545 double minScore = std::numeric_limits<double>::max();
546
547 bool isClosed = ring->isClosed();
548 int numPoints = ring->numPoints();
549
550 std::unique_ptr< QgsLineString > best( ring->clone() );
551
552 QVector< QgsVector > /* yep */ motions;
553 motions.reserve( numPoints );
554
555 for ( int it = 0; it < iterations; ++it )
556 {
557 motions.resize( 0 ); // avoid re-allocations
558
559 // first loop through an calculate all motions
560 QgsPoint a;
561 QgsPoint b;
562 QgsPoint c;
563 for ( int i = 0; i < numPoints; ++i )
564 {
565 if ( isClosed && i == numPoints - 1 )
566 motions << motions.at( 0 ); //closed ring, so last point follows first point motion
567 else if ( !isClosed && ( i == 0 || i == numPoints - 1 ) )
568 {
569 b = ring->pointN( 0 );
570 c = ring->pointN( 1 );
571 motions << QgsVector( 0, 0 ); //non closed line, leave first/last vertex untouched
572 }
573 else
574 {
575 if ( i == 0 )
576 {
577 a = ring->pointN( numPoints - 1 );
578 b = ring->pointN( 0 );
579 }
580 if ( i == numPoints - 1 )
581 c = ring->pointN( 0 );
582 else
583 c = ring->pointN( i + 1 );
584
585 motions << calcMotion( a, b, c, lowerThreshold, upperThreshold );
586 }
587 a = b;
588 b = c;
589 }
590
591 // then apply them
592 for ( int i = 0; i < numPoints; ++i )
593 {
594 ring->setXAt( i, ring->xAt( i ) + motions.at( i ).x() );
595 ring->setYAt( i, ring->yAt( i ) + motions.at( i ).y() );
596 }
597
598 double newScore = squareness( ring, lowerThreshold, upperThreshold );
599 if ( newScore < minScore )
600 {
601 best.reset( ring->clone() );
602 minScore = newScore;
603 }
604
605 if ( minScore < tolerance )
606 break;
607 }
608
609 delete ring;
610
611 return best.release();
612}
613
614
615QgsAbstractGeometry *orthogonalizeGeom( const QgsAbstractGeometry *geom, int maxIterations, double tolerance, double lowerThreshold, double upperThreshold )
616{
617 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
618 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
619 {
620 segmentizedCopy.reset( geom->segmentize() );
621 geom = segmentizedCopy.get();
622 }
623
625 {
626 return doOrthogonalize( static_cast< QgsLineString * >( geom->clone() ),
627 maxIterations, tolerance, lowerThreshold, upperThreshold );
628 }
629 else
630 {
631 // polygon
632 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
633 QgsPolygon *result = new QgsPolygon();
634
635 result->setExteriorRing( doOrthogonalize( static_cast< QgsLineString * >( polygon->exteriorRing()->clone() ),
636 maxIterations, tolerance, lowerThreshold, upperThreshold ) );
637 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
638 {
639 result->addInteriorRing( doOrthogonalize( static_cast< QgsLineString * >( polygon->interiorRing( i )->clone() ),
640 maxIterations, tolerance, lowerThreshold, upperThreshold ) );
641 }
642
643 return result;
644 }
645}
646
647QgsGeometry QgsInternalGeometryEngine::orthogonalize( double tolerance, int maxIterations, double angleThreshold ) const
648{
649 mLastError.clear();
650 if ( !mGeometry || ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) != Qgis::GeometryType::Line
651 && QgsWkbTypes::geometryType( mGeometry->wkbType() ) != Qgis::GeometryType::Polygon ) )
652 {
653 return QgsGeometry();
654 }
655
656 double lowerThreshold = std::cos( ( 90 - angleThreshold ) * M_PI / 180.00 );
657 double upperThreshold = std::cos( angleThreshold * M_PI / 180.0 );
658
660 {
661 int numGeom = gc->numGeometries();
662 QVector< QgsAbstractGeometry * > geometryList;
663 geometryList.reserve( numGeom );
664 for ( int i = 0; i < numGeom; ++i )
665 {
666 geometryList << orthogonalizeGeom( gc->geometryN( i ), maxIterations, tolerance, lowerThreshold, upperThreshold );
667 }
668
669 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
670 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
671 {
672 first.addPartV2( g );
673 }
674 return first;
675 }
676 else
677 {
678 return QgsGeometry( orthogonalizeGeom( mGeometry, maxIterations, tolerance, lowerThreshold, upperThreshold ) );
679 }
680}
681
682// if extraNodesPerSegment < 0, then use distance based mode
683QgsLineString *doDensify( const QgsLineString *ring, int extraNodesPerSegment = -1, double distance = 1 )
684{
685 QVector< double > outX;
686 QVector< double > outY;
687 QVector< double > outZ;
688 QVector< double > outM;
689 double multiplier = 1.0 / double( extraNodesPerSegment + 1 );
690
691 int nPoints = ring->numPoints();
692 outX.reserve( ( extraNodesPerSegment + 1 ) * nPoints );
693 outY.reserve( ( extraNodesPerSegment + 1 ) * nPoints );
694 bool withZ = ring->is3D();
695 if ( withZ )
696 outZ.reserve( ( extraNodesPerSegment + 1 ) * nPoints );
697 bool withM = ring->isMeasure();
698 if ( withM )
699 outM.reserve( ( extraNodesPerSegment + 1 ) * nPoints );
700 double x1 = 0;
701 double x2 = 0;
702 double y1 = 0;
703 double y2 = 0;
704 double z1 = 0;
705 double z2 = 0;
706 double m1 = 0;
707 double m2 = 0;
708 double xOut = 0;
709 double yOut = 0;
710 double zOut = 0;
711 double mOut = 0;
712 int extraNodesThisSegment = extraNodesPerSegment;
713 for ( int i = 0; i < nPoints - 1; ++i )
714 {
715 x1 = ring->xAt( i );
716 x2 = ring->xAt( i + 1 );
717 y1 = ring->yAt( i );
718 y2 = ring->yAt( i + 1 );
719 if ( withZ )
720 {
721 z1 = ring->zAt( i );
722 z2 = ring->zAt( i + 1 );
723 }
724 if ( withM )
725 {
726 m1 = ring->mAt( i );
727 m2 = ring->mAt( i + 1 );
728 }
729
730 outX << x1;
731 outY << y1;
732 if ( withZ )
733 outZ << z1;
734 if ( withM )
735 outM << m1;
736
737 if ( extraNodesPerSegment < 0 )
738 {
739 // distance mode
740 extraNodesThisSegment = std::floor( QgsGeometryUtilsBase::distance2D( x1, y1, x2, y2 ) / distance );
741 if ( extraNodesThisSegment >= 1 )
742 multiplier = 1.0 / ( extraNodesThisSegment + 1 );
743 }
744
745 for ( int j = 0; j < extraNodesThisSegment; ++j )
746 {
747 double delta = multiplier * ( j + 1 );
748 xOut = x1 + delta * ( x2 - x1 );
749 yOut = y1 + delta * ( y2 - y1 );
750 if ( withZ )
751 zOut = z1 + delta * ( z2 - z1 );
752 if ( withM )
753 mOut = m1 + delta * ( m2 - m1 );
754
755 outX << xOut;
756 outY << yOut;
757 if ( withZ )
758 outZ << zOut;
759 if ( withM )
760 outM << mOut;
761 }
762 }
763 outX << ring->xAt( nPoints - 1 );
764 outY << ring->yAt( nPoints - 1 );
765 if ( withZ )
766 outZ << ring->zAt( nPoints - 1 );
767 if ( withM )
768 outM << ring->mAt( nPoints - 1 );
769
770 QgsLineString *result = new QgsLineString( outX, outY, outZ, outM );
771 return result;
772}
773
774QgsAbstractGeometry *densifyGeometry( const QgsAbstractGeometry *geom, int extraNodesPerSegment = 1, double distance = 1 )
775{
776 if ( extraNodesPerSegment < 0 && qgsDoubleNear( distance, 0 ) )
777 {
778 // no change
779 return geom->clone();
780 }
781
782 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
783 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
784 {
785 segmentizedCopy.reset( geom->segmentize() );
786 geom = segmentizedCopy.get();
787 }
788
790 {
791 return doDensify( static_cast< const QgsLineString * >( geom ), extraNodesPerSegment, distance );
792 }
793 else
794 {
795 // polygon
796 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
797 QgsPolygon *result = new QgsPolygon();
798
799 result->setExteriorRing( doDensify( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
800 extraNodesPerSegment, distance ) );
801 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
802 {
803 result->addInteriorRing( doDensify( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
804 extraNodesPerSegment, distance ) );
805 }
806
807 return result;
808 }
809}
810
812{
813 mLastError.clear();
814 if ( !mGeometry )
815 {
816 return QgsGeometry();
817 }
818
819 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == Qgis::GeometryType::Point )
820 {
821 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
822 }
823
825 {
826 int numGeom = gc->numGeometries();
827 QVector< QgsAbstractGeometry * > geometryList;
828 geometryList.reserve( numGeom );
829 for ( int i = 0; i < numGeom; ++i )
830 {
831 geometryList << densifyGeometry( gc->geometryN( i ), extraNodesPerSegment );
832 }
833
834 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
835 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
836 {
837 first.addPartV2( g );
838 }
839 return first;
840 }
841 else
842 {
843 return QgsGeometry( densifyGeometry( mGeometry, extraNodesPerSegment ) );
844 }
845}
846
848{
849 mLastError.clear();
850 if ( !mGeometry )
851 {
852 return QgsGeometry();
853 }
854
855 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == Qgis::GeometryType::Point || qgsDoubleNear( distance, 0 ) )
856 {
857 return QgsGeometry( mGeometry->clone() ); // point geometry (or distance ~= 0), nothing to do
858 }
859
861 {
862 int numGeom = gc->numGeometries();
863 QVector< QgsAbstractGeometry * > geometryList;
864 geometryList.reserve( numGeom );
865 for ( int i = 0; i < numGeom; ++i )
866 {
867 geometryList << densifyGeometry( gc->geometryN( i ), -1, distance );
868 }
869
870 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
871 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
872 {
873 first.addPartV2( g );
874 }
875 return first;
876 }
877 else
878 {
879 return QgsGeometry( densifyGeometry( mGeometry, -1, distance ) );
880 }
881}
882
884//
885// QgsLineSegmentDistanceComparer
886//
887
888// adapted for QGIS geometry classes from original work at https://github.com/trylock/visibility by trylock
889bool QgsLineSegmentDistanceComparer::operator()( QgsLineSegment2D ab, QgsLineSegment2D cd ) const
890{
891 Q_ASSERT_X( ab.pointLeftOfLine( mOrigin ) != 0,
892 "line_segment_dist_comparer",
893 "AB must not be collinear with the origin." );
894 Q_ASSERT_X( cd.pointLeftOfLine( mOrigin ) != 0,
895 "line_segment_dist_comparer",
896 "CD must not be collinear with the origin." );
897
898 // flip the segments so that if there are common endpoints,
899 // they will be the segment's start points
900 // cppcheck-suppress mismatchingContainerExpression
901 if ( ab.end() == cd.start() || ab.end() == cd.end() )
902 ab.reverse();
903 // cppcheck-suppress mismatchingContainerExpression
904 if ( ab.start() == cd.end() )
905 cd.reverse();
906
907 // cases with common endpoints
908 if ( ab.start() == cd.start() )
909 {
910 const int oad = QgsGeometryUtilsBase::leftOfLine( cd.endX(), cd.endY(), mOrigin.x(), mOrigin.y(), ab.startX(), ab.startY() );
911 const int oab = ab.pointLeftOfLine( mOrigin );
912 // cppcheck-suppress mismatchingContainerExpression
913 if ( ab.end() == cd.end() || oad != oab )
914 return false;
915 else
916 return ab.pointLeftOfLine( cd.end() ) != oab;
917 }
918 else
919 {
920 // cases without common endpoints
921 const int cda = cd.pointLeftOfLine( ab.start() );
922 const int cdb = cd.pointLeftOfLine( ab.end() );
923 if ( cdb == 0 && cda == 0 )
924 {
925 return mOrigin.sqrDist( ab.start() ) < mOrigin.sqrDist( cd.start() );
926 }
927 else if ( cda == cdb || cda == 0 || cdb == 0 )
928 {
929 const int cdo = cd.pointLeftOfLine( mOrigin );
930 return cdo == cda || cdo == cdb;
931 }
932 else
933 {
934 const int abo = ab.pointLeftOfLine( mOrigin );
935 return abo != ab.pointLeftOfLine( cd.start() );
936 }
937 }
938}
939
940//
941// QgsClockwiseAngleComparer
942//
943
944bool QgsClockwiseAngleComparer::operator()( const QgsPointXY &a, const QgsPointXY &b ) const
945{
946 const bool aIsLeft = a.x() < mVertex.x();
947 const bool bIsLeft = b.x() < mVertex.x();
948 if ( aIsLeft != bIsLeft )
949 return bIsLeft;
950
951 if ( qgsDoubleNear( a.x(), mVertex.x() ) && qgsDoubleNear( b.x(), mVertex.x() ) )
952 {
953 if ( a.y() >= mVertex.y() || b.y() >= mVertex.y() )
954 {
955 return b.y() < a.y();
956 }
957 else
958 {
959 return a.y() < b.y();
960 }
961 }
962 else
963 {
964 const QgsVector oa = a - mVertex;
965 const QgsVector ob = b - mVertex;
966 const double det = oa.crossProduct( ob );
967 if ( qgsDoubleNear( det, 0.0 ) )
968 {
969 return oa.lengthSquared() < ob.lengthSquared();
970 }
971 else
972 {
973 return det < 0;
974 }
975 }
976}
977
979
980//
981// QgsRay2D
982//
983
984bool QgsRay2D::intersects( const QgsLineSegment2D &segment, QgsPointXY &intersectPoint ) const
985{
986 const QgsVector ao = origin - segment.start();
987 const QgsVector ab = segment.end() - segment.start();
988 const double det = ab.crossProduct( direction );
989 if ( qgsDoubleNear( det, 0.0 ) )
990 {
991 const int abo = segment.pointLeftOfLine( origin );
992 if ( abo != 0 )
993 {
994 return false;
995 }
996 else
997 {
998 const double distA = ao * direction;
999 const double distB = ( origin - segment.end() ) * direction;
1000
1001 if ( distA > 0 && distB > 0 )
1002 {
1003 return false;
1004 }
1005 else
1006 {
1007 if ( ( distA > 0 ) != ( distB > 0 ) )
1008 intersectPoint = origin;
1009 else if ( distA > distB ) // at this point, both distances are negative
1010 intersectPoint = segment.start(); // hence the nearest point is A
1011 else
1012 intersectPoint = segment.end();
1013 return true;
1014 }
1015 }
1016 }
1017 else
1018 {
1019 const double u = ao.crossProduct( direction ) / det;
1020 if ( u < 0.0 || 1.0 < u )
1021 {
1022 return false;
1023 }
1024 else
1025 {
1026 const double t = -ab.crossProduct( ao ) / det;
1027 intersectPoint = origin + direction * t;
1028 return qgsDoubleNear( t, 0.0 ) || t > 0;
1029 }
1030 }
1031}
1032
1033QVector<QgsPointXY> generateSegmentCurve( const QgsPoint &center1, const double radius1, const QgsPoint &center2, const double radius2 )
1034{
1035 // ensure that first circle is smaller than second
1036 if ( radius1 > radius2 )
1037 return generateSegmentCurve( center2, radius2, center1, radius1 );
1038
1039 QgsPointXY t1;
1040 QgsPointXY t2;
1041 QgsPointXY t3;
1042 QgsPointXY t4;
1043 QVector<QgsPointXY> points;
1044 if ( QgsGeometryUtils::circleCircleOuterTangents( center1, radius1, center2, radius2, t1, t2, t3, t4 ) )
1045 {
1046 points << t1
1047 << t2
1048 << t4
1049 << t3;
1050 }
1051 return points;
1052}
1053
1054// partially ported from JTS VariableWidthBuffer,
1055// https://github.com/topobyte/jts/blob/master/jts-lab/src/main/java/com/vividsolutions/jts/operation/buffer/VariableWidthBuffer.java
1056
1057QgsGeometry QgsInternalGeometryEngine::variableWidthBuffer( int segments, const std::function< std::unique_ptr< double[] >( const QgsLineString *line ) > &widthFunction ) const
1058{
1059 mLastError.clear();
1060 if ( !mGeometry )
1061 {
1062 return QgsGeometry();
1063 }
1064
1065 std::vector< std::unique_ptr<QgsLineString > > temporarySegmentizedLines;
1066 std::vector< const QgsLineString * > linesToProcess;
1067 const QgsAbstractGeometry *simplifiedGeom = mGeometry->simplifiedTypeRef();
1068
1069 if ( const QgsMultiCurve *multiCurve = qgsgeometry_cast< const QgsMultiCurve * >( simplifiedGeom ) )
1070 {
1071 for ( int i = 0; i < multiCurve->partCount(); ++i )
1072 {
1073 if ( const QgsCurve *curvePart = qgsgeometry_cast< const QgsCurve * >( multiCurve->geometryN( i ) ) )
1074 {
1075 const QgsAbstractGeometry *part = curvePart->simplifiedTypeRef();
1076 if ( part->nCoordinates() == 0 )
1077 continue; // skip 0 length lines
1078
1079 if ( const QgsLineString *lineString = qgsgeometry_cast< const QgsLineString * >( part ) )
1080 {
1081 linesToProcess.emplace_back( lineString );
1082 }
1083 else
1084 {
1085 std::unique_ptr< QgsLineString > segmentizedCurve( qgis::down_cast<QgsLineString *>( part->segmentize() ) );
1086 linesToProcess.emplace_back( segmentizedCurve.get() );
1087 temporarySegmentizedLines.emplace_back( std::move( segmentizedCurve ) );
1088 }
1089 }
1090 }
1091 }
1092 else if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( simplifiedGeom ) )
1093 {
1094 if ( curve->nCoordinates() > 0 )
1095 {
1096 if ( const QgsLineString *lineString = qgsgeometry_cast< const QgsLineString * >( curve ) )
1097 {
1098 linesToProcess.emplace_back( lineString );
1099 }
1100 else
1101 {
1102 std::unique_ptr< QgsLineString > segmentizedCurve( qgis::down_cast<QgsLineString *>( curve->segmentize() ) );
1103 linesToProcess.emplace_back( segmentizedCurve.get() );
1104 temporarySegmentizedLines.emplace_back( std::move( segmentizedCurve ) );
1105 }
1106 }
1107 }
1108
1109 if ( linesToProcess.empty() )
1110 {
1111 QgsGeometry g;
1112 g.mLastError = u"Input geometry was not a curve type geometry"_s;
1113 return g;
1114 }
1115
1116 QVector<QgsGeometry> bufferedLines;
1117 bufferedLines.reserve( linesToProcess.size() );
1118
1119 for ( const QgsLineString *line : linesToProcess )
1120 {
1121 QVector<QgsGeometry> parts;
1122 QgsPoint prevPoint;
1123 double prevRadius = 0;
1124 QgsGeometry prevCircle;
1125
1126 std::unique_ptr< double[] > widths = widthFunction( line ) ;
1127 for ( int i = 0; i < line->nCoordinates(); ++i )
1128 {
1129 QgsPoint thisPoint = line->pointN( i );
1130 QgsGeometry thisCircle;
1131 double thisRadius = widths[ i ] / 2.0;
1132 if ( thisRadius > 0 )
1133 {
1134 QgsGeometry p = QgsGeometry( thisPoint.clone() );
1135
1136 QgsCircle circ( thisPoint, thisRadius );
1137 thisCircle = QgsGeometry( circ.toPolygon( segments * 4 ) );
1138 parts << thisCircle;
1139 }
1140 else
1141 {
1142 thisCircle = QgsGeometry( thisPoint.clone() );
1143 }
1144
1145 if ( i > 0 )
1146 {
1147 if ( prevRadius > 0 || thisRadius > 0 )
1148 {
1149 QVector< QgsPointXY > points = generateSegmentCurve( prevPoint, prevRadius, thisPoint, thisRadius );
1150 if ( !points.empty() )
1151 {
1152 // snap points to circle vertices
1153
1154 int atVertex = 0;
1155 int beforeVertex = 0;
1156 int afterVertex = 0;
1157 double sqrDist = 0;
1158 double sqrDistPrev = 0;
1159 for ( int j = 0; j < points.count(); ++j )
1160 {
1161 QgsPointXY pA = prevCircle.closestVertex( points.at( j ), atVertex, beforeVertex, afterVertex, sqrDistPrev );
1162 QgsPointXY pB = thisCircle.closestVertex( points.at( j ), atVertex, beforeVertex, afterVertex, sqrDist );
1163 points[j] = sqrDistPrev < sqrDist ? pA : pB;
1164 }
1165 // close ring
1166 points.append( points.at( 0 ) );
1167
1168 auto poly = std::make_unique< QgsPolygon >();
1169 poly->setExteriorRing( new QgsLineString( points ) );
1170 if ( poly->area() > 0 )
1171 parts << QgsGeometry( std::move( poly ) );
1172 }
1173 }
1174 }
1175 prevPoint = thisPoint;
1176 prevRadius = thisRadius;
1177 prevCircle = thisCircle;
1178 }
1179
1180 bufferedLines << QgsGeometry::unaryUnion( parts );
1181 }
1182
1183 QgsGeometry res = QgsGeometry::collectGeometry( bufferedLines );
1184 // happens on some GEOS versions...
1186 return res;
1187}
1188
1189QgsGeometry QgsInternalGeometryEngine::taperedBuffer( double start, double end, int segments ) const
1190{
1191 mLastError.clear();
1192 start = std::fabs( start );
1193 end = std::fabs( end );
1194
1195 auto interpolateWidths = [ start, end ]( const QgsLineString * line )->std::unique_ptr< double [] >
1196 {
1197 // ported from JTS VariableWidthBuffer,
1198 // https://github.com/topobyte/jts/blob/master/jts-lab/src/main/java/com/vividsolutions/jts/operation/buffer/VariableWidthBuffer.java
1199 std::unique_ptr< double [] > widths( new double[ line->nCoordinates() ] );
1200 widths[0] = start;
1201 widths[line->nCoordinates() - 1] = end;
1202
1203 double lineLength = line->length();
1204 double currentLength = 0;
1205 QgsPoint prevPoint = line->pointN( 0 );
1206 for ( int i = 1; i < line->nCoordinates() - 1; ++i )
1207 {
1208 QgsPoint point = line->pointN( i );
1209 double segmentLength = point.distance( prevPoint );
1210 currentLength += segmentLength;
1211 double lengthFraction = lineLength > 0 ? currentLength / lineLength : 1;
1212 double delta = lengthFraction * ( end - start );
1213 widths[i] = start + delta;
1214 prevPoint = point;
1215 }
1216 return widths;
1217 };
1218
1219 return variableWidthBuffer( segments, interpolateWidths );
1220}
1221
1223{
1224 mLastError.clear();
1225 auto widthByM = []( const QgsLineString * line )->std::unique_ptr< double [] >
1226 {
1227 std::unique_ptr< double [] > widths( new double[ line->nCoordinates() ] );
1228 for ( int i = 0; i < line->nCoordinates(); ++i )
1229 {
1230 widths[ i ] = line->mAt( i );
1231 }
1232 return widths;
1233 };
1234
1235 return variableWidthBuffer( segments, widthByM );
1236}
1237
1238QVector<QgsPointXY> randomPointsInPolygonPoly2TriBackend( const QgsAbstractGeometry *geometry, int count,
1239 const std::function< bool( const QgsPointXY & ) > &acceptPoint, unsigned long seed, QgsFeedback *feedback, int maxTriesPerPoint, QString &error )
1240{
1241 // step 1 - tessellate the polygon to triangles
1242 QgsRectangle bounds = geometry->boundingBox();
1244 t.setBounds( bounds );
1245 t.setInputZValueIgnored( true );
1246
1248 {
1249 for ( int i = 0; i < ms->numGeometries(); ++i )
1250 {
1251 if ( feedback && feedback->isCanceled() )
1252 return QVector< QgsPointXY >();
1253
1254 if ( const QgsPolygon *poly = qgsgeometry_cast< const QgsPolygon * >( ms->geometryN( i ) ) )
1255 {
1256 t.addPolygon( *poly, 0 );
1257 }
1258 else
1259 {
1260 std::unique_ptr< QgsPolygon > p( qgsgeometry_cast< QgsPolygon * >( ms->geometryN( i )->segmentize() ) );
1261 t.addPolygon( *p, 0 );
1262 }
1263 }
1264 }
1265 else
1266 {
1267 if ( const QgsPolygon *poly = qgsgeometry_cast< const QgsPolygon * >( geometry ) )
1268 {
1269 t.addPolygon( *poly, 0 );
1270 }
1271 else
1272 {
1273 std::unique_ptr< QgsPolygon > p( qgsgeometry_cast< QgsPolygon * >( geometry->segmentize() ) );
1274 t.addPolygon( *p, 0 );
1275 }
1276 }
1277
1278 if ( feedback && feedback->isCanceled() )
1279 return QVector< QgsPointXY >();
1280
1281 if ( !t.error().isEmpty() )
1282 {
1283 error = t.error();
1284 return QVector< QgsPointXY >();
1285 }
1286
1287 // tessellator data() method can be removed when minimum GEOS version is 3.11 or above
1288 QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
1289 const QVector<float> triangleData = t.data();
1290 QT_WARNING_POP
1291 if ( triangleData.empty() )
1292 return QVector< QgsPointXY >(); //hm
1293
1294 // calculate running sum of triangle areas
1295 std::vector< double > cumulativeAreas;
1296 cumulativeAreas.reserve( t.dataVerticesCount() / 3 );
1297 double totalArea = 0;
1298 for ( auto it = triangleData.constBegin(); it != triangleData.constEnd(); )
1299 {
1300 if ( feedback && feedback->isCanceled() )
1301 return QVector< QgsPointXY >();
1302
1303 const float aX = *it++;
1304 const float aY = *it++;
1305 ( void )it++; // z
1306 const float bX = *it++;
1307 const float bY = *it++;
1308 ( void )it++; // z
1309 const float cX = *it++;
1310 const float cY = *it++;
1311 ( void )it++; // z
1312
1313 const double area = QgsGeometryUtilsBase::triangleArea( aX, aY, bX, bY, cX, cY );
1314 totalArea += area;
1315 cumulativeAreas.emplace_back( totalArea );
1316 }
1317
1318 std::random_device rd;
1319 std::mt19937 mt( seed == 0 ? rd() : seed );
1320 std::uniform_real_distribution<> uniformDist( 0, 1 );
1321
1322 // selects a random triangle, weighted by triangle area
1323 auto selectRandomTriangle = [&cumulativeAreas, totalArea]( double random )->int
1324 {
1325 int triangle = 0;
1326 const double target = random * totalArea;
1327 for ( auto it = cumulativeAreas.begin(); it != cumulativeAreas.end(); ++it, triangle++ )
1328 {
1329 if ( *it > target )
1330 return triangle;
1331 }
1332 Q_ASSERT_X( false, "QgsInternalGeometryEngine::randomPointsInPolygon", "Invalid random triangle index" );
1333 return 0; // no warnings
1334 };
1335
1336
1337 QVector<QgsPointXY> result;
1338 result.reserve( count );
1339 int tries = 0;
1340 for ( int i = 0; i < count; )
1341 {
1342 if ( feedback && feedback->isCanceled() )
1343 return QVector< QgsPointXY >();
1344
1345 const double triangleIndexRnd = uniformDist( mt );
1346 // pick random triangle, weighted by triangle area
1347 const int triangleIndex = selectRandomTriangle( triangleIndexRnd );
1348
1349 // generate a random point inside this triangle
1350 const double weightB = uniformDist( mt );
1351 const double weightC = uniformDist( mt );
1352 double x;
1353 double y;
1354
1355 // get triangle
1356 const double aX = triangleData.at( triangleIndex * 9 ) + bounds.xMinimum();
1357 const double aY = triangleData.at( triangleIndex * 9 + 1 ) + bounds.yMinimum();
1358 const double bX = triangleData.at( triangleIndex * 9 + 3 ) + bounds.xMinimum();
1359 const double bY = triangleData.at( triangleIndex * 9 + 4 ) + bounds.yMinimum();
1360 const double cX = triangleData.at( triangleIndex * 9 + 6 ) + bounds.xMinimum();
1361 const double cY = triangleData.at( triangleIndex * 9 + 7 ) + bounds.yMinimum();
1362 QgsGeometryUtilsBase::weightedPointInTriangle( aX, aY, bX, bY, cX, cY, weightB, weightC, x, y );
1363
1364 QgsPointXY candidate( x, y );
1365 if ( acceptPoint( candidate ) )
1366 {
1367 result << QgsPointXY( x, y );
1368 i++;
1369 tries = 0;
1370 }
1371 else if ( maxTriesPerPoint != 0 )
1372 {
1373 tries++;
1374 // Skip this point if maximum tries is reached
1375 if ( tries == maxTriesPerPoint )
1376 {
1377 tries = 0;
1378 i++;
1379 }
1380 }
1381 }
1382 return result;
1383}
1384
1385QVector<QgsPointXY> randomPointsInPolygonGeosBackend( const QgsAbstractGeometry *geometry, int count,
1386 const std::function< bool( const QgsPointXY & ) > &acceptPoint, unsigned long seed, QgsFeedback *feedback, int maxTriesPerPoint, QString &error )
1387{
1388 // step 1 - tessellate the polygon to triangles
1389 QgsGeos geos( geometry );
1390 std::unique_ptr<QgsAbstractGeometry> triangulation = geos.constrainedDelaunayTriangulation( &error );
1391 if ( !triangulation || triangulation->isEmpty( ) )
1392 return {};
1393
1394 if ( feedback && feedback->isCanceled() )
1395 return {};
1396
1397 const QgsMultiPolygon *mp = qgsgeometry_cast< const QgsMultiPolygon * >( triangulation.get() );
1398 if ( !mp )
1399 return {};
1400
1401 // calculate running sum of triangle areas
1402 std::vector< double > cumulativeAreas;
1403 cumulativeAreas.reserve( mp->numGeometries() );
1404 double totalArea = 0;
1405 std::vector< double > vertices( static_cast< std::size_t >( mp->numGeometries() ) * 6 );
1406 double *vertexData = vertices.data();
1407 for ( auto it = mp->const_parts_begin(); it != mp->const_parts_end(); ++it )
1408 {
1409 if ( feedback && feedback->isCanceled() )
1410 return {};
1411
1413 if ( !part )
1414 return {};
1415
1417 if ( !exterior )
1418 return {};
1419
1420 const double aX = exterior->xAt( 0 );
1421 const double aY = exterior->yAt( 0 );
1422 const double bX = exterior->xAt( 1 );
1423 const double bY = exterior->yAt( 1 );
1424 const double cX = exterior->xAt( 2 );
1425 const double cY = exterior->yAt( 2 );
1426
1427 const double area = QgsGeometryUtilsBase::triangleArea( aX, aY, bX, bY, cX, cY );
1428 *vertexData++ = aX;
1429 *vertexData++ = aY;
1430 *vertexData++ = bX;
1431 *vertexData++ = bY;
1432 *vertexData++ = cX;
1433 *vertexData++ = cY;
1434 totalArea += area;
1435 cumulativeAreas.emplace_back( totalArea );
1436 }
1437
1438 std::random_device rd;
1439 std::mt19937 mt( seed == 0 ? rd() : seed );
1440 std::uniform_real_distribution<> uniformDist( 0, 1 );
1441
1442 // selects a random triangle, weighted by triangle area
1443 auto selectRandomTriangle = [&cumulativeAreas, totalArea]( double random )->int
1444 {
1445 int triangle = 0;
1446 const double target = random * totalArea;
1447 for ( auto it = cumulativeAreas.begin(); it != cumulativeAreas.end(); ++it, triangle++ )
1448 {
1449 if ( *it > target )
1450 return triangle;
1451 }
1452 Q_ASSERT_X( false, "QgsInternalGeometryEngine::randomPointsInPolygon", "Invalid random triangle index" );
1453 return 0; // no warnings
1454 };
1455
1456
1457 QVector<QgsPointXY> result;
1458 result.reserve( count );
1459 int tries = 0;
1460 vertexData = vertices.data();
1461 for ( int i = 0; i < count; )
1462 {
1463 if ( feedback && feedback->isCanceled() )
1464 return QVector< QgsPointXY >();
1465
1466 const double triangleIndexRnd = uniformDist( mt );
1467 // pick random triangle, weighted by triangle area
1468 const std::size_t triangleIndex = selectRandomTriangle( triangleIndexRnd );
1469
1470 // generate a random point inside this triangle
1471 const double weightB = uniformDist( mt );
1472 const double weightC = uniformDist( mt );
1473 double x;
1474 double y;
1475
1476 // get triangle data
1477 const double aX = vertexData[ triangleIndex * 6 ];
1478 const double aY = vertexData[ triangleIndex * 6 + 1 ];
1479 const double bX = vertexData[ triangleIndex * 6 + 2 ];
1480 const double bY = vertexData[ triangleIndex * 6 + 3 ];
1481 const double cX = vertexData[ triangleIndex * 6 + 4 ];
1482 const double cY = vertexData[ triangleIndex * 6 + 5 ];
1483
1484 QgsGeometryUtilsBase::weightedPointInTriangle( aX, aY, bX, bY, cX, cY, weightB, weightC, x, y );
1485
1486 QgsPointXY candidate( x, y );
1487 if ( acceptPoint( candidate ) )
1488 {
1489 result << QgsPointXY( x, y );
1490 i++;
1491 tries = 0;
1492 }
1493 else if ( maxTriesPerPoint != 0 )
1494 {
1495 tries++;
1496 // Skip this point if maximum tries is reached
1497 if ( tries == maxTriesPerPoint )
1498 {
1499 tries = 0;
1500 i++;
1501 }
1502 }
1503 }
1504 return result;
1505}
1506
1508 const std::function< bool( const QgsPointXY & ) > &acceptPoint, unsigned long seed, QgsFeedback *feedback, int maxTriesPerPoint )
1509{
1510 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) != Qgis::GeometryType::Polygon || count == 0 )
1511 return QVector< QgsPointXY >();
1512
1513 // prefer more stable GEOS implementation if available
1514#if (GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR>=11) || GEOS_VERSION_MAJOR>3
1515 return randomPointsInPolygonGeosBackend( mGeometry, count, acceptPoint, seed, feedback, maxTriesPerPoint, mLastError );
1516#else
1517 return randomPointsInPolygonPoly2TriBackend( mGeometry, count, acceptPoint, seed, feedback, maxTriesPerPoint, mLastError );
1518#endif
1519}
1520
1521// ported from PostGIS' lwgeom pta_unstroke
1522
1523std::unique_ptr< QgsCurve > lineToCurve( const QgsCurve *curve, double distanceTolerance,
1524 double pointSpacingAngleTolerance )
1525{
1526 const Qgis::WkbType flatType = QgsWkbTypes::flatType( curve->wkbType() );
1527 if ( flatType == Qgis::WkbType::CircularString )
1528 {
1529 // already curved, so just return a copy
1530 std::unique_ptr< QgsCircularString > out;
1531 out.reset( qgsgeometry_cast< const QgsCircularString * >( curve )->clone() );
1532 return out;
1533 }
1534 else if ( flatType == Qgis::WkbType::CompoundCurve )
1535 {
1536 auto out = std::make_unique< QgsCompoundCurve >();
1538 for ( int i = 0; i < in->nCurves(); i ++ )
1539 {
1540 std::unique_ptr< QgsCurve > processed = lineToCurve( in->curveAt( i ), distanceTolerance, pointSpacingAngleTolerance );
1541 if ( processed )
1542 {
1543 if ( const QgsCompoundCurve *processedCompoundCurve = qgsgeometry_cast< const QgsCompoundCurve *>( processed.get() ) )
1544 {
1545 for ( int i = 0; i < processedCompoundCurve->nCurves(); ++i )
1546 {
1547 out->addCurve( processedCompoundCurve->curveAt( i )->clone() );
1548 }
1549 }
1550 else
1551 {
1552 out->addCurve( processed.release() );
1553 }
1554 }
1555 }
1556 return out;
1557 }
1558 else if ( flatType == Qgis::WkbType::LineString )
1559 {
1560 const QgsLineString *lineString = qgsgeometry_cast< const QgsLineString * >( curve );
1561
1562 auto out = std::make_unique< QgsCompoundCurve >();
1563
1564 /* Minimum number of edges, per quadrant, required to define an arc */
1565 const unsigned int minQuadEdges = 2;
1566
1567 /* Die on null input */
1568 if ( !lineString )
1569 return nullptr;
1570
1571 /* Null on empty input? */
1572 if ( lineString->nCoordinates() == 0 )
1573 return nullptr;
1574
1575 /* We can't desegmentize anything shorter than four points */
1576 if ( lineString->nCoordinates() < 4 )
1577 {
1578 out->addCurve( lineString->clone() );
1579 return out;
1580 }
1581
1582 /* Allocate our result array of vertices that are part of arcs */
1583 int numEdges = lineString->nCoordinates() - 1;
1584 QVector< int > edgesInArcs( numEdges + 1, 0 );
1585
1586 auto arcAngle = []( const QgsPoint & a, const QgsPoint & b, const QgsPoint & c )->double
1587 {
1588 double abX = b.x() - a.x();
1589 double abY = b.y() - a.y();
1590
1591 double cbX = b.x() - c.x();
1592 double cbY = b.y() - c.y();
1593
1594 double dot = ( abX * cbX + abY * cbY ); /* dot product */
1595 double cross = ( abX * cbY - abY * cbX ); /* cross product */
1596
1597 double alpha = std::atan2( cross, dot );
1598
1599 return alpha;
1600 };
1601
1602 /* We make a candidate arc of the first two edges, */
1603 /* And then see if the next edge follows it */
1604 int i = 0;
1605 int j = 0;
1606 int k = 0;
1607 int currentArc = 1;
1608 QgsPoint a1;
1609 QgsPoint a2;
1610 QgsPoint a3;
1611 QgsPoint b;
1612 double centerX = 0.0;
1613 double centerY = 0.0;
1614 double radius = 0;
1615
1616 while ( i < numEdges - 2 )
1617 {
1618 unsigned int arcEdges = 0;
1619 double numQuadrants = 0;
1620 double angle;
1621
1622 bool foundArc = false;
1623 /* Make candidate arc */
1624 a1 = lineString->pointN( i );
1625 a2 = lineString->pointN( i + 1 );
1626 a3 = lineString->pointN( i + 2 );
1627 QgsPoint first = a1;
1628
1629 for ( j = i + 3; j < numEdges + 1; j++ )
1630 {
1631 b = lineString->pointN( j );
1632
1633 /* Does this point fall on our candidate arc? */
1634 if ( QgsGeometryUtils::pointContinuesArc( a1, a2, a3, b, distanceTolerance, pointSpacingAngleTolerance ) )
1635 {
1636 /* Yes. Mark this edge and the two preceding it as arc components */
1637 foundArc = true;
1638 for ( k = j - 1; k > j - 4; k-- )
1639 edgesInArcs[k] = currentArc;
1640 }
1641 else
1642 {
1643 /* No. So we're done with this candidate arc */
1644 currentArc++;
1645 break;
1646 }
1647
1648 a1 = a2;
1649 a2 = a3;
1650 a3 = b;
1651 }
1652 /* Jump past all the edges that were added to the arc */
1653 if ( foundArc )
1654 {
1655 /* Check if an arc was composed by enough edges to be
1656 * really considered an arc
1657 * See http://trac.osgeo.org/postgis/ticket/2420
1658 */
1659 arcEdges = j - 1 - i;
1660 if ( first.x() == b.x() && first.y() == b.y() )
1661 {
1662 numQuadrants = 4;
1663 }
1664 else
1665 {
1666 QgsGeometryUtils::circleCenterRadius( first, b, a1, radius, centerX, centerY );
1667
1668 angle = arcAngle( first, QgsPoint( centerX, centerY ), b );
1669 int p2Side = QgsGeometryUtilsBase::leftOfLine( b.x(), b.y(), first.x(), first.y(), a1.x(), a1.y() );
1670 if ( p2Side >= 0 )
1671 angle = -angle;
1672
1673 if ( angle < 0 )
1674 angle = 2 * M_PI + angle;
1675 numQuadrants = ( 4 * angle ) / ( 2 * M_PI );
1676 }
1677 /* a1 is first point, b is last point */
1678 if ( arcEdges < minQuadEdges * numQuadrants )
1679 {
1680 // LWDEBUGF( 4, "Not enough edges for a %g quadrants arc, %g needed", num_quadrants, min_quad_edges * num_quadrants );
1681 for ( k = j - 1; k >= i; k-- )
1682 edgesInArcs[k] = 0;
1683 }
1684
1685 i = j - 1;
1686 }
1687 else
1688 {
1689 /* Mark this edge as a linear edge */
1690 edgesInArcs[i] = 0;
1691 i = i + 1;
1692 }
1693 }
1694
1695 int start = 0;
1696 int end = 0;
1697 /* non-zero if edge is part of an arc */
1698 int edgeType = edgesInArcs[0];
1699
1700 auto addPointsToCurve = [ lineString, &out ]( int start, int end, int type )
1701 {
1702 if ( type == 0 )
1703 {
1704 // straight segment
1705 QVector< QgsPoint > points;
1706 for ( int j = start; j < end + 2; ++ j )
1707 {
1708 points.append( lineString->pointN( j ) );
1709 }
1710 std::unique_ptr< QgsCurve > straightSegment = std::make_unique< QgsLineString >( points );
1711 out->addCurve( straightSegment.release() );
1712 }
1713 else
1714 {
1715 // curved segment
1716 QVector< QgsPoint > points;
1717 points.append( lineString->pointN( start ) );
1718 points.append( lineString->pointN( ( start + end + 1 ) / 2 ) );
1719 points.append( lineString->pointN( end + 1 ) );
1720 auto curvedSegment = std::make_unique< QgsCircularString >();
1721 curvedSegment->setPoints( points );
1722 out->addCurve( curvedSegment.release() );
1723 }
1724 };
1725
1726 for ( int i = 1; i < numEdges; i++ )
1727 {
1728 if ( edgeType != edgesInArcs[i] )
1729 {
1730 end = i - 1;
1731 addPointsToCurve( start, end, edgeType );
1732 start = i;
1733 edgeType = edgesInArcs[i];
1734 }
1735 }
1736
1737 /* Roll out last item */
1738 end = numEdges - 1;
1739 addPointsToCurve( start, end, edgeType );
1740
1741 // return a simplified type if it doesn't need to be a compound curve resu
1742 if ( QgsWkbTypes::flatType( out->simplifiedTypeRef()->wkbType() ) == Qgis::WkbType::CircularString )
1743 {
1744 std::unique_ptr< QgsCircularString > res;
1745 res.reset( qgsgeometry_cast< const QgsCircularString * >( out->simplifiedTypeRef() )->clone() );
1746 return res;
1747 }
1748
1749 return out;
1750 }
1751
1752 return nullptr;
1753}
1754
1755std::unique_ptr< QgsAbstractGeometry > convertGeometryToCurves( const QgsAbstractGeometry *geom, double distanceTolerance, double angleTolerance )
1756{
1758 {
1759 return lineToCurve( qgsgeometry_cast< const QgsCurve * >( geom ), distanceTolerance, angleTolerance );
1760 }
1761 else
1762 {
1763 // polygon
1765 auto result = std::make_unique< QgsCurvePolygon>();
1766
1767 result->setExteriorRing( lineToCurve( polygon->exteriorRing(),
1768 distanceTolerance, angleTolerance ).release() );
1769 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
1770 {
1771 result->addInteriorRing( lineToCurve( polygon->interiorRing( i ),
1772 distanceTolerance, angleTolerance ).release() );
1773 }
1774
1775 return result;
1776 }
1777}
1778
1779QgsGeometry QgsInternalGeometryEngine::convertToCurves( double distanceTolerance, double angleTolerance ) const
1780{
1781 mLastError.clear();
1782 if ( !mGeometry )
1783 {
1784 return QgsGeometry();
1785 }
1786
1787 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == Qgis::GeometryType::Point )
1788 {
1789 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
1790 }
1791
1793 {
1794 int numGeom = gc->numGeometries();
1795 QVector< QgsAbstractGeometry * > geometryList;
1796 geometryList.reserve( numGeom );
1797 for ( int i = 0; i < numGeom; ++i )
1798 {
1799 geometryList << convertGeometryToCurves( gc->geometryN( i ), distanceTolerance, angleTolerance ).release();
1800 }
1801
1802 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
1803 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
1804 {
1805 first.addPartV2( g );
1806 }
1807 return first;
1808 }
1809 else
1810 {
1811 return QgsGeometry( convertGeometryToCurves( mGeometry, distanceTolerance, angleTolerance ) );
1812 }
1813}
1814
1815QgsGeometry QgsInternalGeometryEngine::orientedMinimumBoundingBox( double &area, double &angle, double &width, double &height ) const
1816{
1817 mLastError.clear();
1818
1819 QgsRectangle minRect;
1820 area = std::numeric_limits<double>::max();
1821 angle = 0;
1822 width = std::numeric_limits<double>::max();
1823 height = std::numeric_limits<double>::max();
1824
1825 if ( !mGeometry || mGeometry->nCoordinates() < 2 )
1826 return QgsGeometry();
1827
1828 std::unique_ptr< QgsGeometryEngine >engine( QgsGeometry::createGeometryEngine( mGeometry ) );
1829 std::unique_ptr< QgsAbstractGeometry > hull( engine->convexHull( &mLastError ) );
1830 if ( !hull )
1831 return QgsGeometry();
1832
1833 QgsVertexId vertexId;
1834 QgsPoint pt0;
1835 QgsPoint pt1;
1836 QgsPoint pt2;
1837 // get first point
1838 if ( !hull->nextVertex( vertexId, pt0 ) )
1839 {
1840 return QgsGeometry();
1841 }
1842
1843 pt1 = pt0;
1844 double totalRotation = 0;
1845 while ( hull->nextVertex( vertexId, pt2 ) )
1846 {
1847 double currentAngle = QgsGeometryUtilsBase::lineAngle( pt1.x(), pt1.y(), pt2.x(), pt2.y() );
1848 double rotateAngle = 180.0 / M_PI * currentAngle;
1849 totalRotation += rotateAngle;
1850
1851 QTransform t = QTransform::fromTranslate( pt0.x(), pt0.y() );
1852 t.rotate( rotateAngle );
1853 t.translate( -pt0.x(), -pt0.y() );
1854
1855 hull->transform( t );
1856
1857 QgsRectangle bounds = hull->boundingBox();
1858 double currentArea = bounds.width() * bounds.height();
1859 if ( currentArea < area )
1860 {
1861 minRect = bounds;
1862 area = currentArea;
1863 angle = totalRotation;
1864 width = bounds.width();
1865 height = bounds.height();
1866 }
1867
1868 pt1 = hull->vertexAt( vertexId );
1869 }
1870
1871 QgsGeometry minBounds = QgsGeometry::fromRect( minRect );
1872 minBounds.rotate( angle, QgsPointXY( pt0.x(), pt0.y() ) );
1873
1874 if ( width > height )
1875 {
1876 width = minRect.height();
1877 height = minRect.width();
1878 angle = angle + 90.0;
1879 }
1880
1881 // constrain angle to 0 - 180
1882 if ( angle > 180.0 )
1883 angle = std::fmod( angle, 180.0 );
1884
1885 return minBounds;
1886}
1887
1888std::unique_ptr< QgsLineString > triangularWavesAlongLine( const QgsLineString *line, double wavelength, const double amplitude, const bool strictWavelength )
1889{
1890 const int totalPoints = line->numPoints();
1891 if ( totalPoints < 2 )
1892 return nullptr;
1893
1894 const double *x = line->xData();
1895 const double *y = line->yData();
1896
1897 double prevX = *x++;
1898 double prevY = *y++;
1899
1900 QVector< double > outX;
1901 QVector< double > outY;
1902 const double totalLength = line->length();
1903
1904 const int maxRepetitions = std::ceil( totalLength / wavelength );
1905 if ( !strictWavelength )
1906 wavelength = totalLength / maxRepetitions;
1907
1908 const int estimatedPoints = maxRepetitions * 2 + 2;
1909 outX.reserve( estimatedPoints );
1910 outY.reserve( estimatedPoints );
1911 outX.append( prevX );
1912 outY.append( prevY );
1913
1914 double distanceToNextPointFromStartOfSegment = wavelength / 4;
1915 int side = -1;
1916 for ( int i = 1; i < totalPoints; ++i )
1917 {
1918 double thisX = *x++;
1919 double thisY = *y++;
1920
1921 const double segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
1922 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1923 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
1924 {
1925 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1926 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
1927 double pX, pY;
1928 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
1929
1930 // project point on line out by amplitude
1931 const double outPointX = pX + side * amplitude * std::sin( segmentAngleRadians + M_PI_2 );
1932 const double outPointY = pY + side * amplitude * std::cos( segmentAngleRadians + M_PI_2 );
1933
1934 outX.append( outPointX );
1935 outY.append( outPointY );
1936
1937 distanceToNextPointFromStartOfSegment += wavelength / 2;
1938 side = -side;
1939 }
1940
1941 prevX = thisX;
1942 prevY = thisY;
1943 distanceToNextPointFromStartOfSegment -= segmentLength;
1944 }
1945
1946 outX.append( prevX );
1947 outY.append( prevY );
1948
1949 return std::make_unique< QgsLineString >( outX, outY );
1950}
1951
1952std::unique_ptr< QgsLineString > triangularWavesRandomizedAlongLine( const QgsLineString *line,
1953 const double minimumWavelength, const double maximumWavelength,
1954 const double minimumAmplitude, const double maximumAmplitude,
1955 std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
1956{
1957 const int totalPoints = line->numPoints();
1958 if ( totalPoints < 2 )
1959 return nullptr;
1960
1961 const double *x = line->xData();
1962 const double *y = line->yData();
1963
1964 double prevX = *x++;
1965 double prevY = *y++;
1966
1967 QVector< double > outX;
1968 QVector< double > outY;
1969 const double totalLength = line->length();
1970
1971 const int maxRepetitions = std::ceil( totalLength / minimumWavelength );
1972
1973 const int estimatedPoints = maxRepetitions * 2 + 2;
1974 outX.reserve( estimatedPoints );
1975 outY.reserve( estimatedPoints );
1976 outX.append( prevX );
1977 outY.append( prevY );
1978
1979 double wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
1980 double distanceToNextPointFromStartOfSegment = wavelength / 4;
1981 double amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
1982
1983 int side = -1;
1984 for ( int i = 1; i < totalPoints; ++i )
1985 {
1986 double thisX = *x++;
1987 double thisY = *y++;
1988
1989 const double segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
1990 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1991 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
1992 {
1993 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1994 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
1995 double pX, pY;
1996 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
1997
1998 // project point on line out by amplitude
1999 const double outPointX = pX + side * amplitude * std::sin( segmentAngleRadians + M_PI_2 );
2000 const double outPointY = pY + side * amplitude * std::cos( segmentAngleRadians + M_PI_2 );
2001 outX.append( outPointX );
2002 outY.append( outPointY );
2003
2004 wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
2005 amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
2006
2007 distanceToNextPointFromStartOfSegment += wavelength / 2;
2008 side = -side;
2009 }
2010
2011 prevX = thisX;
2012 prevY = thisY;
2013 distanceToNextPointFromStartOfSegment -= segmentLength;
2014 }
2015
2016 outX.append( prevX );
2017 outY.append( prevY );
2018
2019 return std::make_unique< QgsLineString >( outX, outY );
2020}
2021
2022std::unique_ptr< QgsAbstractGeometry > triangularWavesPrivate( const QgsAbstractGeometry *geom, double wavelength, double amplitude, bool strictWavelength )
2023{
2024 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2025 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2026 {
2027 segmentizedCopy.reset( geom->segmentize() );
2028 geom = segmentizedCopy.get();
2029 }
2030
2032 {
2033 return triangularWavesAlongLine( static_cast< const QgsLineString * >( geom ), wavelength, amplitude, strictWavelength );
2034 }
2035 else
2036 {
2037 // polygon
2038 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2039 auto result = std::make_unique< QgsPolygon >();
2040
2041 result->setExteriorRing( triangularWavesAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2042 wavelength, amplitude, strictWavelength ).release() );
2043 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2044 {
2045 result->addInteriorRing( triangularWavesAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2046 wavelength, amplitude, strictWavelength ).release() );
2047 }
2048
2049 return result;
2050 }
2051}
2052
2053std::unique_ptr< QgsAbstractGeometry > triangularWavesRandomizedPrivate( const QgsAbstractGeometry *geom, double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
2054{
2055 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2056 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2057 {
2058 segmentizedCopy.reset( geom->segmentize() );
2059 geom = segmentizedCopy.get();
2060 }
2061
2063 {
2064 return triangularWavesRandomizedAlongLine( static_cast< const QgsLineString * >( geom ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt );
2065 }
2066 else
2067 {
2068 // polygon
2069 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2070 auto result = std::make_unique< QgsPolygon >();
2071
2072 result->setExteriorRing( triangularWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2073 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2074 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2075 {
2076 result->addInteriorRing( triangularWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2077 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2078 }
2079
2080 return result;
2081 }
2082}
2083
2084QgsGeometry QgsInternalGeometryEngine::triangularWaves( double wavelength, double amplitude, bool strictWavelength ) const
2085{
2086 if ( wavelength < 0 || qgsDoubleNear( wavelength, 0 ) )
2087 return QgsGeometry();
2088
2089 mLastError.clear();
2090 if ( !mGeometry )
2091 {
2092 return QgsGeometry();
2093 }
2094
2095 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == Qgis::GeometryType::Point )
2096 {
2097 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2098 }
2099
2101 {
2102 int numGeom = gc->numGeometries();
2103 QVector< QgsAbstractGeometry * > geometryList;
2104 geometryList.reserve( numGeom );
2105 for ( int i = 0; i < numGeom; ++i )
2106 {
2107 geometryList << triangularWavesPrivate( gc->geometryN( i ), wavelength, amplitude, strictWavelength ).release();
2108 }
2109
2110 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2111 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2112 {
2113 first.addPartV2( g );
2114 }
2115 return first;
2116 }
2117 else
2118 {
2119 return QgsGeometry( triangularWavesPrivate( mGeometry, wavelength, amplitude, strictWavelength ) );
2120 }
2121}
2122
2123QgsGeometry QgsInternalGeometryEngine::triangularWavesRandomized( double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, unsigned long seed ) const
2124{
2125 if ( minimumWavelength < 0 || qgsDoubleNear( minimumWavelength, 0 ) || maximumWavelength < 0 || qgsDoubleNear( maximumWavelength, 0 ) || maximumWavelength < minimumWavelength )
2126 return QgsGeometry();
2127
2128 mLastError.clear();
2129 if ( !mGeometry )
2130 {
2131 return QgsGeometry();
2132 }
2133
2134 std::random_device rd;
2135 std::mt19937 mt( seed == 0 ? rd() : seed );
2136 std::uniform_real_distribution<> uniformDist( 0, 1 );
2137
2138 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == Qgis::GeometryType::Point )
2139 {
2140 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2141 }
2142
2144 {
2145 int numGeom = gc->numGeometries();
2146 QVector< QgsAbstractGeometry * > geometryList;
2147 geometryList.reserve( numGeom );
2148 for ( int i = 0; i < numGeom; ++i )
2149 {
2150 geometryList << triangularWavesRandomizedPrivate( gc->geometryN( i ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release();
2151 }
2152
2153 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2154 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2155 {
2156 first.addPartV2( g );
2157 }
2158 return first;
2159 }
2160 else
2161 {
2162 return QgsGeometry( triangularWavesRandomizedPrivate( mGeometry, minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ) );
2163 }
2164}
2165
2166std::unique_ptr< QgsLineString > squareWavesAlongLine( const QgsLineString *line, double wavelength, const double amplitude, const bool strictWavelength )
2167{
2168 const int totalPoints = line->numPoints();
2169 if ( totalPoints < 2 )
2170 return nullptr;
2171
2172 const double *x = line->xData();
2173 const double *y = line->yData();
2174
2175 double prevX = *x++;
2176 double prevY = *y++;
2177
2178 QVector< double > outX;
2179 QVector< double > outY;
2180 const double totalLength = line->length();
2181
2182 const int maxRepetitions = std::ceil( totalLength / wavelength );
2183 if ( !strictWavelength )
2184 wavelength = totalLength / maxRepetitions;
2185
2186 const int estimatedPoints = maxRepetitions * 4 + 2;
2187 outX.reserve( estimatedPoints );
2188 outY.reserve( estimatedPoints );
2189 outX.append( prevX );
2190 outY.append( prevY );
2191
2192 const double startSegmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, *x, *y );
2193 outX.append( prevX - amplitude * std::sin( startSegmentAngleRadians + M_PI_2 ) );
2194 outY.append( prevY - amplitude * std::cos( startSegmentAngleRadians + M_PI_2 ) );
2195
2196 double distanceToNextPointFromStartOfSegment = wavelength / 2;
2197
2198 int side = -1;
2199 for ( int i = 1; i < totalPoints; ++i )
2200 {
2201 double thisX = *x++;
2202 double thisY = *y++;
2203
2204 const double segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
2205 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
2206 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
2207 {
2208 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
2209 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
2210 double pX, pY;
2211 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
2212
2213 // project point on line out by amplitude
2214 const double sinAngle = std::sin( segmentAngleRadians + M_PI_2 );
2215 const double cosAngle = std::cos( segmentAngleRadians + M_PI_2 );
2216 outX.append( pX + side * amplitude * sinAngle );
2217 outY.append( pY + side * amplitude * cosAngle );
2218 outX.append( pX - side * amplitude * sinAngle );
2219 outY.append( pY - side * amplitude * cosAngle );
2220
2221 distanceToNextPointFromStartOfSegment += wavelength / 2;
2222 side = -side;
2223 }
2224
2225 prevX = thisX;
2226 prevY = thisY;
2227 distanceToNextPointFromStartOfSegment -= segmentLength;
2228 }
2229
2230 // replace last point, which will be projected out to amplitude of wave, with the actual end point of the line
2231 outX.pop_back();
2232 outY.pop_back();
2233 outX.append( prevX );
2234 outY.append( prevY );
2235
2236 return std::make_unique< QgsLineString >( outX, outY );
2237}
2238
2239std::unique_ptr< QgsLineString > squareWavesRandomizedAlongLine( const QgsLineString *line,
2240 const double minimumWavelength, const double maximumWavelength,
2241 const double minimumAmplitude, const double maximumAmplitude,
2242 std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
2243{
2244 const int totalPoints = line->numPoints();
2245 if ( totalPoints < 2 )
2246 return nullptr;
2247
2248 const double *x = line->xData();
2249 const double *y = line->yData();
2250
2251 double prevX = *x++;
2252 double prevY = *y++;
2253
2254 QVector< double > outX;
2255 QVector< double > outY;
2256 const double totalLength = line->length();
2257
2258 const int maxRepetitions = std::ceil( totalLength / minimumWavelength );
2259
2260 const int estimatedPoints = maxRepetitions * 4 + 2;
2261 outX.reserve( estimatedPoints );
2262 outY.reserve( estimatedPoints );
2263 outX.append( prevX );
2264 outY.append( prevY );
2265
2266 double amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
2267
2268 double segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, *x, *y );
2269 outX.append( prevX - amplitude * std::sin( segmentAngleRadians + M_PI_2 ) );
2270 outY.append( prevY - amplitude * std::cos( segmentAngleRadians + M_PI_2 ) );
2271
2272 double wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
2273 double distanceToNextPointFromStartOfSegment = wavelength / 2;
2274
2275 int side = -1;
2276 for ( int i = 1; i < totalPoints; ++i )
2277 {
2278 double thisX = *x++;
2279 double thisY = *y++;
2280
2281 segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
2282 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
2283 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
2284 {
2285 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
2286 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
2287 double pX, pY;
2288 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
2289
2290 // project point on line out by amplitude
2291 const double sinAngle = std::sin( segmentAngleRadians + M_PI_2 );
2292 const double cosAngle = std::cos( segmentAngleRadians + M_PI_2 );
2293 outX.append( pX + side * amplitude * sinAngle );
2294 outY.append( pY + side * amplitude * cosAngle );
2295
2296 amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
2297 outX.append( pX - side * amplitude * sinAngle );
2298 outY.append( pY - side * amplitude * cosAngle );
2299
2300 wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
2301 distanceToNextPointFromStartOfSegment += wavelength / 2;
2302 side = -side;
2303 }
2304
2305 prevX = thisX;
2306 prevY = thisY;
2307 distanceToNextPointFromStartOfSegment -= segmentLength;
2308 }
2309
2310 outX.append( prevX + side * amplitude * std::sin( segmentAngleRadians + M_PI_2 ) );
2311 outY.append( prevY + side * amplitude * std::cos( segmentAngleRadians + M_PI_2 ) );
2312 outX.append( prevX );
2313 outY.append( prevY );
2314
2315 return std::make_unique< QgsLineString >( outX, outY );
2316}
2317
2318std::unique_ptr< QgsAbstractGeometry > squareWavesPrivate( const QgsAbstractGeometry *geom, double wavelength, double amplitude, bool strictWavelength )
2319{
2320 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2321 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2322 {
2323 segmentizedCopy.reset( geom->segmentize() );
2324 geom = segmentizedCopy.get();
2325 }
2326
2328 {
2329 return squareWavesAlongLine( static_cast< const QgsLineString * >( geom ), wavelength, amplitude, strictWavelength );
2330 }
2331 else
2332 {
2333 // polygon
2334 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2335 auto result = std::make_unique< QgsPolygon >();
2336
2337 result->setExteriorRing( squareWavesAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2338 wavelength, amplitude, strictWavelength ).release() );
2339 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2340 {
2341 result->addInteriorRing( squareWavesAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2342 wavelength, amplitude, strictWavelength ).release() );
2343 }
2344
2345 return result;
2346 }
2347}
2348
2349std::unique_ptr< QgsAbstractGeometry > squareWavesRandomizedPrivate( const QgsAbstractGeometry *geom, double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
2350{
2351 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2352 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2353 {
2354 segmentizedCopy.reset( geom->segmentize() );
2355 geom = segmentizedCopy.get();
2356 }
2357
2359 {
2360 return squareWavesRandomizedAlongLine( static_cast< const QgsLineString * >( geom ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt );
2361 }
2362 else
2363 {
2364 // polygon
2365 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2366 auto result = std::make_unique< QgsPolygon >();
2367
2368 result->setExteriorRing( squareWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2369 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2370 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2371 {
2372 result->addInteriorRing( squareWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2373 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2374 }
2375
2376 return result;
2377 }
2378}
2379
2380QgsGeometry QgsInternalGeometryEngine::squareWaves( double wavelength, double amplitude, bool strictWavelength ) const
2381{
2382 if ( wavelength < 0 || qgsDoubleNear( wavelength, 0 ) )
2383 return QgsGeometry();
2384
2385 mLastError.clear();
2386 if ( !mGeometry )
2387 {
2388 return QgsGeometry();
2389 }
2390
2391 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == Qgis::GeometryType::Point )
2392 {
2393 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2394 }
2395
2397 {
2398 int numGeom = gc->numGeometries();
2399 QVector< QgsAbstractGeometry * > geometryList;
2400 geometryList.reserve( numGeom );
2401 for ( int i = 0; i < numGeom; ++i )
2402 {
2403 geometryList << squareWavesPrivate( gc->geometryN( i ), wavelength, amplitude, strictWavelength ).release();
2404 }
2405
2406 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2407 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2408 {
2409 first.addPartV2( g );
2410 }
2411 return first;
2412 }
2413 else
2414 {
2415 return QgsGeometry( squareWavesPrivate( mGeometry, wavelength, amplitude, strictWavelength ) );
2416 }
2417}
2418
2419QgsGeometry QgsInternalGeometryEngine::squareWavesRandomized( double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, unsigned long seed ) const
2420{
2421 if ( minimumWavelength < 0 || qgsDoubleNear( minimumWavelength, 0 ) || maximumWavelength < 0 || qgsDoubleNear( maximumWavelength, 0 ) || maximumWavelength < minimumWavelength )
2422 return QgsGeometry();
2423
2424 mLastError.clear();
2425 if ( !mGeometry )
2426 {
2427 return QgsGeometry();
2428 }
2429
2430 std::random_device rd;
2431 std::mt19937 mt( seed == 0 ? rd() : seed );
2432 std::uniform_real_distribution<> uniformDist( 0, 1 );
2433
2434 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == Qgis::GeometryType::Point )
2435 {
2436 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2437 }
2438
2440 {
2441 int numGeom = gc->numGeometries();
2442 QVector< QgsAbstractGeometry * > geometryList;
2443 geometryList.reserve( numGeom );
2444 for ( int i = 0; i < numGeom; ++i )
2445 {
2446 geometryList << squareWavesRandomizedPrivate( gc->geometryN( i ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release();
2447 }
2448
2449 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2450 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2451 {
2452 first.addPartV2( g );
2453 }
2454 return first;
2455 }
2456 else
2457 {
2458 return QgsGeometry( squareWavesRandomizedPrivate( mGeometry, minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ) );
2459 }
2460}
2461
2462std::unique_ptr< QgsLineString > roundWavesAlongLine( const QgsLineString *line, double wavelength, const double amplitude, const bool strictWavelength )
2463{
2464 const int totalPoints = line->numPoints();
2465 if ( totalPoints < 2 )
2466 return nullptr;
2467
2468 const double *x = line->xData();
2469 const double *y = line->yData();
2470
2471 double prevX = *x++;
2472 double prevY = *y++;
2473
2474 const double totalLength = line->length();
2475
2476 const int maxRepetitions = std::ceil( totalLength / wavelength );
2477 if ( !strictWavelength )
2478 wavelength = totalLength / maxRepetitions;
2479
2480 const int segments = 10;
2481
2482 int side = -1;
2483
2484 double xOutBuffer[4] { prevX, prevX, prevX, prevX };
2485 double yOutBuffer[4] { prevY, prevY, prevY, prevY };
2486 bool isFirstPart = true;
2487
2488 double distanceToNextPointFromStartOfSegment = wavelength / 8;
2489 int bufferIndex = 1;
2490 auto out = std::make_unique< QgsLineString >();
2491
2492 double segmentAngleRadians = 0;
2493 double remainingDistance = totalLength;
2494 double totalCoveredDistance = 0;
2495
2496 for ( int i = 1; i < totalPoints; ++i )
2497 {
2498 double thisX = *x++;
2499 double thisY = *y++;
2500
2501 segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
2502 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
2503 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
2504 {
2505 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
2506 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
2507 double pX, pY;
2508 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
2509 remainingDistance = totalLength - totalCoveredDistance - distanceToPoint;
2510
2511 const double sinAngle = std::sin( segmentAngleRadians + M_PI_2 );
2512 const double cosAngle = std::cos( segmentAngleRadians + M_PI_2 );
2513
2514 if ( bufferIndex == 1 && isFirstPart )
2515 {
2516 xOutBuffer[1] = ( xOutBuffer[0] + pX - side * amplitude * sinAngle ) * 0.5;
2517 yOutBuffer[1] = ( yOutBuffer[0] + pY - side * amplitude * cosAngle ) * 0.5;
2518 xOutBuffer[2] = pX - side * amplitude * sinAngle;
2519 yOutBuffer[2] = pY - side * amplitude * cosAngle;
2520 bufferIndex = 2;
2521 distanceToNextPointFromStartOfSegment += wavelength / 8;
2522 }
2523 else if ( bufferIndex == 1 )
2524 {
2525 xOutBuffer[1] = pX + side * amplitude * sinAngle;
2526 yOutBuffer[1] = pY + side * amplitude * cosAngle;
2527 xOutBuffer[2] = pX - side * amplitude * sinAngle;
2528 yOutBuffer[2] = pY - side * amplitude * cosAngle;
2529 bufferIndex = 2;
2530 distanceToNextPointFromStartOfSegment += wavelength / 4;
2531 }
2532 else if ( bufferIndex == 2 )
2533 {
2534 xOutBuffer[3] = pX - side * amplitude * sinAngle;
2535 yOutBuffer[3] = pY - side * amplitude * cosAngle;
2536
2537 if ( isFirstPart )
2538 {
2539 xOutBuffer[2] = ( xOutBuffer[2] + xOutBuffer[3] ) * 0.5;
2540 yOutBuffer[2] = ( yOutBuffer[2] + yOutBuffer[3] ) * 0.5;
2541 isFirstPart = false;
2542 }
2543
2544 // flush curve
2545 std::unique_ptr< QgsLineString > bezier( QgsLineString::fromBezierCurve( QgsPoint( xOutBuffer[0], yOutBuffer[0] ),
2546 QgsPoint( xOutBuffer[1], yOutBuffer[1] ),
2547 QgsPoint( xOutBuffer[2], yOutBuffer[2] ),
2548 QgsPoint( xOutBuffer[3], yOutBuffer[3] ),
2549 segments ) );
2550 out->append( bezier.get() );
2551
2552 // shuffle buffer alone
2553 xOutBuffer[0] = xOutBuffer[3];
2554 yOutBuffer[0] = yOutBuffer[3];
2555 bufferIndex = 1;
2556 side = -side;
2557 distanceToNextPointFromStartOfSegment += wavelength / 4;
2558 }
2559 }
2560 totalCoveredDistance += segmentLength;
2561 prevX = thisX;
2562 prevY = thisY;
2563 distanceToNextPointFromStartOfSegment -= segmentLength;
2564 }
2565
2566 const double midX = prevX - remainingDistance / 2 * std::sin( segmentAngleRadians );
2567 const double midY = prevY - remainingDistance / 2 * std::cos( segmentAngleRadians );
2568 const double pX = midX + side * amplitude * std::sin( segmentAngleRadians + M_PI_2 );
2569 const double pY = midY + side * amplitude * std::cos( segmentAngleRadians + M_PI_2 );
2570
2571 std::unique_ptr< QgsLineString > bezier( QgsLineString::fromBezierCurve( out->endPoint(),
2572 QgsPoint( ( out->endPoint().x() + pX ) * 0.5, ( out->endPoint().y() + pY ) * 0.5 ),
2573 QgsPoint( ( prevX + pX ) * 0.5, ( prevY + pY ) * 0.5 ),
2574 QgsPoint( prevX, prevY ),
2575 segments / 2 ) );
2576 out->append( bezier.get() );
2577
2578 return out;
2579}
2580
2581std::unique_ptr< QgsLineString > roundWavesRandomizedAlongLine( const QgsLineString *line,
2582 const double minimumWavelength, const double maximumWavelength,
2583 const double minimumAmplitude, const double maximumAmplitude,
2584 std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
2585{
2586 const int totalPoints = line->numPoints();
2587 if ( totalPoints < 2 )
2588 return nullptr;
2589
2590 const double *x = line->xData();
2591 const double *y = line->yData();
2592
2593 double prevX = *x++;
2594 double prevY = *y++;
2595
2596 const double totalLength = line->length();
2597
2598 const int segments = 10;
2599
2600 int side = -1;
2601
2602 double xOutBuffer[4] { prevX, prevX, prevX, prevX };
2603 double yOutBuffer[4] { prevY, prevY, prevY, prevY };
2604 bool isFirstPart = true;
2605
2606 double amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
2607 double wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
2608
2609 double distanceToNextPointFromStartOfSegment = wavelength / 8;
2610 int bufferIndex = 1;
2611 auto out = std::make_unique< QgsLineString >();
2612
2613 double segmentAngleRadians = 0;
2614
2615 double remainingDistance = totalLength;
2616 double totalCoveredDistance = 0;
2617
2618 for ( int i = 1; i < totalPoints; ++i )
2619 {
2620 double thisX = *x++;
2621 double thisY = *y++;
2622
2623 segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
2624 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
2625 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
2626 {
2627 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
2628 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
2629 double pX, pY;
2630 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
2631 remainingDistance = totalLength - totalCoveredDistance - distanceToPoint;
2632
2633 const double sinAngle = std::sin( segmentAngleRadians + M_PI_2 );
2634 const double cosAngle = std::cos( segmentAngleRadians + M_PI_2 );
2635
2636 if ( bufferIndex == 1 && isFirstPart )
2637 {
2638 xOutBuffer[1] = ( xOutBuffer[0] + pX - side * amplitude * sinAngle ) * 0.5;
2639 yOutBuffer[1] = ( yOutBuffer[0] + pY - side * amplitude * cosAngle ) * 0.5;
2640 xOutBuffer[2] = pX - side * amplitude * sinAngle;
2641 yOutBuffer[2] = pY - side * amplitude * cosAngle;
2642 bufferIndex = 2;
2643 distanceToNextPointFromStartOfSegment += wavelength / 8;
2644 }
2645 else if ( bufferIndex == 1 )
2646 {
2647 xOutBuffer[1] = pX + side * amplitude * sinAngle;
2648 yOutBuffer[1] = pY + side * amplitude * cosAngle;
2649 amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
2650 xOutBuffer[2] = pX - side * amplitude * sinAngle;
2651 bufferIndex = 2;
2652 yOutBuffer[2] = pY - side * amplitude * cosAngle;
2653 distanceToNextPointFromStartOfSegment += wavelength / 4;
2654 }
2655 else if ( bufferIndex == 2 )
2656 {
2657 xOutBuffer[3] = pX - side * amplitude * sinAngle;
2658 yOutBuffer[3] = pY - side * amplitude * cosAngle;
2659
2660 if ( isFirstPart )
2661 {
2662 xOutBuffer[2] = ( xOutBuffer[2] + xOutBuffer[3] ) * 0.5;
2663 yOutBuffer[2] = ( yOutBuffer[2] + yOutBuffer[3] ) * 0.5;
2664 isFirstPart = false;
2665 }
2666
2667 // flush curve
2668 std::unique_ptr< QgsLineString > bezier( QgsLineString::fromBezierCurve( QgsPoint( xOutBuffer[0], yOutBuffer[0] ),
2669 QgsPoint( xOutBuffer[1], yOutBuffer[1] ),
2670 QgsPoint( xOutBuffer[2], yOutBuffer[2] ),
2671 QgsPoint( xOutBuffer[3], yOutBuffer[3] ),
2672 segments ) );
2673 out->append( bezier.get() );
2674
2675 // shuffle buffer alone
2676 xOutBuffer[0] = xOutBuffer[3];
2677 yOutBuffer[0] = yOutBuffer[3];
2678 bufferIndex = 1;
2679 side = -side;
2680
2681 wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
2682
2683 distanceToNextPointFromStartOfSegment += wavelength / 4;
2684 }
2685 }
2686 totalCoveredDistance += segmentLength;
2687
2688 prevX = thisX;
2689 prevY = thisY;
2690 distanceToNextPointFromStartOfSegment -= segmentLength;
2691 }
2692
2693 const double midX = prevX - remainingDistance / 2 * std::sin( segmentAngleRadians );
2694 const double midY = prevY - remainingDistance / 2 * std::cos( segmentAngleRadians );
2695 const double pX = midX + side * amplitude * std::sin( segmentAngleRadians + M_PI_2 );
2696 const double pY = midY + side * amplitude * std::cos( segmentAngleRadians + M_PI_2 );
2697
2698 if ( out->isEmpty() )
2699 {
2700 std::unique_ptr< QgsLineString > bezier( QgsLineString::fromBezierCurve( line->startPoint(),
2701 QgsPoint( ( line->startPoint().x() + pX ) * 0.5, ( line->startPoint().y() + pY ) * 0.5 ),
2702 QgsPoint( ( prevX + pX ) * 0.5, ( prevY + pY ) * 0.5 ),
2703 QgsPoint( prevX, prevY ),
2704 segments ) );
2705 out->append( bezier.get() );
2706 }
2707 else
2708 {
2709 std::unique_ptr< QgsLineString > bezier( QgsLineString::fromBezierCurve( out->endPoint(),
2710 QgsPoint( ( out->endPoint().x() + pX ) * 0.5, ( out->endPoint().y() + pY ) * 0.5 ),
2711 QgsPoint( ( prevX + pX ) * 0.5, ( prevY + pY ) * 0.5 ),
2712 QgsPoint( prevX, prevY ),
2713 segments ) );
2714 out->append( bezier.get() );
2715 }
2716
2717 return out;
2718}
2719
2720std::unique_ptr< QgsAbstractGeometry > roundWavesPrivate( const QgsAbstractGeometry *geom, double wavelength, double amplitude, bool strictWavelength )
2721{
2722 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2723 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2724 {
2725 segmentizedCopy.reset( geom->segmentize() );
2726 geom = segmentizedCopy.get();
2727 }
2728
2730 {
2731 return roundWavesAlongLine( static_cast< const QgsLineString * >( geom ), wavelength, amplitude, strictWavelength );
2732 }
2733 else
2734 {
2735 // polygon
2736 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2737 auto result = std::make_unique< QgsPolygon >();
2738
2739 result->setExteriorRing( roundWavesAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2740 wavelength, amplitude, strictWavelength ).release() );
2741 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2742 {
2743 result->addInteriorRing( roundWavesAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2744 wavelength, amplitude, strictWavelength ).release() );
2745 }
2746
2747 return result;
2748 }
2749}
2750
2751std::unique_ptr< QgsAbstractGeometry > roundWavesRandomizedPrivate( const QgsAbstractGeometry *geom, double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
2752{
2753 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2754 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2755 {
2756 segmentizedCopy.reset( geom->segmentize() );
2757 geom = segmentizedCopy.get();
2758 }
2759
2761 {
2762 return roundWavesRandomizedAlongLine( static_cast< const QgsLineString * >( geom ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt );
2763 }
2764 else
2765 {
2766 // polygon
2767 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2768 auto result = std::make_unique< QgsPolygon >();
2769
2770 result->setExteriorRing( roundWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2771 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2772 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2773 {
2774 result->addInteriorRing( roundWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2775 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2776 }
2777
2778 return result;
2779 }
2780}
2781
2782QgsGeometry QgsInternalGeometryEngine::roundWaves( double wavelength, double amplitude, bool strictWavelength ) const
2783{
2784 if ( wavelength < 0 || qgsDoubleNear( wavelength, 0 ) )
2785 return QgsGeometry();
2786
2787 mLastError.clear();
2788 if ( !mGeometry )
2789 {
2790 return QgsGeometry();
2791 }
2792
2793 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == Qgis::GeometryType::Point )
2794 {
2795 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2796 }
2797
2799 {
2800 int numGeom = gc->numGeometries();
2801 QVector< QgsAbstractGeometry * > geometryList;
2802 geometryList.reserve( numGeom );
2803 for ( int i = 0; i < numGeom; ++i )
2804 {
2805 geometryList << roundWavesPrivate( gc->geometryN( i ), wavelength, amplitude, strictWavelength ).release();
2806 }
2807
2808 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2809 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2810 {
2811 first.addPartV2( g );
2812 }
2813 return first;
2814 }
2815 else
2816 {
2817 return QgsGeometry( roundWavesPrivate( mGeometry, wavelength, amplitude, strictWavelength ) );
2818 }
2819}
2820
2821QgsGeometry QgsInternalGeometryEngine::roundWavesRandomized( double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, unsigned long seed ) const
2822{
2823 if ( minimumWavelength < 0 || qgsDoubleNear( minimumWavelength, 0 ) || maximumWavelength < 0 || qgsDoubleNear( maximumWavelength, 0 ) || maximumWavelength < minimumWavelength )
2824 return QgsGeometry();
2825
2826 mLastError.clear();
2827 if ( !mGeometry )
2828 {
2829 return QgsGeometry();
2830 }
2831
2832 std::random_device rd;
2833 std::mt19937 mt( seed == 0 ? rd() : seed );
2834 std::uniform_real_distribution<> uniformDist( 0, 1 );
2835
2836 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == Qgis::GeometryType::Point )
2837 {
2838 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2839 }
2840
2842 {
2843 int numGeom = gc->numGeometries();
2844 QVector< QgsAbstractGeometry * > geometryList;
2845 geometryList.reserve( numGeom );
2846 for ( int i = 0; i < numGeom; ++i )
2847 {
2848 geometryList << roundWavesRandomizedPrivate( gc->geometryN( i ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release();
2849 }
2850
2851 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2852 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2853 {
2854 first.addPartV2( g );
2855 }
2856 return first;
2857 }
2858 else
2859 {
2860 return QgsGeometry( roundWavesRandomizedPrivate( mGeometry, minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ) );
2861 }
2862}
2863
2864std::unique_ptr< QgsMultiLineString > dashPatternAlongLine( const QgsLineString *line,
2865 const QVector< double> &pattern,
2869 double patternOffset )
2870{
2871 const int totalPoints = line->numPoints();
2872 if ( totalPoints < 2 )
2873 return nullptr;
2874
2875 const int patternSize = pattern.size();
2876
2877 const double *x = line->xData();
2878 const double *y = line->yData();
2879
2880 double prevX = *x++;
2881 double prevY = *y++;
2882
2883 auto result = std::make_unique< QgsMultiLineString >();
2884
2885 QVector< double > outX;
2886 QVector< double > outY;
2887 const double totalLength = line->length();
2888
2889 double patternLength = 0;
2890 double patternDashLength = 0;
2891 double patternGapLength = 0;
2892 for ( int i = 0; i < pattern.size(); ++i )
2893 {
2894 patternLength += pattern.at( i );
2895 if ( i % 2 == 0 )
2896 patternDashLength += pattern.at( i );
2897 else
2898 patternGapLength += pattern.at( i );
2899 }
2900
2901 double firstPatternLength = 0;
2902 double firstPatternDashLength = 0;
2903 double firstPatternGapLength = 0;
2904 switch ( startRule )
2905 {
2908 firstPatternLength = patternLength;
2909 firstPatternDashLength = patternDashLength;
2910 firstPatternGapLength = patternGapLength;
2911 break;
2913 firstPatternLength = patternLength - pattern.at( 0 ) * 0.5; // remove half of first dash
2914 firstPatternDashLength = patternDashLength - pattern.at( 0 ) * 0.5;
2915 firstPatternGapLength = patternGapLength;
2916 break;
2918 firstPatternLength = pattern.at( patternSize - 1 ); // start at LAST dash
2919 firstPatternDashLength = 0;
2920 firstPatternGapLength = pattern.at( patternSize - 1 );
2921 break;
2923 firstPatternLength = pattern.at( patternSize - 1 ) * 0.5; // start at half of last dash
2924 firstPatternDashLength = 0;
2925 firstPatternGapLength = pattern.at( patternSize - 1 ) * 0.5;
2926 break;
2927 }
2928
2929 const bool isSmallEnoughForSinglePattern = ( totalLength - firstPatternLength ) < patternLength * 0.5;
2930
2931 double lastPatternLength = isSmallEnoughForSinglePattern ? firstPatternLength : patternLength;
2932 double lastPatternDashLength = isSmallEnoughForSinglePattern ? firstPatternDashLength : patternDashLength;
2933 double lastPatternGapLength = isSmallEnoughForSinglePattern ? firstPatternGapLength : patternGapLength;
2934 switch ( endRule )
2935 {
2937 lastPatternLength = 0;
2938 lastPatternDashLength = 0;
2939 lastPatternGapLength = 0;
2940 break;
2942 lastPatternLength -= pattern.at( patternSize - 1 ); // remove last gap
2943 lastPatternGapLength -= pattern.at( patternSize - 1 );
2944 break;
2946 lastPatternLength -= pattern.at( patternSize - 1 ) + pattern.at( patternSize - 2 ) * 0.5; // remove last gap, half of last dash
2947 lastPatternDashLength -= pattern.at( patternSize - 2 ) * 0.5;
2948 lastPatternGapLength -= pattern.at( patternSize - 1 );
2949 break;
2951 lastPatternGapLength = patternGapLength;
2952 break;
2954 lastPatternLength -= pattern.at( patternSize - 1 ) * 0.5; // remove half of last gap
2955 lastPatternGapLength -= pattern.at( patternSize - 1 ) * 0.5;
2956 break;
2957 }
2958
2959 const double remainingLengthForCompletePatterns = totalLength - ( !isSmallEnoughForSinglePattern ? firstPatternLength : 0 ) - lastPatternLength;
2960 const int middlePatternRepetitions = std::max( static_cast< int >( std::round( remainingLengthForCompletePatterns / patternLength ) ), 0 );
2961
2962 const double totalUnscaledLengthOfPatterns = ( !isSmallEnoughForSinglePattern ? firstPatternLength : 0 ) + middlePatternRepetitions * patternLength + lastPatternLength;
2963 const double totalUnscaledLengthOfDashes = ( !isSmallEnoughForSinglePattern ? firstPatternDashLength : 0 ) + middlePatternRepetitions * patternDashLength + lastPatternDashLength;
2964 const double totalUnscaledLengthOfGaps = ( !isSmallEnoughForSinglePattern ? firstPatternGapLength : 0 ) + middlePatternRepetitions * patternGapLength + lastPatternGapLength;
2965
2966 double dashLengthScalingFactor = 1;
2967 double gapLengthScalingFactor = 1;
2969 {
2970 // calculate scaling factors
2971 const double lengthToShrinkBy = totalUnscaledLengthOfPatterns - totalLength;
2972
2973 switch ( adjustment )
2974 {
2976 dashLengthScalingFactor = totalLength / totalUnscaledLengthOfPatterns;
2977 gapLengthScalingFactor = dashLengthScalingFactor;
2978 break;
2980 dashLengthScalingFactor = ( totalUnscaledLengthOfDashes - lengthToShrinkBy ) / totalUnscaledLengthOfDashes;
2981 break;
2983 gapLengthScalingFactor = ( totalUnscaledLengthOfGaps - lengthToShrinkBy ) / totalUnscaledLengthOfGaps;
2984 break;
2985 }
2986 }
2987
2988 dashLengthScalingFactor = std::max( dashLengthScalingFactor, 0.0 );
2989 gapLengthScalingFactor = std::max( gapLengthScalingFactor, 0.0 );
2990
2991 const int maxPatterns = middlePatternRepetitions + 2;
2992 result->reserve( maxPatterns );
2993
2994 int patternIndex = 0;
2995 double distanceToNextPointFromStartOfSegment = pattern.at( 0 ) * dashLengthScalingFactor;
2996 bool isDash = true;
2997 switch ( startRule )
2998 {
3001 break;
3003 distanceToNextPointFromStartOfSegment *= 0.5; // skip to half way through first dash
3004 break;
3006 patternIndex = patternSize - 1; // start at last gap
3007 isDash = false;
3008 distanceToNextPointFromStartOfSegment = pattern.at( patternSize - 1 ) * gapLengthScalingFactor;
3009 break;
3011 patternIndex = patternSize - 1; // skip straight to half way through last gap
3012 isDash = false;
3013 distanceToNextPointFromStartOfSegment = 0.5 * pattern.at( patternSize - 1 ) * gapLengthScalingFactor;
3014 break;
3015 }
3016
3017 const double adjustedOffset = fmod( patternOffset, patternLength );
3018 const double scaledOffset = ( adjustedOffset < 0 ? ( adjustedOffset + patternLength ) : adjustedOffset ) * ( gapLengthScalingFactor + dashLengthScalingFactor ) / 2;
3019 if ( !qgsDoubleNear( scaledOffset, 0 ) )
3020 {
3021 // shuffle pattern along by offset
3022 double remainingOffset = scaledOffset;
3023 while ( remainingOffset > 0 )
3024 {
3025 if ( distanceToNextPointFromStartOfSegment > remainingOffset )
3026 {
3027 distanceToNextPointFromStartOfSegment -= remainingOffset;
3028 break;
3029 }
3030
3031 remainingOffset -= distanceToNextPointFromStartOfSegment;
3032 isDash = !isDash;
3033 patternIndex++;
3034 if ( patternIndex == patternSize )
3035 patternIndex = 0;
3036
3037 distanceToNextPointFromStartOfSegment = pattern.at( patternIndex ) * ( isDash ? dashLengthScalingFactor : gapLengthScalingFactor );
3038 }
3039 }
3040
3041 if ( isDash )
3042 {
3043 outX.append( prevX );
3044 outY.append( prevY );
3045 }
3046
3047 for ( int i = 1; i < totalPoints; ++i )
3048 {
3049 double thisX = *x++;
3050 double thisY = *y++;
3051
3052 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
3053 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
3054 {
3055 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
3056 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
3057 double pX, pY;
3058 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
3059
3060 outX.append( pX );
3061 outY.append( pY );
3062 if ( isDash )
3063 {
3064 result->addGeometry( new QgsLineString( outX, outY ) );
3065 outX.resize( 0 );
3066 outY.resize( 0 );
3067 }
3068
3069 isDash = !isDash;
3070 patternIndex++;
3071 if ( patternIndex >= patternSize )
3072 patternIndex = 0;
3073
3074 distanceToNextPointFromStartOfSegment += pattern.at( patternIndex ) * ( isDash ? dashLengthScalingFactor : gapLengthScalingFactor );
3075 }
3076
3077 if ( isDash )
3078 {
3079 outX.append( thisX );
3080 outY.append( thisY );
3081 }
3082
3083 prevX = thisX;
3084 prevY = thisY;
3085 distanceToNextPointFromStartOfSegment -= segmentLength;
3086 }
3087
3088 if ( isDash )
3089 {
3090 outX.append( prevX );
3091 outY.append( prevY );
3092 result->addGeometry( new QgsLineString( outX, outY ) );
3093 }
3094
3095 return result;
3096}
3097
3098std::unique_ptr< QgsAbstractGeometry > applyDashPatternPrivate( const QgsAbstractGeometry *geom,
3099 const QVector<double> &pattern,
3103 double patternOffset )
3104{
3105 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
3106 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
3107 {
3108 segmentizedCopy.reset( geom->segmentize() );
3109 geom = segmentizedCopy.get();
3110 }
3111
3113 {
3114 return dashPatternAlongLine( static_cast< const QgsLineString * >( geom ), pattern, startRule, endRule, adjustment, patternOffset );
3115 }
3116 else
3117 {
3118 // polygon
3119 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
3120 auto result = std::make_unique< QgsMultiLineString >();
3121
3122 std::unique_ptr< QgsMultiLineString > exteriorParts = dashPatternAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ), pattern, startRule, endRule, adjustment, patternOffset );
3123 for ( int i = 0; i < exteriorParts->numGeometries(); ++i )
3124 result->addGeometry( exteriorParts->geometryN( i )->clone() );
3125
3126 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
3127 {
3128 std::unique_ptr< QgsMultiLineString > ringParts = dashPatternAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ), pattern, startRule, endRule, adjustment, patternOffset );
3129 for ( int j = 0; j < ringParts->numGeometries(); ++j )
3130 result->addGeometry( ringParts->geometryN( j )->clone() );
3131 }
3132
3133 return result;
3134 }
3135}
3136
3138{
3139 if ( pattern.size() < 2 )
3140 return QgsGeometry( mGeometry->clone() );
3141
3142 mLastError.clear();
3143 if ( !mGeometry || mGeometry->isEmpty() )
3144 {
3145 return QgsGeometry();
3146 }
3147
3148 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) == Qgis::GeometryType::Point )
3149 {
3150 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
3151 }
3152
3154 {
3155 int numGeom = gc->numGeometries();
3156 QVector< QgsAbstractGeometry * > geometryList;
3157 geometryList.reserve( numGeom );
3158 for ( int i = 0; i < numGeom; ++i )
3159 {
3160 geometryList << applyDashPatternPrivate( gc->geometryN( i ), pattern, startRule, endRule, adjustment, patternOffset ).release();
3161 }
3162
3163 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
3164 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
3165 {
3167 {
3168 for ( int j = 0; j < collection->numGeometries(); ++j )
3169 {
3170 first.addPartV2( collection->geometryN( j )->clone() );
3171 }
3172 delete collection;
3173 }
3174 else
3175 {
3176 first.addPartV2( g );
3177 }
3178 }
3179 return first;
3180 }
3181 else
3182 {
3183 return QgsGeometry( applyDashPatternPrivate( mGeometry, pattern, startRule, endRule, adjustment, patternOffset ) );
3184 }
3185}
DashPatternSizeAdjustment
Dash pattern size adjustment options.
Definition qgis.h:3377
@ ScaleDashOnly
Only dash lengths are adjusted.
Definition qgis.h:3379
@ ScaleBothDashAndGap
Both the dash and gap lengths are adjusted equally.
Definition qgis.h:3378
@ ScaleGapOnly
Only gap lengths are adjusted.
Definition qgis.h:3380
@ Point
Points.
Definition qgis.h:377
@ Line
Lines.
Definition qgis.h:378
@ Polygon
Polygons.
Definition qgis.h:379
DashPatternLineEndingRule
Dash pattern line ending rules.
Definition qgis.h:3362
@ HalfDash
Start or finish the pattern with a half length dash.
Definition qgis.h:3365
@ HalfGap
Start or finish the pattern with a half length gap.
Definition qgis.h:3367
@ FullGap
Start or finish the pattern with a full gap.
Definition qgis.h:3366
@ FullDash
Start or finish the pattern with a full dash.
Definition qgis.h:3364
@ NoRule
No special rule.
Definition qgis.h:3363
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:291
@ CompoundCurve
CompoundCurve.
Definition qgis.h:302
@ LineString
LineString.
Definition qgis.h:294
@ Polygon
Polygon.
Definition qgis.h:295
@ CircularString
CircularString.
Definition qgis.h:301
The vertex_iterator class provides an STL-style iterator for vertices.
Abstract base class for all geometries.
vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry.
virtual const QgsAbstractGeometry * simplifiedTypeRef() const
Returns a reference to the simplest lossless representation of this geometry, e.g.
virtual QgsAbstractGeometry * segmentize(double tolerance=M_PI/180., SegmentationToleranceType toleranceType=MaximumAngle) const
Returns a version of the geometry without curves.
bool isMeasure() const
Returns true if the geometry contains m values.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary const part after the last part of the geometry.
vertex_iterator vertices_begin() const
Returns STL-style iterator pointing to the first vertex of the geometry.
const_part_iterator const_parts_begin() const
Returns STL-style iterator pointing to the const first part of the geometry.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
Circle geometry type.
Definition qgscircle.h:46
Compound curve geometry type.
int nCurves() const
Returns the number of curves in the geometry.
const QgsCurve * curveAt(int i) const
Returns the curve at the specified index.
Curve polygon geometry type.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
virtual int numPoints() const =0
Returns the number of points in the curve.
QgsCurve * segmentize(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a geometry without curves.
Definition qgscurve.cpp:176
virtual bool isClosed() const
Returns true if the curve is closed.
Definition qgscurve.cpp:54
QgsCurve * clone() const override=0
Clones the geometry by performing a deep copy.
virtual QgsPolygon * toPolygon(unsigned int segments=36) const
Returns a segmented polygon.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:55
int partCount() const override
Returns count of parts contained in the geometry.
int numGeometries() const
Returns the number of geometries within the collection.
const QgsAbstractGeometry * geometryN(int n) const
Returns a const reference to a geometry from within the collection.
static void pointOnLineWithDistance(double x1, double y1, double x2, double y2, double distance, double &x, double &y, double *z1=nullptr, double *z2=nullptr, double *z=nullptr, double *m1=nullptr, double *m2=nullptr, double *m=nullptr)
Calculates the point a specified distance from (x1, y1) toward a second point (x2,...
static double distance2D(double x1, double y1, double x2, double y2)
Returns the 2D distance between (x1, y1) and (x2, y2).
static double lineAngle(double x1, double y1, double x2, double y2)
Calculates the direction of line joining two points in radians, clockwise from the north direction.
static void weightedPointInTriangle(double aX, double aY, double bX, double bY, double cX, double cY, double weightB, double weightC, double &pointX, double &pointY)
Returns a weighted point inside the triangle denoted by the points (aX, aY), (bX, bY) and (cX,...
static double triangleArea(double aX, double aY, double bX, double bY, double cX, double cY)
Returns the area of the triangle denoted by the points (aX, aY), (bX, bY) and (cX,...
static int leftOfLine(const double x, const double y, const double x1, const double y1, const double x2, const double y2)
Returns a value < 0 if the point (x, y) is left of the line from (x1, y1) -> (x2, y2).
static void circleCenterRadius(const QgsPoint &pt1, const QgsPoint &pt2, const QgsPoint &pt3, double &radius, double &centerX, double &centerY)
Returns radius and center of the circle through pt1, pt2, pt3.
static bool pointContinuesArc(const QgsPoint &a1, const QgsPoint &a2, const QgsPoint &a3, const QgsPoint &b, double distanceTolerance, double pointSpacingAngleTolerance)
Returns true if point b is on the arc formed by points a1, a2, and a3, but not within that arc portio...
static int circleCircleOuterTangents(const QgsPointXY &center1, double radius1, const QgsPointXY &center2, double radius2, QgsPointXY &line1P1, QgsPointXY &line1P2, QgsPointXY &line2P1, QgsPointXY &line2P2)
Calculates the outer tangent points for two circles, centered at center1 and center2 and with radii o...
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
QgsPointXY closestVertex(const QgsPointXY &point, int &closestVertexIndex, int &previousVertexIndex, int &nextVertexIndex, double &sqrDist) const
Returns the vertex closest to the given point, the corresponding vertex index, squared distance snap ...
bool removeDuplicateNodes(double epsilon=4 *std::numeric_limits< double >::epsilon(), bool useZValues=false)
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a degenerat...
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
Qgis::GeometryOperationResult addPartV2(const QVector< QgsPointXY > &points, Qgis::WkbType wkbType=Qgis::WkbType::Unknown)
Adds a new part to a the geometry.
Qgis::GeometryOperationResult rotate(double rotation, const QgsPointXY &center)
Rotate this geometry around the Z axis.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
Creates and returns a new geometry engine representing the specified geometry using precision on a gr...
Does vector analysis using the GEOS library and handles import, export, and exception handling.
Definition qgsgeos.h:141
QgsGeometry triangularWavesRandomized(double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, unsigned long seed=0) const
Constructs randomized triangular waves along the boundary of the geometry, with the specified wavelen...
QgsGeometry triangularWaves(double wavelength, double amplitude, bool strictWavelength=false) const
Constructs triangular waves along the boundary of the geometry, with the specified wavelength and amp...
QgsInternalGeometryEngine(const QgsGeometry &geometry)
The caller is responsible that the geometry is available and unchanged for the whole lifetime of this...
QgsGeometry roundWaves(double wavelength, double amplitude, bool strictWavelength=false) const
Constructs rounded (sine-like) waves along the boundary of the geometry, with the specified wavelengt...
QgsGeometry poleOfInaccessibility(double precision, double *distanceFromBoundary=nullptr) const
Calculates the approximate pole of inaccessibility for a surface, which is the most distant internal ...
QgsGeometry squareWaves(double wavelength, double amplitude, bool strictWavelength=false) const
Constructs square waves along the boundary of the geometry, with the specified wavelength and amplitu...
QgsGeometry variableWidthBufferByM(int segments) const
Calculates a variable width buffer using the m-values from a (multi)line geometry.
QgsGeometry extrude(double x, double y) const
Will extrude a line or (segmentized) curve by a given offset and return a polygon representation of i...
QgsGeometry roundWavesRandomized(double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, unsigned long seed=0) const
Constructs randomized rounded (sine-like) waves along the boundary of the geometry,...
QgsGeometry orthogonalize(double tolerance=1.0E-8, int maxIterations=1000, double angleThreshold=15.0) const
Attempts to orthogonalize a line or polygon geometry by shifting vertices to make the geometries angl...
QgsGeometry variableWidthBuffer(int segments, const std::function< std::unique_ptr< double[] >(const QgsLineString *line) > &widthFunction) const
Calculates a variable width buffer for a (multi)curve geometry.
QString lastError() const
Returns an error string referring to the last error encountered.
QgsGeometry orientedMinimumBoundingBox(double &area, double &angle, double &width, double &height) const
Returns the oriented minimum bounding box for the geometry, which is the smallest (by area) rotated r...
QgsGeometry densifyByDistance(double distance) const
Densifies the geometry by adding regularly placed extra nodes inside each segment so that the maximum...
QgsGeometry taperedBuffer(double startWidth, double endWidth, int segments) const
Calculates a tapered width buffer for a (multi)curve geometry.
QVector< QgsPointXY > randomPointsInPolygon(int count, const std::function< bool(const QgsPointXY &) > &acceptPoint, unsigned long seed=0, QgsFeedback *feedback=nullptr, int maxTriesPerPoint=0)
Returns a list of count random points generated inside a polygon geometry (if acceptPoint is specifie...
QgsGeometry densifyByCount(int extraNodesPerSegment) const
Densifies the geometry by adding the specified number of extra nodes within each segment of the geome...
QgsGeometry applyDashPattern(const QVector< double > &pattern, Qgis::DashPatternLineEndingRule startRule=Qgis::DashPatternLineEndingRule::NoRule, Qgis::DashPatternLineEndingRule endRule=Qgis::DashPatternLineEndingRule::NoRule, Qgis::DashPatternSizeAdjustment adjustment=Qgis::DashPatternSizeAdjustment::ScaleBothDashAndGap, double patternOffset=0) const
Applies a dash pattern to a geometry, returning a MultiLineString geometry which is the input geometr...
QgsGeometry squareWavesRandomized(double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, unsigned long seed=0) const
Constructs randomized square waves along the boundary of the geometry, with the specified wavelength ...
QgsGeometry convertToCurves(double distanceTolerance, double angleTolerance) const
Attempts to convert a non-curved geometry into a curved geometry type (e.g.
bool isAxisParallelRectangle(double maximumDeviation, bool simpleRectanglesOnly=false) const
Returns true if the geometry is a polygon that is almost an axis-parallel rectangle.
Represents a single 2D line segment, consisting of a 2D start and end vertex only.
int pointLeftOfLine(const QgsPointXY &point) const
Tests if a point is to the left of the line segment.
double endY() const
Returns the segment's end y-coordinate.
QgsPointXY end() const
Returns the segment's end point.
double endX() const
Returns the segment's end x-coordinate.
QgsPointXY start() const
Returns the segment's start point.
double startX() const
Returns the segment's start x-coordinate.
void reverse()
Reverses the line segment, so that the start and end points are flipped.
double startY() const
Returns the segment's start y-coordinate.
Line string geometry type, with support for z-dimension and m-values.
static std::unique_ptr< QgsLineString > fromBezierCurve(const QgsPoint &start, const QgsPoint &controlPoint1, const QgsPoint &controlPoint2, const QgsPoint &end, int segments=30)
Returns a new linestring created by segmentizing the bezier curve between start and end,...
bool isClosed() const override
Returns true if the curve is closed.
const double * yData() const
Returns a const pointer to the y vertex data.
const double * xData() const
Returns a const pointer to the x vertex data.
double length() const override
Returns the planar, 2-dimensional length of the geometry.
QgsPoint startPoint() const override
Returns the starting point of the curve.
int numPoints() const override
Returns the number of points in the curve.
QgsPoint pointN(int i) const
Returns the specified point from inside the line string.
int nCoordinates() const override
Returns the number of nodes contained in the geometry.
double yAt(int index) const override
Returns the y-coordinate of the specified node in the line string.
void setYAt(int index, double y)
Sets the y-coordinate of the specified node in the line string.
double mAt(int index) const override
Returns the m value of the specified node in the line string.
void setXAt(int index, double x)
Sets the x-coordinate of the specified node in the line string.
QgsLineString * clone() const override
Clones the geometry by performing a deep copy.
double zAt(int index) const override
Returns the z-coordinate of the specified node in the line string.
double xAt(int index) const override
Returns the x-coordinate of the specified node in the line string.
Multi curve geometry collection.
Multi polygon geometry collection.
Multi surface geometry collection.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
QgsPoint * clone() const override
Clones the geometry by performing a deep copy.
Definition qgspoint.cpp:129
double x
Definition qgspoint.h:56
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:760
double distance(double x, double y) const
Returns the Cartesian 2D distance between this point and a specified x, y coordinate.
Definition qgspoint.h:449
double y
Definition qgspoint.h:57
Polygon geometry type.
Definition qgspolygon.h:37
void setExteriorRing(QgsCurve *ring) override
Sets the exterior ring of the polygon.
void addInteriorRing(QgsCurve *ring) override
Adds an interior ring to the geometry (takes ownership).
bool intersects(const QgsLineSegment2D &segment, QgsPointXY &intersectPoint) const
Finds the closest intersection point of the ray and a line segment.
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
Surface geometry type.
Definition qgssurface.h:34
Tessellates polygons into triangles.
Q_DECL_DEPRECATED QVector< float > data() const
Returns array of triangle vertex data.
void setBounds(const QgsRectangle &bounds)
Sets scaling and the bounds of the input geometry coordinates.
void addPolygon(const QgsPolygon &polygon, float extrusionHeight)
Tessellates a triangle and adds its vertex entries to the output data array.
QString error() const
Returns a descriptive error string if the tessellation failed.
int dataVerticesCount() const
Returns the number of vertices stored in the output data array.
void setInputZValueIgnored(bool ignore)
Sets whether Z values from the input geometries are ignored (true) or not (false).
Represent a 2-dimensional vector.
Definition qgsvector.h:34
double lengthSquared() const
Returns the length of the vector.
Definition qgsvector.h:138
double crossProduct(QgsVector v) const
Returns the 2D cross product of this vector and another vector v.
Definition qgsvector.h:192
QgsVector normalized() const
Returns the vector's normalized (or "unit") vector (ie same angle but length of 1....
Definition qgsvector.cpp:33
double length() const
Returns the length of the vector.
Definition qgsvector.h:128
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Q_INVOKABLE bool isCurvedType(Qgis::WkbType type)
Returns true if the WKB type is a curved type or can contain curved geometries.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
Contains geos related utilities and functions.
Definition qgsgeos.h:77
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:6950
T qgsgeometry_cast(QgsAbstractGeometry *geom)
QVector< QgsPointXY > randomPointsInPolygonGeosBackend(const QgsAbstractGeometry *geometry, int count, const std::function< bool(const QgsPointXY &) > &acceptPoint, unsigned long seed, QgsFeedback *feedback, int maxTriesPerPoint, QString &error)
std::unique_ptr< QgsCurve > lineToCurve(const QgsCurve *curve, double distanceTolerance, double pointSpacingAngleTolerance)
bool matchesOrientation(std::array< Direction, 4 > dirs, std::array< Direction, 4 > oriented)
double normalizedDotProduct(const QgsPoint &a, const QgsPoint &b, const QgsPoint &c)
bool isClockwise(std::array< Direction, 4 > dirs)
Checks whether the 4 directions in dirs make up a clockwise rectangle.
std::unique_ptr< QgsLineString > squareWavesRandomizedAlongLine(const QgsLineString *line, const double minimumWavelength, const double maximumWavelength, const double minimumAmplitude, const double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt)
std::unique_ptr< QgsLineString > squareWavesAlongLine(const QgsLineString *line, double wavelength, const double amplitude, const bool strictWavelength)
QgsAbstractGeometry * orthogonalizeGeom(const QgsAbstractGeometry *geom, int maxIterations, double tolerance, double lowerThreshold, double upperThreshold)
QVector< QgsPointXY > randomPointsInPolygonPoly2TriBackend(const QgsAbstractGeometry *geometry, int count, const std::function< bool(const QgsPointXY &) > &acceptPoint, unsigned long seed, QgsFeedback *feedback, int maxTriesPerPoint, QString &error)
std::unique_ptr< QgsLineString > triangularWavesRandomizedAlongLine(const QgsLineString *line, const double minimumWavelength, const double maximumWavelength, const double minimumAmplitude, const double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt)
QgsLineString * doDensify(const QgsLineString *ring, int extraNodesPerSegment=-1, double distance=1)
Direction getEdgeDirection(const QgsPoint &p1, const QgsPoint &p2, double maxDev)
Determines the direction of an edge from p1 to p2.
std::unique_ptr< QgsAbstractGeometry > applyDashPatternPrivate(const QgsAbstractGeometry *geom, const QVector< double > &pattern, Qgis::DashPatternLineEndingRule startRule, Qgis::DashPatternLineEndingRule endRule, Qgis::DashPatternSizeAdjustment adjustment, double patternOffset)
std::unique_ptr< QgsLineString > roundWavesRandomizedAlongLine(const QgsLineString *line, const double minimumWavelength, const double maximumWavelength, const double minimumAmplitude, const double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt)
QgsVector calcMotion(const QgsPoint &a, const QgsPoint &b, const QgsPoint &c, double lowerThreshold, double upperThreshold)
QVector< QgsPointXY > generateSegmentCurve(const QgsPoint &center1, const double radius1, const QgsPoint &center2, const double radius2)
bool dotProductWithinAngleTolerance(double dotProduct, double lowerThreshold, double upperThreshold)
QgsLineString * doOrthogonalize(QgsLineString *ring, int iterations, double tolerance, double lowerThreshold, double upperThreshold)
double squareness(QgsLineString *ring, double lowerThreshold, double upperThreshold)
std::unique_ptr< QgsMultiLineString > dashPatternAlongLine(const QgsLineString *line, const QVector< double > &pattern, Qgis::DashPatternLineEndingRule startRule, Qgis::DashPatternLineEndingRule endRule, Qgis::DashPatternSizeAdjustment adjustment, double patternOffset)
std::unique_ptr< QgsAbstractGeometry > convertGeometryToCurves(const QgsAbstractGeometry *geom, double distanceTolerance, double angleTolerance)
std::unique_ptr< QgsAbstractGeometry > roundWavesPrivate(const QgsAbstractGeometry *geom, double wavelength, double amplitude, bool strictWavelength)
bool isCounterClockwise(std::array< Direction, 4 > dirs)
Checks whether the 4 directions in dirs make up a counter-clockwise rectangle.
QgsAbstractGeometry * densifyGeometry(const QgsAbstractGeometry *geom, int extraNodesPerSegment=1, double distance=1)
std::unique_ptr< QgsLineString > roundWavesAlongLine(const QgsLineString *line, double wavelength, const double amplitude, const bool strictWavelength)
std::pair< bool, std::array< Direction, 4 > > getEdgeDirections(const QgsPolygon *g, double maxDev)
Checks whether the polygon consists of four nearly axis-parallel sides.
std::unique_ptr< QgsAbstractGeometry > squareWavesRandomizedPrivate(const QgsAbstractGeometry *geom, double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt)
std::unique_ptr< QgsAbstractGeometry > triangularWavesRandomizedPrivate(const QgsAbstractGeometry *geom, double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt)
std::unique_ptr< QgsAbstractGeometry > triangularWavesPrivate(const QgsAbstractGeometry *geom, double wavelength, double amplitude, bool strictWavelength)
std::unique_ptr< QgsAbstractGeometry > squareWavesPrivate(const QgsAbstractGeometry *geom, double wavelength, double amplitude, bool strictWavelength)
std::unique_ptr< QgsLineString > triangularWavesAlongLine(const QgsLineString *line, double wavelength, const double amplitude, const bool strictWavelength)
std::unique_ptr< QgsAbstractGeometry > roundWavesRandomizedPrivate(const QgsAbstractGeometry *geom, double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt)
QLineF segment(int index, QRectF rect, double radius)
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:34