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