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