QGIS API Documentation 3.39.0-Master (47f7b3a4989)
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
1234 if ( const QgsMultiSurface *ms = qgsgeometry_cast< const QgsMultiSurface * >( geometry ) )
1235 {
1236 for ( int i = 0; i < ms->numGeometries(); ++i )
1237 {
1238 if ( feedback && feedback->isCanceled() )
1239 return QVector< QgsPointXY >();
1240
1241 if ( QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( ms->geometryN( i ) ) )
1242 {
1243 t.addPolygon( *poly, 0 );
1244 }
1245 else
1246 {
1247 std::unique_ptr< QgsPolygon > p( qgsgeometry_cast< QgsPolygon * >( ms->geometryN( i )->segmentize() ) );
1248 t.addPolygon( *p, 0 );
1249 }
1250 }
1251 }
1252 else
1253 {
1254 if ( const QgsPolygon *poly = qgsgeometry_cast< const QgsPolygon * >( geometry ) )
1255 {
1256 t.addPolygon( *poly, 0 );
1257 }
1258 else
1259 {
1260 std::unique_ptr< QgsPolygon > p( qgsgeometry_cast< QgsPolygon * >( geometry->segmentize() ) );
1261 t.addPolygon( *p, 0 );
1262 }
1263 }
1264
1265 if ( feedback && feedback->isCanceled() )
1266 return QVector< QgsPointXY >();
1267
1268 if ( !t.error().isEmpty() )
1269 {
1270 error = t.error();
1271 return QVector< QgsPointXY >();
1272 }
1273
1274 const QVector<float> triangleData = t.data();
1275 if ( triangleData.empty() )
1276 return QVector< QgsPointXY >(); //hm
1277
1278 // calculate running sum of triangle areas
1279 std::vector< double > cumulativeAreas;
1280 cumulativeAreas.reserve( t.dataVerticesCount() / 3 );
1281 double totalArea = 0;
1282 for ( auto it = triangleData.constBegin(); it != triangleData.constEnd(); )
1283 {
1284 if ( feedback && feedback->isCanceled() )
1285 return QVector< QgsPointXY >();
1286
1287 const float aX = *it++;
1288 ( void )it++; // z
1289 const float aY = -( *it++ );
1290 const float bX = *it++;
1291 ( void )it++; // z
1292 const float bY = -( *it++ );
1293 const float cX = *it++;
1294 ( void )it++; // z
1295 const float cY = -( *it++ );
1296
1297 const double area = QgsGeometryUtilsBase::triangleArea( aX, aY, bX, bY, cX, cY );
1298 totalArea += area;
1299 cumulativeAreas.emplace_back( totalArea );
1300 }
1301
1302 std::random_device rd;
1303 std::mt19937 mt( seed == 0 ? rd() : seed );
1304 std::uniform_real_distribution<> uniformDist( 0, 1 );
1305
1306 // selects a random triangle, weighted by triangle area
1307 auto selectRandomTriangle = [&cumulativeAreas, totalArea]( double random )->int
1308 {
1309 int triangle = 0;
1310 const double target = random * totalArea;
1311 for ( auto it = cumulativeAreas.begin(); it != cumulativeAreas.end(); ++it, triangle++ )
1312 {
1313 if ( *it > target )
1314 return triangle;
1315 }
1316 Q_ASSERT_X( false, "QgsInternalGeometryEngine::randomPointsInPolygon", "Invalid random triangle index" );
1317 return 0; // no warnings
1318 };
1319
1320
1321 QVector<QgsPointXY> result;
1322 result.reserve( count );
1323 int tries = 0;
1324 for ( int i = 0; i < count; )
1325 {
1326 if ( feedback && feedback->isCanceled() )
1327 return QVector< QgsPointXY >();
1328
1329 const double triangleIndexRnd = uniformDist( mt );
1330 // pick random triangle, weighted by triangle area
1331 const int triangleIndex = selectRandomTriangle( triangleIndexRnd );
1332
1333 // generate a random point inside this triangle
1334 const double weightB = uniformDist( mt );
1335 const double weightC = uniformDist( mt );
1336 double x;
1337 double y;
1338
1339 // get triangle
1340 const double aX = triangleData.at( triangleIndex * 9 ) + bounds.xMinimum();
1341 const double aY = -triangleData.at( triangleIndex * 9 + 2 ) + bounds.yMinimum();
1342 const double bX = triangleData.at( triangleIndex * 9 + 3 ) + bounds.xMinimum();
1343 const double bY = -triangleData.at( triangleIndex * 9 + 5 ) + bounds.yMinimum();
1344 const double cX = triangleData.at( triangleIndex * 9 + 6 ) + bounds.xMinimum();
1345 const double cY = -triangleData.at( triangleIndex * 9 + 8 ) + bounds.yMinimum();
1346 QgsGeometryUtilsBase::weightedPointInTriangle( aX, aY, bX, bY, cX, cY, weightB, weightC, x, y );
1347
1348 QgsPointXY candidate( x, y );
1349 if ( acceptPoint( candidate ) )
1350 {
1351 result << QgsPointXY( x, y );
1352 i++;
1353 tries = 0;
1354 }
1355 else if ( maxTriesPerPoint != 0 )
1356 {
1357 tries++;
1358 // Skip this point if maximum tries is reached
1359 if ( tries == maxTriesPerPoint )
1360 {
1361 tries = 0;
1362 i++;
1363 }
1364 }
1365 }
1366 return result;
1367}
1368
1369QVector<QgsPointXY> randomPointsInPolygonGeosBackend( const QgsAbstractGeometry *geometry, int count,
1370 const std::function< bool( const QgsPointXY & ) > &acceptPoint, unsigned long seed, QgsFeedback *feedback, int maxTriesPerPoint, QString &error )
1371{
1372 // step 1 - tessellate the polygon to triangles
1373 QgsGeos geos( geometry );
1374 std::unique_ptr<QgsAbstractGeometry> triangulation = geos.constrainedDelaunayTriangulation( &error );
1375 if ( !triangulation || triangulation->isEmpty( ) )
1376 return {};
1377
1378 if ( feedback && feedback->isCanceled() )
1379 return {};
1380
1381 const QgsMultiPolygon *mp = qgsgeometry_cast< const QgsMultiPolygon * >( triangulation.get() );
1382 if ( !mp )
1383 return {};
1384
1385 // calculate running sum of triangle areas
1386 std::vector< double > cumulativeAreas;
1387 cumulativeAreas.reserve( mp->numGeometries() );
1388 double totalArea = 0;
1389 std::vector< double > vertices( static_cast< std::size_t >( mp->numGeometries() ) * 6 );
1390 double *vertexData = vertices.data();
1391 for ( auto it = mp->const_parts_begin(); it != mp->const_parts_end(); ++it )
1392 {
1393 if ( feedback && feedback->isCanceled() )
1394 return {};
1395
1396 const QgsPolygon *part = qgsgeometry_cast< const QgsPolygon * >( *it );
1397 if ( !part )
1398 return {};
1399
1400 const QgsLineString *exterior = qgsgeometry_cast< const QgsLineString * >( part->exteriorRing() );
1401 if ( !exterior )
1402 return {};
1403
1404 const double aX = exterior->xAt( 0 );
1405 const double aY = exterior->yAt( 0 );
1406 const double bX = exterior->xAt( 1 );
1407 const double bY = exterior->yAt( 1 );
1408 const double cX = exterior->xAt( 2 );
1409 const double cY = exterior->yAt( 2 );
1410
1411 const double area = QgsGeometryUtilsBase::triangleArea( aX, aY, bX, bY, cX, cY );
1412 *vertexData++ = aX;
1413 *vertexData++ = aY;
1414 *vertexData++ = bX;
1415 *vertexData++ = bY;
1416 *vertexData++ = cX;
1417 *vertexData++ = cY;
1418 totalArea += area;
1419 cumulativeAreas.emplace_back( totalArea );
1420 }
1421
1422 std::random_device rd;
1423 std::mt19937 mt( seed == 0 ? rd() : seed );
1424 std::uniform_real_distribution<> uniformDist( 0, 1 );
1425
1426 // selects a random triangle, weighted by triangle area
1427 auto selectRandomTriangle = [&cumulativeAreas, totalArea]( double random )->int
1428 {
1429 int triangle = 0;
1430 const double target = random * totalArea;
1431 for ( auto it = cumulativeAreas.begin(); it != cumulativeAreas.end(); ++it, triangle++ )
1432 {
1433 if ( *it > target )
1434 return triangle;
1435 }
1436 Q_ASSERT_X( false, "QgsInternalGeometryEngine::randomPointsInPolygon", "Invalid random triangle index" );
1437 return 0; // no warnings
1438 };
1439
1440
1441 QVector<QgsPointXY> result;
1442 result.reserve( count );
1443 int tries = 0;
1444 vertexData = vertices.data();
1445 for ( int i = 0; i < count; )
1446 {
1447 if ( feedback && feedback->isCanceled() )
1448 return QVector< QgsPointXY >();
1449
1450 const double triangleIndexRnd = uniformDist( mt );
1451 // pick random triangle, weighted by triangle area
1452 const std::size_t triangleIndex = selectRandomTriangle( triangleIndexRnd );
1453
1454 // generate a random point inside this triangle
1455 const double weightB = uniformDist( mt );
1456 const double weightC = uniformDist( mt );
1457 double x;
1458 double y;
1459
1460 // get triangle data
1461 const double aX = vertexData[ triangleIndex * 6 ];
1462 const double aY = vertexData[ triangleIndex * 6 + 1 ];
1463 const double bX = vertexData[ triangleIndex * 6 + 2 ];
1464 const double bY = vertexData[ triangleIndex * 6 + 3 ];
1465 const double cX = vertexData[ triangleIndex * 6 + 4 ];
1466 const double cY = vertexData[ triangleIndex * 6 + 5 ];
1467
1468 QgsGeometryUtilsBase::weightedPointInTriangle( aX, aY, bX, bY, cX, cY, weightB, weightC, x, y );
1469
1470 QgsPointXY candidate( x, y );
1471 if ( acceptPoint( candidate ) )
1472 {
1473 result << QgsPointXY( x, y );
1474 i++;
1475 tries = 0;
1476 }
1477 else if ( maxTriesPerPoint != 0 )
1478 {
1479 tries++;
1480 // Skip this point if maximum tries is reached
1481 if ( tries == maxTriesPerPoint )
1482 {
1483 tries = 0;
1484 i++;
1485 }
1486 }
1487 }
1488 return result;
1489}
1490
1492 const std::function< bool( const QgsPointXY & ) > &acceptPoint, unsigned long seed, QgsFeedback *feedback, int maxTriesPerPoint )
1493{
1494 if ( QgsWkbTypes::geometryType( mGeometry->wkbType() ) != Qgis::GeometryType::Polygon || count == 0 )
1495 return QVector< QgsPointXY >();
1496
1497 // prefer more stable GEOS implementation if available
1498#if (GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR>=11) || GEOS_VERSION_MAJOR>3
1499 return randomPointsInPolygonGeosBackend( mGeometry, count, acceptPoint, seed, feedback, maxTriesPerPoint, mLastError );
1500#else
1501 return randomPointsInPolygonPoly2TriBackend( mGeometry, count, acceptPoint, seed, feedback, maxTriesPerPoint, mLastError );
1502#endif
1503}
1504
1505// ported from PostGIS' lwgeom pta_unstroke
1506
1507std::unique_ptr< QgsCurve > lineToCurve( const QgsCurve *curve, double distanceTolerance,
1508 double pointSpacingAngleTolerance )
1509{
1510 const Qgis::WkbType flatType = QgsWkbTypes::flatType( curve->wkbType() );
1511 if ( flatType == Qgis::WkbType::CircularString )
1512 {
1513 // already curved, so just return a copy
1514 std::unique_ptr< QgsCircularString > out;
1515 out.reset( qgsgeometry_cast< QgsCircularString * >( curve )->clone() );
1516 return out;
1517 }
1518 else if ( flatType == Qgis::WkbType::CompoundCurve )
1519 {
1520 std::unique_ptr< QgsCompoundCurve > out = std::make_unique< QgsCompoundCurve >();
1521 const QgsCompoundCurve *in = qgsgeometry_cast< const QgsCompoundCurve * >( curve );
1522 for ( int i = 0; i < in->nCurves(); i ++ )
1523 {
1524 std::unique_ptr< QgsCurve > processed = lineToCurve( in->curveAt( i ), distanceTolerance, pointSpacingAngleTolerance );
1525 if ( processed )
1526 {
1527 if ( const QgsCompoundCurve *processedCompoundCurve = qgsgeometry_cast< const QgsCompoundCurve *>( processed.get() ) )
1528 {
1529 for ( int i = 0; i < processedCompoundCurve->nCurves(); ++i )
1530 {
1531 out->addCurve( processedCompoundCurve->curveAt( i )->clone() );
1532 }
1533 }
1534 else
1535 {
1536 out->addCurve( processed.release() );
1537 }
1538 }
1539 }
1540 return out;
1541 }
1542 else if ( flatType == Qgis::WkbType::LineString )
1543 {
1544 const QgsLineString *lineString = qgsgeometry_cast< QgsLineString * >( curve );
1545
1546 std::unique_ptr< QgsCompoundCurve > out = std::make_unique< QgsCompoundCurve >();
1547
1548 /* Minimum number of edges, per quadrant, required to define an arc */
1549 const unsigned int minQuadEdges = 2;
1550
1551 /* Die on null input */
1552 if ( !lineString )
1553 return nullptr;
1554
1555 /* Null on empty input? */
1556 if ( lineString->nCoordinates() == 0 )
1557 return nullptr;
1558
1559 /* We can't desegmentize anything shorter than four points */
1560 if ( lineString->nCoordinates() < 4 )
1561 {
1562 out->addCurve( lineString->clone() );
1563 return out;
1564 }
1565
1566 /* Allocate our result array of vertices that are part of arcs */
1567 int numEdges = lineString->nCoordinates() - 1;
1568 QVector< int > edgesInArcs( numEdges + 1, 0 );
1569
1570 auto arcAngle = []( const QgsPoint & a, const QgsPoint & b, const QgsPoint & c )->double
1571 {
1572 double abX = b.x() - a.x();
1573 double abY = b.y() - a.y();
1574
1575 double cbX = b.x() - c.x();
1576 double cbY = b.y() - c.y();
1577
1578 double dot = ( abX * cbX + abY * cbY ); /* dot product */
1579 double cross = ( abX * cbY - abY * cbX ); /* cross product */
1580
1581 double alpha = std::atan2( cross, dot );
1582
1583 return alpha;
1584 };
1585
1586 /* We make a candidate arc of the first two edges, */
1587 /* And then see if the next edge follows it */
1588 int i = 0;
1589 int j = 0;
1590 int k = 0;
1591 int currentArc = 1;
1592 QgsPoint a1;
1593 QgsPoint a2;
1594 QgsPoint a3;
1595 QgsPoint b;
1596 double centerX = 0.0;
1597 double centerY = 0.0;
1598 double radius = 0;
1599
1600 while ( i < numEdges - 2 )
1601 {
1602 unsigned int arcEdges = 0;
1603 double numQuadrants = 0;
1604 double angle;
1605
1606 bool foundArc = false;
1607 /* Make candidate arc */
1608 a1 = lineString->pointN( i );
1609 a2 = lineString->pointN( i + 1 );
1610 a3 = lineString->pointN( i + 2 );
1611 QgsPoint first = a1;
1612
1613 for ( j = i + 3; j < numEdges + 1; j++ )
1614 {
1615 b = lineString->pointN( j );
1616
1617 /* Does this point fall on our candidate arc? */
1618 if ( QgsGeometryUtils::pointContinuesArc( a1, a2, a3, b, distanceTolerance, pointSpacingAngleTolerance ) )
1619 {
1620 /* Yes. Mark this edge and the two preceding it as arc components */
1621 foundArc = true;
1622 for ( k = j - 1; k > j - 4; k-- )
1623 edgesInArcs[k] = currentArc;
1624 }
1625 else
1626 {
1627 /* No. So we're done with this candidate arc */
1628 currentArc++;
1629 break;
1630 }
1631
1632 a1 = a2;
1633 a2 = a3;
1634 a3 = b;
1635 }
1636 /* Jump past all the edges that were added to the arc */
1637 if ( foundArc )
1638 {
1639 /* Check if an arc was composed by enough edges to be
1640 * really considered an arc
1641 * See http://trac.osgeo.org/postgis/ticket/2420
1642 */
1643 arcEdges = j - 1 - i;
1644 if ( first.x() == b.x() && first.y() == b.y() )
1645 {
1646 numQuadrants = 4;
1647 }
1648 else
1649 {
1650 QgsGeometryUtils::circleCenterRadius( first, b, a1, radius, centerX, centerY );
1651
1652 angle = arcAngle( first, QgsPoint( centerX, centerY ), b );
1653 int p2Side = QgsGeometryUtilsBase::leftOfLine( b.x(), b.y(), first.x(), first.y(), a1.x(), a1.y() );
1654 if ( p2Side >= 0 )
1655 angle = -angle;
1656
1657 if ( angle < 0 )
1658 angle = 2 * M_PI + angle;
1659 numQuadrants = ( 4 * angle ) / ( 2 * M_PI );
1660 }
1661 /* a1 is first point, b is last point */
1662 if ( arcEdges < minQuadEdges * numQuadrants )
1663 {
1664 // LWDEBUGF( 4, "Not enough edges for a %g quadrants arc, %g needed", num_quadrants, min_quad_edges * num_quadrants );
1665 for ( k = j - 1; k >= i; k-- )
1666 edgesInArcs[k] = 0;
1667 }
1668
1669 i = j - 1;
1670 }
1671 else
1672 {
1673 /* Mark this edge as a linear edge */
1674 edgesInArcs[i] = 0;
1675 i = i + 1;
1676 }
1677 }
1678
1679 int start = 0;
1680 int end = 0;
1681 /* non-zero if edge is part of an arc */
1682 int edgeType = edgesInArcs[0];
1683
1684 auto addPointsToCurve = [ lineString, &out ]( int start, int end, int type )
1685 {
1686 if ( type == 0 )
1687 {
1688 // straight segment
1689 QVector< QgsPoint > points;
1690 for ( int j = start; j < end + 2; ++ j )
1691 {
1692 points.append( lineString->pointN( j ) );
1693 }
1694 std::unique_ptr< QgsCurve > straightSegment = std::make_unique< QgsLineString >( points );
1695 out->addCurve( straightSegment.release() );
1696 }
1697 else
1698 {
1699 // curved segment
1700 QVector< QgsPoint > points;
1701 points.append( lineString->pointN( start ) );
1702 points.append( lineString->pointN( ( start + end + 1 ) / 2 ) );
1703 points.append( lineString->pointN( end + 1 ) );
1704 std::unique_ptr< QgsCircularString > curvedSegment = std::make_unique< QgsCircularString >();
1705 curvedSegment->setPoints( points );
1706 out->addCurve( curvedSegment.release() );
1707 }
1708 };
1709
1710 for ( int i = 1; i < numEdges; i++ )
1711 {
1712 if ( edgeType != edgesInArcs[i] )
1713 {
1714 end = i - 1;
1715 addPointsToCurve( start, end, edgeType );
1716 start = i;
1717 edgeType = edgesInArcs[i];
1718 }
1719 }
1720
1721 /* Roll out last item */
1722 end = numEdges - 1;
1723 addPointsToCurve( start, end, edgeType );
1724
1725 // return a simplified type if it doesn't need to be a compound curve resu
1726 if ( QgsWkbTypes::flatType( out->simplifiedTypeRef()->wkbType() ) == Qgis::WkbType::CircularString )
1727 {
1728 std::unique_ptr< QgsCircularString > res;
1729 res.reset( qgsgeometry_cast< const QgsCircularString * >( out->simplifiedTypeRef() )->clone() );
1730 return res;
1731 }
1732
1733 return out;
1734 }
1735
1736 return nullptr;
1737}
1738
1739std::unique_ptr< QgsAbstractGeometry > convertGeometryToCurves( const QgsAbstractGeometry *geom, double distanceTolerance, double angleTolerance )
1740{
1742 {
1743 return lineToCurve( qgsgeometry_cast< const QgsCurve * >( geom ), distanceTolerance, angleTolerance );
1744 }
1745 else
1746 {
1747 // polygon
1748 const QgsCurvePolygon *polygon = qgsgeometry_cast< const QgsCurvePolygon * >( geom );
1749 std::unique_ptr< QgsCurvePolygon > result = std::make_unique< QgsCurvePolygon>();
1750
1751 result->setExteriorRing( lineToCurve( polygon->exteriorRing(),
1752 distanceTolerance, angleTolerance ).release() );
1753 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
1754 {
1755 result->addInteriorRing( lineToCurve( polygon->interiorRing( i ),
1756 distanceTolerance, angleTolerance ).release() );
1757 }
1758
1759 return result;
1760 }
1761}
1762
1763QgsGeometry QgsInternalGeometryEngine::convertToCurves( double distanceTolerance, double angleTolerance ) const
1764{
1765 mLastError.clear();
1766 if ( !mGeometry )
1767 {
1768 return QgsGeometry();
1769 }
1770
1772 {
1773 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
1774 }
1775
1776 if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( mGeometry ) )
1777 {
1778 int numGeom = gc->numGeometries();
1779 QVector< QgsAbstractGeometry * > geometryList;
1780 geometryList.reserve( numGeom );
1781 for ( int i = 0; i < numGeom; ++i )
1782 {
1783 geometryList << convertGeometryToCurves( gc->geometryN( i ), distanceTolerance, angleTolerance ).release();
1784 }
1785
1786 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
1787 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
1788 {
1789 first.addPartV2( g );
1790 }
1791 return first;
1792 }
1793 else
1794 {
1795 return QgsGeometry( convertGeometryToCurves( mGeometry, distanceTolerance, angleTolerance ) );
1796 }
1797}
1798
1799QgsGeometry QgsInternalGeometryEngine::orientedMinimumBoundingBox( double &area, double &angle, double &width, double &height ) const
1800{
1801 mLastError.clear();
1802
1803 QgsRectangle minRect;
1804 area = std::numeric_limits<double>::max();
1805 angle = 0;
1806 width = std::numeric_limits<double>::max();
1807 height = std::numeric_limits<double>::max();
1808
1809 if ( !mGeometry || mGeometry->nCoordinates() < 2 )
1810 return QgsGeometry();
1811
1812 std::unique_ptr< QgsGeometryEngine >engine( QgsGeometry::createGeometryEngine( mGeometry ) );
1813 std::unique_ptr< QgsAbstractGeometry > hull( engine->convexHull( &mLastError ) );
1814 if ( !hull )
1815 return QgsGeometry();
1816
1817 QgsVertexId vertexId;
1818 QgsPoint pt0;
1819 QgsPoint pt1;
1820 QgsPoint pt2;
1821 // get first point
1822 hull->nextVertex( vertexId, pt0 );
1823 pt1 = pt0;
1824 double totalRotation = 0;
1825 while ( hull->nextVertex( vertexId, pt2 ) )
1826 {
1827 double currentAngle = QgsGeometryUtilsBase::lineAngle( pt1.x(), pt1.y(), pt2.x(), pt2.y() );
1828 double rotateAngle = 180.0 / M_PI * currentAngle;
1829 totalRotation += rotateAngle;
1830
1831 QTransform t = QTransform::fromTranslate( pt0.x(), pt0.y() );
1832 t.rotate( rotateAngle );
1833 t.translate( -pt0.x(), -pt0.y() );
1834
1835 hull->transform( t );
1836
1837 QgsRectangle bounds = hull->boundingBox();
1838 double currentArea = bounds.width() * bounds.height();
1839 if ( currentArea < area )
1840 {
1841 minRect = bounds;
1842 area = currentArea;
1843 angle = totalRotation;
1844 width = bounds.width();
1845 height = bounds.height();
1846 }
1847
1848 pt1 = hull->vertexAt( vertexId );
1849 }
1850
1851 QgsGeometry minBounds = QgsGeometry::fromRect( minRect );
1852 minBounds.rotate( angle, QgsPointXY( pt0.x(), pt0.y() ) );
1853
1854 if ( width > height )
1855 {
1856 width = minRect.height();
1857 height = minRect.width();
1858 angle = angle + 90.0;
1859 }
1860
1861 // constrain angle to 0 - 180
1862 if ( angle > 180.0 )
1863 angle = std::fmod( angle, 180.0 );
1864
1865 return minBounds;
1866}
1867
1868std::unique_ptr< QgsLineString > triangularWavesAlongLine( const QgsLineString *line, double wavelength, const double amplitude, const bool strictWavelength )
1869{
1870 const int totalPoints = line->numPoints();
1871 if ( totalPoints < 2 )
1872 return nullptr;
1873
1874 const double *x = line->xData();
1875 const double *y = line->yData();
1876
1877 double prevX = *x++;
1878 double prevY = *y++;
1879
1880 QVector< double > outX;
1881 QVector< double > outY;
1882 const double totalLength = line->length();
1883
1884 const int maxRepetitions = std::ceil( totalLength / wavelength );
1885 if ( !strictWavelength )
1886 wavelength = totalLength / maxRepetitions;
1887
1888 const int estimatedPoints = maxRepetitions * 2 + 2;
1889 outX.reserve( estimatedPoints );
1890 outY.reserve( estimatedPoints );
1891 outX.append( prevX );
1892 outY.append( prevY );
1893
1894 double distanceToNextPointFromStartOfSegment = wavelength / 4;
1895 int side = -1;
1896 for ( int i = 1; i < totalPoints; ++i )
1897 {
1898 double thisX = *x++;
1899 double thisY = *y++;
1900
1901 const double segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
1902 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1903 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
1904 {
1905 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1906 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
1907 double pX, pY;
1908 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
1909
1910 // project point on line out by amplitude
1911 const double outPointX = pX + side * amplitude * std::sin( segmentAngleRadians + M_PI_2 );
1912 const double outPointY = pY + side * amplitude * std::cos( segmentAngleRadians + M_PI_2 );
1913
1914 outX.append( outPointX );
1915 outY.append( outPointY );
1916
1917 distanceToNextPointFromStartOfSegment += wavelength / 2;
1918 side = -side;
1919 }
1920
1921 prevX = thisX;
1922 prevY = thisY;
1923 distanceToNextPointFromStartOfSegment -= segmentLength;
1924 }
1925
1926 outX.append( prevX );
1927 outY.append( prevY );
1928
1929 return std::make_unique< QgsLineString >( outX, outY );
1930}
1931
1932std::unique_ptr< QgsLineString > triangularWavesRandomizedAlongLine( const QgsLineString *line,
1933 const double minimumWavelength, const double maximumWavelength,
1934 const double minimumAmplitude, const double maximumAmplitude,
1935 std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
1936{
1937 const int totalPoints = line->numPoints();
1938 if ( totalPoints < 2 )
1939 return nullptr;
1940
1941 const double *x = line->xData();
1942 const double *y = line->yData();
1943
1944 double prevX = *x++;
1945 double prevY = *y++;
1946
1947 QVector< double > outX;
1948 QVector< double > outY;
1949 const double totalLength = line->length();
1950
1951 const int maxRepetitions = std::ceil( totalLength / minimumWavelength );
1952
1953 const int estimatedPoints = maxRepetitions * 2 + 2;
1954 outX.reserve( estimatedPoints );
1955 outY.reserve( estimatedPoints );
1956 outX.append( prevX );
1957 outY.append( prevY );
1958
1959 double wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
1960 double distanceToNextPointFromStartOfSegment = wavelength / 4;
1961 double amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
1962
1963 int side = -1;
1964 for ( int i = 1; i < totalPoints; ++i )
1965 {
1966 double thisX = *x++;
1967 double thisY = *y++;
1968
1969 const double segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
1970 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
1971 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
1972 {
1973 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
1974 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
1975 double pX, pY;
1976 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
1977
1978 // project point on line out by amplitude
1979 const double outPointX = pX + side * amplitude * std::sin( segmentAngleRadians + M_PI_2 );
1980 const double outPointY = pY + side * amplitude * std::cos( segmentAngleRadians + M_PI_2 );
1981 outX.append( outPointX );
1982 outY.append( outPointY );
1983
1984 wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
1985 amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
1986
1987 distanceToNextPointFromStartOfSegment += wavelength / 2;
1988 side = -side;
1989 }
1990
1991 prevX = thisX;
1992 prevY = thisY;
1993 distanceToNextPointFromStartOfSegment -= segmentLength;
1994 }
1995
1996 outX.append( prevX );
1997 outY.append( prevY );
1998
1999 return std::make_unique< QgsLineString >( outX, outY );
2000}
2001
2002std::unique_ptr< QgsAbstractGeometry > triangularWavesPrivate( const QgsAbstractGeometry *geom, double wavelength, double amplitude, bool strictWavelength )
2003{
2004 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2005 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2006 {
2007 segmentizedCopy.reset( geom->segmentize() );
2008 geom = segmentizedCopy.get();
2009 }
2010
2012 {
2013 return triangularWavesAlongLine( static_cast< const QgsLineString * >( geom ), wavelength, amplitude, strictWavelength );
2014 }
2015 else
2016 {
2017 // polygon
2018 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2019 std::unique_ptr< QgsPolygon > result = std::make_unique< QgsPolygon >();
2020
2021 result->setExteriorRing( triangularWavesAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2022 wavelength, amplitude, strictWavelength ).release() );
2023 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2024 {
2025 result->addInteriorRing( triangularWavesAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2026 wavelength, amplitude, strictWavelength ).release() );
2027 }
2028
2029 return result;
2030 }
2031}
2032
2033std::unique_ptr< QgsAbstractGeometry > triangularWavesRandomizedPrivate( const QgsAbstractGeometry *geom, double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
2034{
2035 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2036 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2037 {
2038 segmentizedCopy.reset( geom->segmentize() );
2039 geom = segmentizedCopy.get();
2040 }
2041
2043 {
2044 return triangularWavesRandomizedAlongLine( static_cast< const QgsLineString * >( geom ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt );
2045 }
2046 else
2047 {
2048 // polygon
2049 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2050 std::unique_ptr< QgsPolygon > result = std::make_unique< QgsPolygon >();
2051
2052 result->setExteriorRing( triangularWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2053 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2054 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2055 {
2056 result->addInteriorRing( triangularWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2057 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2058 }
2059
2060 return result;
2061 }
2062}
2063
2064QgsGeometry QgsInternalGeometryEngine::triangularWaves( double wavelength, double amplitude, bool strictWavelength ) const
2065{
2066 if ( wavelength < 0 || qgsDoubleNear( wavelength, 0 ) )
2067 return QgsGeometry();
2068
2069 mLastError.clear();
2070 if ( !mGeometry )
2071 {
2072 return QgsGeometry();
2073 }
2074
2076 {
2077 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2078 }
2079
2080 if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( mGeometry ) )
2081 {
2082 int numGeom = gc->numGeometries();
2083 QVector< QgsAbstractGeometry * > geometryList;
2084 geometryList.reserve( numGeom );
2085 for ( int i = 0; i < numGeom; ++i )
2086 {
2087 geometryList << triangularWavesPrivate( gc->geometryN( i ), wavelength, amplitude, strictWavelength ).release();
2088 }
2089
2090 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2091 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2092 {
2093 first.addPartV2( g );
2094 }
2095 return first;
2096 }
2097 else
2098 {
2099 return QgsGeometry( triangularWavesPrivate( mGeometry, wavelength, amplitude, strictWavelength ) );
2100 }
2101}
2102
2103QgsGeometry QgsInternalGeometryEngine::triangularWavesRandomized( double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, unsigned long seed ) const
2104{
2105 if ( minimumWavelength < 0 || qgsDoubleNear( minimumWavelength, 0 ) || maximumWavelength < 0 || qgsDoubleNear( maximumWavelength, 0 ) || maximumWavelength < minimumWavelength )
2106 return QgsGeometry();
2107
2108 mLastError.clear();
2109 if ( !mGeometry )
2110 {
2111 return QgsGeometry();
2112 }
2113
2114 std::random_device rd;
2115 std::mt19937 mt( seed == 0 ? rd() : seed );
2116 std::uniform_real_distribution<> uniformDist( 0, 1 );
2117
2119 {
2120 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2121 }
2122
2123 if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( mGeometry ) )
2124 {
2125 int numGeom = gc->numGeometries();
2126 QVector< QgsAbstractGeometry * > geometryList;
2127 geometryList.reserve( numGeom );
2128 for ( int i = 0; i < numGeom; ++i )
2129 {
2130 geometryList << triangularWavesRandomizedPrivate( gc->geometryN( i ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release();
2131 }
2132
2133 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2134 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2135 {
2136 first.addPartV2( g );
2137 }
2138 return first;
2139 }
2140 else
2141 {
2142 return QgsGeometry( triangularWavesRandomizedPrivate( mGeometry, minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ) );
2143 }
2144}
2145
2146std::unique_ptr< QgsLineString > squareWavesAlongLine( const QgsLineString *line, double wavelength, const double amplitude, const bool strictWavelength )
2147{
2148 const int totalPoints = line->numPoints();
2149 if ( totalPoints < 2 )
2150 return nullptr;
2151
2152 const double *x = line->xData();
2153 const double *y = line->yData();
2154
2155 double prevX = *x++;
2156 double prevY = *y++;
2157
2158 QVector< double > outX;
2159 QVector< double > outY;
2160 const double totalLength = line->length();
2161
2162 const int maxRepetitions = std::ceil( totalLength / wavelength );
2163 if ( !strictWavelength )
2164 wavelength = totalLength / maxRepetitions;
2165
2166 const int estimatedPoints = maxRepetitions * 4 + 2;
2167 outX.reserve( estimatedPoints );
2168 outY.reserve( estimatedPoints );
2169 outX.append( prevX );
2170 outY.append( prevY );
2171
2172 const double startSegmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, *x, *y );
2173 outX.append( prevX - amplitude * std::sin( startSegmentAngleRadians + M_PI_2 ) );
2174 outY.append( prevY - amplitude * std::cos( startSegmentAngleRadians + M_PI_2 ) );
2175
2176 double distanceToNextPointFromStartOfSegment = wavelength / 2;
2177
2178 int side = -1;
2179 for ( int i = 1; i < totalPoints; ++i )
2180 {
2181 double thisX = *x++;
2182 double thisY = *y++;
2183
2184 const double segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
2185 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
2186 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
2187 {
2188 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
2189 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
2190 double pX, pY;
2191 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
2192
2193 // project point on line out by amplitude
2194 const double sinAngle = std::sin( segmentAngleRadians + M_PI_2 );
2195 const double cosAngle = std::cos( segmentAngleRadians + M_PI_2 );
2196 outX.append( pX + side * amplitude * sinAngle );
2197 outY.append( pY + side * amplitude * cosAngle );
2198 outX.append( pX - side * amplitude * sinAngle );
2199 outY.append( pY - side * amplitude * cosAngle );
2200
2201 distanceToNextPointFromStartOfSegment += wavelength / 2;
2202 side = -side;
2203 }
2204
2205 prevX = thisX;
2206 prevY = thisY;
2207 distanceToNextPointFromStartOfSegment -= segmentLength;
2208 }
2209
2210 // replace last point, which will be projected out to amplitude of wave, with the actual end point of the line
2211 outX.pop_back();
2212 outY.pop_back();
2213 outX.append( prevX );
2214 outY.append( prevY );
2215
2216 return std::make_unique< QgsLineString >( outX, outY );
2217}
2218
2219std::unique_ptr< QgsLineString > squareWavesRandomizedAlongLine( const QgsLineString *line,
2220 const double minimumWavelength, const double maximumWavelength,
2221 const double minimumAmplitude, const double maximumAmplitude,
2222 std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
2223{
2224 const int totalPoints = line->numPoints();
2225 if ( totalPoints < 2 )
2226 return nullptr;
2227
2228 const double *x = line->xData();
2229 const double *y = line->yData();
2230
2231 double prevX = *x++;
2232 double prevY = *y++;
2233
2234 QVector< double > outX;
2235 QVector< double > outY;
2236 const double totalLength = line->length();
2237
2238 const int maxRepetitions = std::ceil( totalLength / minimumWavelength );
2239
2240 const int estimatedPoints = maxRepetitions * 4 + 2;
2241 outX.reserve( estimatedPoints );
2242 outY.reserve( estimatedPoints );
2243 outX.append( prevX );
2244 outY.append( prevY );
2245
2246 double amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
2247
2248 double segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, *x, *y );
2249 outX.append( prevX - amplitude * std::sin( segmentAngleRadians + M_PI_2 ) );
2250 outY.append( prevY - amplitude * std::cos( segmentAngleRadians + M_PI_2 ) );
2251
2252 double wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
2253 double distanceToNextPointFromStartOfSegment = wavelength / 2;
2254
2255 int side = -1;
2256 for ( int i = 1; i < totalPoints; ++i )
2257 {
2258 double thisX = *x++;
2259 double thisY = *y++;
2260
2261 segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
2262 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
2263 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
2264 {
2265 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
2266 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
2267 double pX, pY;
2268 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
2269
2270 // project point on line out by amplitude
2271 const double sinAngle = std::sin( segmentAngleRadians + M_PI_2 );
2272 const double cosAngle = std::cos( segmentAngleRadians + M_PI_2 );
2273 outX.append( pX + side * amplitude * sinAngle );
2274 outY.append( pY + side * amplitude * cosAngle );
2275
2276 amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
2277 outX.append( pX - side * amplitude * sinAngle );
2278 outY.append( pY - side * amplitude * cosAngle );
2279
2280 wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
2281 distanceToNextPointFromStartOfSegment += wavelength / 2;
2282 side = -side;
2283 }
2284
2285 prevX = thisX;
2286 prevY = thisY;
2287 distanceToNextPointFromStartOfSegment -= segmentLength;
2288 }
2289
2290 outX.append( prevX + side * amplitude * std::sin( segmentAngleRadians + M_PI_2 ) );
2291 outY.append( prevY + side * amplitude * std::cos( segmentAngleRadians + M_PI_2 ) );
2292 outX.append( prevX );
2293 outY.append( prevY );
2294
2295 return std::make_unique< QgsLineString >( outX, outY );
2296}
2297
2298std::unique_ptr< QgsAbstractGeometry > squareWavesPrivate( const QgsAbstractGeometry *geom, double wavelength, double amplitude, bool strictWavelength )
2299{
2300 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2301 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2302 {
2303 segmentizedCopy.reset( geom->segmentize() );
2304 geom = segmentizedCopy.get();
2305 }
2306
2308 {
2309 return squareWavesAlongLine( static_cast< const QgsLineString * >( geom ), wavelength, amplitude, strictWavelength );
2310 }
2311 else
2312 {
2313 // polygon
2314 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2315 std::unique_ptr< QgsPolygon > result = std::make_unique< QgsPolygon >();
2316
2317 result->setExteriorRing( squareWavesAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2318 wavelength, amplitude, strictWavelength ).release() );
2319 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2320 {
2321 result->addInteriorRing( squareWavesAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2322 wavelength, amplitude, strictWavelength ).release() );
2323 }
2324
2325 return result;
2326 }
2327}
2328
2329std::unique_ptr< QgsAbstractGeometry > squareWavesRandomizedPrivate( const QgsAbstractGeometry *geom, double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
2330{
2331 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2332 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2333 {
2334 segmentizedCopy.reset( geom->segmentize() );
2335 geom = segmentizedCopy.get();
2336 }
2337
2339 {
2340 return squareWavesRandomizedAlongLine( static_cast< const QgsLineString * >( geom ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt );
2341 }
2342 else
2343 {
2344 // polygon
2345 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2346 std::unique_ptr< QgsPolygon > result = std::make_unique< QgsPolygon >();
2347
2348 result->setExteriorRing( squareWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2349 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2350 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2351 {
2352 result->addInteriorRing( squareWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2353 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2354 }
2355
2356 return result;
2357 }
2358}
2359
2360QgsGeometry QgsInternalGeometryEngine::squareWaves( double wavelength, double amplitude, bool strictWavelength ) const
2361{
2362 if ( wavelength < 0 || qgsDoubleNear( wavelength, 0 ) )
2363 return QgsGeometry();
2364
2365 mLastError.clear();
2366 if ( !mGeometry )
2367 {
2368 return QgsGeometry();
2369 }
2370
2372 {
2373 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2374 }
2375
2376 if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( mGeometry ) )
2377 {
2378 int numGeom = gc->numGeometries();
2379 QVector< QgsAbstractGeometry * > geometryList;
2380 geometryList.reserve( numGeom );
2381 for ( int i = 0; i < numGeom; ++i )
2382 {
2383 geometryList << squareWavesPrivate( gc->geometryN( i ), wavelength, amplitude, strictWavelength ).release();
2384 }
2385
2386 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2387 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2388 {
2389 first.addPartV2( g );
2390 }
2391 return first;
2392 }
2393 else
2394 {
2395 return QgsGeometry( squareWavesPrivate( mGeometry, wavelength, amplitude, strictWavelength ) );
2396 }
2397}
2398
2399QgsGeometry QgsInternalGeometryEngine::squareWavesRandomized( double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, unsigned long seed ) const
2400{
2401 if ( minimumWavelength < 0 || qgsDoubleNear( minimumWavelength, 0 ) || maximumWavelength < 0 || qgsDoubleNear( maximumWavelength, 0 ) || maximumWavelength < minimumWavelength )
2402 return QgsGeometry();
2403
2404 mLastError.clear();
2405 if ( !mGeometry )
2406 {
2407 return QgsGeometry();
2408 }
2409
2410 std::random_device rd;
2411 std::mt19937 mt( seed == 0 ? rd() : seed );
2412 std::uniform_real_distribution<> uniformDist( 0, 1 );
2413
2415 {
2416 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2417 }
2418
2419 if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( mGeometry ) )
2420 {
2421 int numGeom = gc->numGeometries();
2422 QVector< QgsAbstractGeometry * > geometryList;
2423 geometryList.reserve( numGeom );
2424 for ( int i = 0; i < numGeom; ++i )
2425 {
2426 geometryList << squareWavesRandomizedPrivate( gc->geometryN( i ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release();
2427 }
2428
2429 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2430 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2431 {
2432 first.addPartV2( g );
2433 }
2434 return first;
2435 }
2436 else
2437 {
2438 return QgsGeometry( squareWavesRandomizedPrivate( mGeometry, minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ) );
2439 }
2440}
2441
2442std::unique_ptr< QgsLineString > roundWavesAlongLine( const QgsLineString *line, double wavelength, const double amplitude, const bool strictWavelength )
2443{
2444 const int totalPoints = line->numPoints();
2445 if ( totalPoints < 2 )
2446 return nullptr;
2447
2448 const double *x = line->xData();
2449 const double *y = line->yData();
2450
2451 double prevX = *x++;
2452 double prevY = *y++;
2453
2454 const double totalLength = line->length();
2455
2456 const int maxRepetitions = std::ceil( totalLength / wavelength );
2457 if ( !strictWavelength )
2458 wavelength = totalLength / maxRepetitions;
2459
2460 const int segments = 10;
2461
2462 int side = -1;
2463
2464 double xOutBuffer[4] { prevX, prevX, prevX, prevX };
2465 double yOutBuffer[4] { prevY, prevY, prevY, prevY };
2466 bool isFirstPart = true;
2467
2468 double distanceToNextPointFromStartOfSegment = wavelength / 8;
2469 int bufferIndex = 1;
2470 std::unique_ptr< QgsLineString > out = std::make_unique< QgsLineString >();
2471
2472 double segmentAngleRadians = 0;
2473 double remainingDistance = totalLength;
2474 double totalCoveredDistance = 0;
2475
2476 for ( int i = 1; i < totalPoints; ++i )
2477 {
2478 double thisX = *x++;
2479 double thisY = *y++;
2480
2481 segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
2482 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
2483 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
2484 {
2485 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
2486 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
2487 double pX, pY;
2488 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
2489 remainingDistance = totalLength - totalCoveredDistance - distanceToPoint;
2490
2491 const double sinAngle = std::sin( segmentAngleRadians + M_PI_2 );
2492 const double cosAngle = std::cos( segmentAngleRadians + M_PI_2 );
2493
2494 if ( bufferIndex == 0 )
2495 {
2496 xOutBuffer[0] = pX + side * amplitude * sinAngle;
2497 yOutBuffer[0] = pY + side * amplitude * cosAngle;
2498 bufferIndex = 1;
2499 distanceToNextPointFromStartOfSegment += wavelength / 4;
2500 }
2501 else if ( bufferIndex == 1 && isFirstPart )
2502 {
2503 xOutBuffer[1] = ( xOutBuffer[0] + pX - side * amplitude * sinAngle ) * 0.5;
2504 yOutBuffer[1] = ( yOutBuffer[0] + pY - side * amplitude * cosAngle ) * 0.5;
2505 xOutBuffer[2] = pX - side * amplitude * sinAngle;
2506 yOutBuffer[2] = pY - side * amplitude * cosAngle;
2507 bufferIndex = 2;
2508 distanceToNextPointFromStartOfSegment += wavelength / 8;
2509 }
2510 else if ( bufferIndex == 1 )
2511 {
2512 xOutBuffer[1] = pX + side * amplitude * sinAngle;
2513 yOutBuffer[1] = pY + side * amplitude * cosAngle;
2514 xOutBuffer[2] = pX - side * amplitude * sinAngle;
2515 yOutBuffer[2] = pY - side * amplitude * cosAngle;
2516 bufferIndex = 2;
2517 distanceToNextPointFromStartOfSegment += wavelength / 4;
2518 }
2519 else if ( bufferIndex == 2 )
2520 {
2521 xOutBuffer[3] = pX - side * amplitude * sinAngle;
2522 yOutBuffer[3] = pY - side * amplitude * cosAngle;
2523
2524 if ( isFirstPart )
2525 {
2526 xOutBuffer[2] = ( xOutBuffer[2] + xOutBuffer[3] ) * 0.5;
2527 yOutBuffer[2] = ( yOutBuffer[2] + yOutBuffer[3] ) * 0.5;
2528 isFirstPart = false;
2529 }
2530
2531 // flush curve
2532 std::unique_ptr< QgsLineString > bezier( QgsLineString::fromBezierCurve( QgsPoint( xOutBuffer[0], yOutBuffer[0] ),
2533 QgsPoint( xOutBuffer[1], yOutBuffer[1] ),
2534 QgsPoint( xOutBuffer[2], yOutBuffer[2] ),
2535 QgsPoint( xOutBuffer[3], yOutBuffer[3] ),
2536 segments ) );
2537 out->append( bezier.get() );
2538
2539 // shuffle buffer alone
2540 xOutBuffer[0] = xOutBuffer[3];
2541 yOutBuffer[0] = yOutBuffer[3];
2542 bufferIndex = 1;
2543 side = -side;
2544 distanceToNextPointFromStartOfSegment += wavelength / 4;
2545 }
2546 }
2547 totalCoveredDistance += segmentLength;
2548 prevX = thisX;
2549 prevY = thisY;
2550 distanceToNextPointFromStartOfSegment -= segmentLength;
2551 }
2552
2553 const double midX = prevX - remainingDistance / 2 * std::sin( segmentAngleRadians );
2554 const double midY = prevY - remainingDistance / 2 * std::cos( segmentAngleRadians );
2555 const double pX = midX + side * amplitude * std::sin( segmentAngleRadians + M_PI_2 );
2556 const double pY = midY + side * amplitude * std::cos( segmentAngleRadians + M_PI_2 );
2557
2558 std::unique_ptr< QgsLineString > bezier( QgsLineString::fromBezierCurve( out->endPoint(),
2559 QgsPoint( ( out->endPoint().x() + pX ) * 0.5, ( out->endPoint().y() + pY ) * 0.5 ),
2560 QgsPoint( ( prevX + pX ) * 0.5, ( prevY + pY ) * 0.5 ),
2561 QgsPoint( prevX, prevY ),
2562 segments / 2 ) );
2563 out->append( bezier.get() );
2564
2565 return out;
2566}
2567
2568std::unique_ptr< QgsLineString > roundWavesRandomizedAlongLine( const QgsLineString *line,
2569 const double minimumWavelength, const double maximumWavelength,
2570 const double minimumAmplitude, const double maximumAmplitude,
2571 std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
2572{
2573 const int totalPoints = line->numPoints();
2574 if ( totalPoints < 2 )
2575 return nullptr;
2576
2577 const double *x = line->xData();
2578 const double *y = line->yData();
2579
2580 double prevX = *x++;
2581 double prevY = *y++;
2582
2583 const double totalLength = line->length();
2584
2585 const int segments = 10;
2586
2587 int side = -1;
2588
2589 double xOutBuffer[4] { prevX, prevX, prevX, prevX };
2590 double yOutBuffer[4] { prevY, prevY, prevY, prevY };
2591 bool isFirstPart = true;
2592
2593 double amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
2594 double wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
2595
2596 double distanceToNextPointFromStartOfSegment = wavelength / 8;
2597 int bufferIndex = 1;
2598 std::unique_ptr< QgsLineString > out = std::make_unique< QgsLineString >();
2599
2600 double segmentAngleRadians = 0;
2601
2602 double remainingDistance = totalLength;
2603 double totalCoveredDistance = 0;
2604
2605 for ( int i = 1; i < totalPoints; ++i )
2606 {
2607 double thisX = *x++;
2608 double thisY = *y++;
2609
2610 segmentAngleRadians = QgsGeometryUtilsBase::lineAngle( prevX, prevY, thisX, thisY );
2611 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
2612 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
2613 {
2614 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
2615 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
2616 double pX, pY;
2617 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
2618 remainingDistance = totalLength - totalCoveredDistance - distanceToPoint;
2619
2620 const double sinAngle = std::sin( segmentAngleRadians + M_PI_2 );
2621 const double cosAngle = std::cos( segmentAngleRadians + M_PI_2 );
2622
2623 if ( bufferIndex == 0 )
2624 {
2625 xOutBuffer[0] = pX + side * amplitude * sinAngle;
2626 yOutBuffer[0] = pY + side * amplitude * cosAngle;
2627 bufferIndex = 1;
2628 distanceToNextPointFromStartOfSegment += wavelength / 4;
2629 }
2630 else if ( bufferIndex == 1 && isFirstPart )
2631 {
2632 xOutBuffer[1] = ( xOutBuffer[0] + pX - side * amplitude * sinAngle ) * 0.5;
2633 yOutBuffer[1] = ( yOutBuffer[0] + pY - side * amplitude * cosAngle ) * 0.5;
2634 xOutBuffer[2] = pX - side * amplitude * sinAngle;
2635 yOutBuffer[2] = pY - side * amplitude * cosAngle;
2636 bufferIndex = 2;
2637 distanceToNextPointFromStartOfSegment += wavelength / 8;
2638 }
2639 else if ( bufferIndex == 1 )
2640 {
2641 xOutBuffer[1] = pX + side * amplitude * sinAngle;
2642 yOutBuffer[1] = pY + side * amplitude * cosAngle;
2643 amplitude = uniformDist( mt ) * ( maximumAmplitude - minimumAmplitude ) + minimumAmplitude;
2644 xOutBuffer[2] = pX - side * amplitude * sinAngle;
2645 bufferIndex = 2;
2646 yOutBuffer[2] = pY - side * amplitude * cosAngle;
2647 distanceToNextPointFromStartOfSegment += wavelength / 4;
2648 }
2649 else if ( bufferIndex == 2 )
2650 {
2651 xOutBuffer[3] = pX - side * amplitude * sinAngle;
2652 yOutBuffer[3] = pY - side * amplitude * cosAngle;
2653
2654 if ( isFirstPart )
2655 {
2656 xOutBuffer[2] = ( xOutBuffer[2] + xOutBuffer[3] ) * 0.5;
2657 yOutBuffer[2] = ( yOutBuffer[2] + yOutBuffer[3] ) * 0.5;
2658 isFirstPart = false;
2659 }
2660
2661 // flush curve
2662 std::unique_ptr< QgsLineString > bezier( QgsLineString::fromBezierCurve( QgsPoint( xOutBuffer[0], yOutBuffer[0] ),
2663 QgsPoint( xOutBuffer[1], yOutBuffer[1] ),
2664 QgsPoint( xOutBuffer[2], yOutBuffer[2] ),
2665 QgsPoint( xOutBuffer[3], yOutBuffer[3] ),
2666 segments ) );
2667 out->append( bezier.get() );
2668
2669 // shuffle buffer alone
2670 xOutBuffer[0] = xOutBuffer[3];
2671 yOutBuffer[0] = yOutBuffer[3];
2672 bufferIndex = 1;
2673 side = -side;
2674
2675 wavelength = uniformDist( mt ) * ( maximumWavelength - minimumWavelength ) + minimumWavelength;
2676
2677 distanceToNextPointFromStartOfSegment += wavelength / 4;
2678 }
2679 }
2680 totalCoveredDistance += segmentLength;
2681
2682 prevX = thisX;
2683 prevY = thisY;
2684 distanceToNextPointFromStartOfSegment -= segmentLength;
2685 }
2686
2687 const double midX = prevX - remainingDistance / 2 * std::sin( segmentAngleRadians );
2688 const double midY = prevY - remainingDistance / 2 * std::cos( segmentAngleRadians );
2689 const double pX = midX + side * amplitude * std::sin( segmentAngleRadians + M_PI_2 );
2690 const double pY = midY + side * amplitude * std::cos( segmentAngleRadians + M_PI_2 );
2691
2692 if ( out->isEmpty() )
2693 {
2694 std::unique_ptr< QgsLineString > bezier( QgsLineString::fromBezierCurve( line->startPoint(),
2695 QgsPoint( ( line->startPoint().x() + pX ) * 0.5, ( line->startPoint().y() + pY ) * 0.5 ),
2696 QgsPoint( ( prevX + pX ) * 0.5, ( prevY + pY ) * 0.5 ),
2697 QgsPoint( prevX, prevY ),
2698 segments ) );
2699 out->append( bezier.get() );
2700 }
2701 else
2702 {
2703 std::unique_ptr< QgsLineString > bezier( QgsLineString::fromBezierCurve( out->endPoint(),
2704 QgsPoint( ( out->endPoint().x() + pX ) * 0.5, ( out->endPoint().y() + pY ) * 0.5 ),
2705 QgsPoint( ( prevX + pX ) * 0.5, ( prevY + pY ) * 0.5 ),
2706 QgsPoint( prevX, prevY ),
2707 segments ) );
2708 out->append( bezier.get() );
2709 }
2710
2711 return out;
2712}
2713
2714std::unique_ptr< QgsAbstractGeometry > roundWavesPrivate( const QgsAbstractGeometry *geom, double wavelength, double amplitude, bool strictWavelength )
2715{
2716 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2717 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2718 {
2719 segmentizedCopy.reset( geom->segmentize() );
2720 geom = segmentizedCopy.get();
2721 }
2722
2724 {
2725 return roundWavesAlongLine( static_cast< const QgsLineString * >( geom ), wavelength, amplitude, strictWavelength );
2726 }
2727 else
2728 {
2729 // polygon
2730 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2731 std::unique_ptr< QgsPolygon > result = std::make_unique< QgsPolygon >();
2732
2733 result->setExteriorRing( roundWavesAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2734 wavelength, amplitude, strictWavelength ).release() );
2735 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2736 {
2737 result->addInteriorRing( roundWavesAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2738 wavelength, amplitude, strictWavelength ).release() );
2739 }
2740
2741 return result;
2742 }
2743}
2744
2745std::unique_ptr< QgsAbstractGeometry > roundWavesRandomizedPrivate( const QgsAbstractGeometry *geom, double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, std::uniform_real_distribution<> &uniformDist, std::mt19937 &mt )
2746{
2747 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
2748 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
2749 {
2750 segmentizedCopy.reset( geom->segmentize() );
2751 geom = segmentizedCopy.get();
2752 }
2753
2755 {
2756 return roundWavesRandomizedAlongLine( static_cast< const QgsLineString * >( geom ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt );
2757 }
2758 else
2759 {
2760 // polygon
2761 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
2762 std::unique_ptr< QgsPolygon > result = std::make_unique< QgsPolygon >();
2763
2764 result->setExteriorRing( roundWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ),
2765 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2766 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
2767 {
2768 result->addInteriorRing( roundWavesRandomizedAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ),
2769 minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release() );
2770 }
2771
2772 return result;
2773 }
2774}
2775
2776QgsGeometry QgsInternalGeometryEngine::roundWaves( double wavelength, double amplitude, bool strictWavelength ) const
2777{
2778 if ( wavelength < 0 || qgsDoubleNear( wavelength, 0 ) )
2779 return QgsGeometry();
2780
2781 mLastError.clear();
2782 if ( !mGeometry )
2783 {
2784 return QgsGeometry();
2785 }
2786
2788 {
2789 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2790 }
2791
2792 if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( mGeometry ) )
2793 {
2794 int numGeom = gc->numGeometries();
2795 QVector< QgsAbstractGeometry * > geometryList;
2796 geometryList.reserve( numGeom );
2797 for ( int i = 0; i < numGeom; ++i )
2798 {
2799 geometryList << roundWavesPrivate( gc->geometryN( i ), wavelength, amplitude, strictWavelength ).release();
2800 }
2801
2802 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2803 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2804 {
2805 first.addPartV2( g );
2806 }
2807 return first;
2808 }
2809 else
2810 {
2811 return QgsGeometry( roundWavesPrivate( mGeometry, wavelength, amplitude, strictWavelength ) );
2812 }
2813}
2814
2815QgsGeometry QgsInternalGeometryEngine::roundWavesRandomized( double minimumWavelength, double maximumWavelength, double minimumAmplitude, double maximumAmplitude, unsigned long seed ) const
2816{
2817 if ( minimumWavelength < 0 || qgsDoubleNear( minimumWavelength, 0 ) || maximumWavelength < 0 || qgsDoubleNear( maximumWavelength, 0 ) || maximumWavelength < minimumWavelength )
2818 return QgsGeometry();
2819
2820 mLastError.clear();
2821 if ( !mGeometry )
2822 {
2823 return QgsGeometry();
2824 }
2825
2826 std::random_device rd;
2827 std::mt19937 mt( seed == 0 ? rd() : seed );
2828 std::uniform_real_distribution<> uniformDist( 0, 1 );
2829
2831 {
2832 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
2833 }
2834
2835 if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( mGeometry ) )
2836 {
2837 int numGeom = gc->numGeometries();
2838 QVector< QgsAbstractGeometry * > geometryList;
2839 geometryList.reserve( numGeom );
2840 for ( int i = 0; i < numGeom; ++i )
2841 {
2842 geometryList << roundWavesRandomizedPrivate( gc->geometryN( i ), minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ).release();
2843 }
2844
2845 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
2846 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
2847 {
2848 first.addPartV2( g );
2849 }
2850 return first;
2851 }
2852 else
2853 {
2854 return QgsGeometry( roundWavesRandomizedPrivate( mGeometry, minimumWavelength, maximumWavelength, minimumAmplitude, maximumAmplitude, uniformDist, mt ) );
2855 }
2856}
2857
2858std::unique_ptr< QgsMultiLineString > dashPatternAlongLine( const QgsLineString *line,
2859 const QVector< double> &pattern,
2863 double patternOffset )
2864{
2865 const int totalPoints = line->numPoints();
2866 if ( totalPoints < 2 )
2867 return nullptr;
2868
2869 const int patternSize = pattern.size();
2870
2871 const double *x = line->xData();
2872 const double *y = line->yData();
2873
2874 double prevX = *x++;
2875 double prevY = *y++;
2876
2877 std::unique_ptr< QgsMultiLineString > result = std::make_unique< QgsMultiLineString >();
2878
2879 QVector< double > outX;
2880 QVector< double > outY;
2881 const double totalLength = line->length();
2882
2883 double patternLength = 0;
2884 double patternDashLength = 0;
2885 double patternGapLength = 0;
2886 for ( int i = 0; i < pattern.size(); ++i )
2887 {
2888 patternLength += pattern.at( i );
2889 if ( i % 2 == 0 )
2890 patternDashLength += pattern.at( i );
2891 else
2892 patternGapLength += pattern.at( i );
2893 }
2894
2895 double firstPatternLength = 0;
2896 double firstPatternDashLength = 0;
2897 double firstPatternGapLength = 0;
2898 switch ( startRule )
2899 {
2902 firstPatternLength = patternLength;
2903 firstPatternDashLength = patternDashLength;
2904 firstPatternGapLength = patternGapLength;
2905 break;
2907 firstPatternLength = patternLength - pattern.at( 0 ) * 0.5; // remove half of first dash
2908 firstPatternDashLength = patternDashLength - pattern.at( 0 ) * 0.5;
2909 firstPatternGapLength = patternGapLength;
2910 break;
2912 firstPatternLength = pattern.at( patternSize - 1 ); // start at LAST dash
2913 firstPatternDashLength = 0;
2914 firstPatternGapLength = pattern.at( patternSize - 1 );
2915 break;
2917 firstPatternLength = pattern.at( patternSize - 1 ) * 0.5; // start at half of last dash
2918 firstPatternDashLength = 0;
2919 firstPatternGapLength = pattern.at( patternSize - 1 ) * 0.5;
2920 break;
2921 }
2922
2923 const bool isSmallEnoughForSinglePattern = ( totalLength - firstPatternLength ) < patternLength * 0.5;
2924
2925 double lastPatternLength = isSmallEnoughForSinglePattern ? firstPatternLength : patternLength;
2926 double lastPatternDashLength = isSmallEnoughForSinglePattern ? firstPatternDashLength : patternDashLength;
2927 double lastPatternGapLength = isSmallEnoughForSinglePattern ? firstPatternGapLength : patternGapLength;
2928 switch ( endRule )
2929 {
2931 lastPatternLength = 0;
2932 lastPatternDashLength = 0;
2933 lastPatternGapLength = 0;
2934 break;
2936 lastPatternLength -= pattern.at( patternSize - 1 ); // remove last gap
2937 lastPatternGapLength -= pattern.at( patternSize - 1 );
2938 break;
2940 lastPatternLength -= pattern.at( patternSize - 1 ) + pattern.at( patternSize - 2 ) * 0.5; // remove last gap, half of last dash
2941 lastPatternDashLength -= pattern.at( patternSize - 2 ) * 0.5;
2942 lastPatternGapLength -= pattern.at( patternSize - 1 );
2943 break;
2945 lastPatternGapLength = patternGapLength;
2946 break;
2948 lastPatternLength -= pattern.at( patternSize - 1 ) * 0.5; // remove half of last gap
2949 lastPatternGapLength -= pattern.at( patternSize - 1 ) * 0.5;
2950 break;
2951 }
2952
2953 const double remainingLengthForCompletePatterns = totalLength - ( !isSmallEnoughForSinglePattern ? firstPatternLength : 0 ) - lastPatternLength;
2954 const int middlePatternRepetitions = std::max( static_cast< int >( std::round( remainingLengthForCompletePatterns / patternLength ) ), 0 );
2955
2956 const double totalUnscaledLengthOfPatterns = ( !isSmallEnoughForSinglePattern ? firstPatternLength : 0 ) + middlePatternRepetitions * patternLength + lastPatternLength;
2957 const double totalUnscaledLengthOfDashes = ( !isSmallEnoughForSinglePattern ? firstPatternDashLength : 0 ) + middlePatternRepetitions * patternDashLength + lastPatternDashLength;
2958 const double totalUnscaledLengthOfGaps = ( !isSmallEnoughForSinglePattern ? firstPatternGapLength : 0 ) + middlePatternRepetitions * patternGapLength + lastPatternGapLength;
2959
2960 double dashLengthScalingFactor = 1;
2961 double gapLengthScalingFactor = 1;
2963 {
2964 // calculate scaling factors
2965 const double lengthToShrinkBy = totalUnscaledLengthOfPatterns - totalLength;
2966
2967 switch ( adjustment )
2968 {
2970 dashLengthScalingFactor = totalLength / totalUnscaledLengthOfPatterns;
2971 gapLengthScalingFactor = dashLengthScalingFactor;
2972 break;
2974 dashLengthScalingFactor = ( totalUnscaledLengthOfDashes - lengthToShrinkBy ) / totalUnscaledLengthOfDashes;
2975 break;
2977 gapLengthScalingFactor = ( totalUnscaledLengthOfGaps - lengthToShrinkBy ) / totalUnscaledLengthOfGaps;
2978 break;
2979 }
2980 }
2981
2982 dashLengthScalingFactor = std::max( dashLengthScalingFactor, 0.0 );
2983 gapLengthScalingFactor = std::max( gapLengthScalingFactor, 0.0 );
2984
2985 const int maxPatterns = middlePatternRepetitions + 2;
2986 result->reserve( maxPatterns );
2987
2988 int patternIndex = 0;
2989 double distanceToNextPointFromStartOfSegment = pattern.at( 0 ) * dashLengthScalingFactor;
2990 bool isDash = true;
2991 switch ( startRule )
2992 {
2995 break;
2997 distanceToNextPointFromStartOfSegment *= 0.5; // skip to half way through first dash
2998 break;
3000 patternIndex = patternSize - 1; // start at last gap
3001 isDash = false;
3002 distanceToNextPointFromStartOfSegment = pattern.at( patternSize - 1 ) * gapLengthScalingFactor;
3003 break;
3005 patternIndex = patternSize - 1; // skip straight to half way through last gap
3006 isDash = false;
3007 distanceToNextPointFromStartOfSegment = 0.5 * pattern.at( patternSize - 1 ) * gapLengthScalingFactor;
3008 break;
3009 }
3010
3011 const double adjustedOffset = fmod( patternOffset, patternLength );
3012 const double scaledOffset = ( adjustedOffset < 0 ? ( adjustedOffset + patternLength ) : adjustedOffset ) * ( gapLengthScalingFactor + dashLengthScalingFactor ) / 2;
3013 if ( !qgsDoubleNear( scaledOffset, 0 ) )
3014 {
3015 // shuffle pattern along by offset
3016 double remainingOffset = scaledOffset;
3017 while ( remainingOffset > 0 )
3018 {
3019 if ( distanceToNextPointFromStartOfSegment > remainingOffset )
3020 {
3021 distanceToNextPointFromStartOfSegment -= remainingOffset;
3022 break;
3023 }
3024
3025 remainingOffset -= distanceToNextPointFromStartOfSegment;
3026 isDash = !isDash;
3027 patternIndex++;
3028 if ( patternIndex == patternSize )
3029 patternIndex = 0;
3030
3031 distanceToNextPointFromStartOfSegment = pattern.at( patternIndex ) * ( isDash ? dashLengthScalingFactor : gapLengthScalingFactor );
3032 }
3033 }
3034
3035 if ( isDash )
3036 {
3037 outX.append( prevX );
3038 outY.append( prevY );
3039 }
3040
3041 for ( int i = 1; i < totalPoints; ++i )
3042 {
3043 double thisX = *x++;
3044 double thisY = *y++;
3045
3046 const double segmentLength = QgsGeometryUtilsBase::distance2D( thisX, thisY, prevX, prevY );
3047 while ( distanceToNextPointFromStartOfSegment < segmentLength || qgsDoubleNear( distanceToNextPointFromStartOfSegment, segmentLength ) )
3048 {
3049 // point falls on this segment - truncate to segment length if qgsDoubleNear test was actually > segment length
3050 const double distanceToPoint = std::min( distanceToNextPointFromStartOfSegment, segmentLength );
3051 double pX, pY;
3052 QgsGeometryUtilsBase::pointOnLineWithDistance( prevX, prevY, thisX, thisY, distanceToPoint, pX, pY );
3053
3054 outX.append( pX );
3055 outY.append( pY );
3056 if ( isDash )
3057 {
3058 result->addGeometry( new QgsLineString( outX, outY ) );
3059 outX.resize( 0 );
3060 outY.resize( 0 );
3061 }
3062
3063 isDash = !isDash;
3064 patternIndex++;
3065 if ( patternIndex >= patternSize )
3066 patternIndex = 0;
3067
3068 distanceToNextPointFromStartOfSegment += pattern.at( patternIndex ) * ( isDash ? dashLengthScalingFactor : gapLengthScalingFactor );
3069 }
3070
3071 if ( isDash )
3072 {
3073 outX.append( thisX );
3074 outY.append( thisY );
3075 }
3076
3077 prevX = thisX;
3078 prevY = thisY;
3079 distanceToNextPointFromStartOfSegment -= segmentLength;
3080 }
3081
3082 if ( isDash )
3083 {
3084 outX.append( prevX );
3085 outY.append( prevY );
3086 result->addGeometry( new QgsLineString( outX, outY ) );
3087 }
3088
3089 return result;
3090}
3091
3092std::unique_ptr< QgsAbstractGeometry > applyDashPatternPrivate( const QgsAbstractGeometry *geom,
3093 const QVector<double> &pattern,
3097 double patternOffset )
3098{
3099 std::unique_ptr< QgsAbstractGeometry > segmentizedCopy;
3100 if ( QgsWkbTypes::isCurvedType( geom->wkbType() ) )
3101 {
3102 segmentizedCopy.reset( geom->segmentize() );
3103 geom = segmentizedCopy.get();
3104 }
3105
3107 {
3108 return dashPatternAlongLine( static_cast< const QgsLineString * >( geom ), pattern, startRule, endRule, adjustment, patternOffset );
3109 }
3110 else
3111 {
3112 // polygon
3113 const QgsPolygon *polygon = static_cast< const QgsPolygon * >( geom );
3114 std::unique_ptr< QgsMultiLineString > result = std::make_unique< QgsMultiLineString >();
3115
3116 std::unique_ptr< QgsMultiLineString > exteriorParts = dashPatternAlongLine( static_cast< const QgsLineString * >( polygon->exteriorRing() ), pattern, startRule, endRule, adjustment, patternOffset );
3117 for ( int i = 0; i < exteriorParts->numGeometries(); ++i )
3118 result->addGeometry( exteriorParts->geometryN( i )->clone() );
3119
3120 for ( int i = 0; i < polygon->numInteriorRings(); ++i )
3121 {
3122 std::unique_ptr< QgsMultiLineString > ringParts = dashPatternAlongLine( static_cast< const QgsLineString * >( polygon->interiorRing( i ) ), pattern, startRule, endRule, adjustment, patternOffset );
3123 for ( int j = 0; j < ringParts->numGeometries(); ++j )
3124 result->addGeometry( ringParts->geometryN( j )->clone() );
3125 }
3126
3127 return result;
3128 }
3129}
3130
3132{
3133 if ( pattern.size() < 2 )
3134 return QgsGeometry( mGeometry->clone() );
3135
3136 mLastError.clear();
3137 if ( !mGeometry || mGeometry->isEmpty() )
3138 {
3139 return QgsGeometry();
3140 }
3141
3143 {
3144 return QgsGeometry( mGeometry->clone() ); // point geometry, nothing to do
3145 }
3146
3147 if ( const QgsGeometryCollection *gc = qgsgeometry_cast< const QgsGeometryCollection *>( mGeometry ) )
3148 {
3149 int numGeom = gc->numGeometries();
3150 QVector< QgsAbstractGeometry * > geometryList;
3151 geometryList.reserve( numGeom );
3152 for ( int i = 0; i < numGeom; ++i )
3153 {
3154 geometryList << applyDashPatternPrivate( gc->geometryN( i ), pattern, startRule, endRule, adjustment, patternOffset ).release();
3155 }
3156
3157 QgsGeometry first = QgsGeometry( geometryList.takeAt( 0 ) );
3158 for ( QgsAbstractGeometry *g : std::as_const( geometryList ) )
3159 {
3160 if ( const QgsGeometryCollection *collection = qgsgeometry_cast< QgsGeometryCollection * >( g ) )
3161 {
3162 for ( int j = 0; j < collection->numGeometries(); ++j )
3163 {
3164 first.addPartV2( collection->geometryN( j )->clone() );
3165 }
3166 delete collection;
3167 }
3168 else
3169 {
3170 first.addPartV2( g );
3171 }
3172 }
3173 return first;
3174 }
3175 else
3176 {
3177 return QgsGeometry( applyDashPatternPrivate( mGeometry, pattern, startRule, endRule, adjustment, patternOffset ) );
3178 }
3179}
DashPatternSizeAdjustment
Dash pattern size adjustment options.
Definition qgis.h:2817
@ 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:2802
@ 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:201
@ 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)
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:104
QgsPoint vertexAt(QgsVertexId) const override
Returns the point corresponding to a specified vertex id.
Definition qgspoint.cpp:526
double x
Definition qgspoint.h:52
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:737
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:477
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.
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:5465
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