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