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