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