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